SpringBoot + SpringSecurity + JWT 完整整合实战(生产级无状态认证)

SpringBoot + SpringSecurity + JWT 完整整合实战(生产级无状态认证)

前言

在前后端分离、微服务架构中,SpringSecurity + JWT 是企业级最主流安全方案

SpringSecurity 负责权限控制、登录校验;JWT 负责无状态令牌签发与校验。

本文实现一套可直接上线 的完整架构:

登录签发 JWT → 请求携带 Token → Security 自动鉴权 → 角色权限控制 → 异常统一处理。

全程标准配置,无冗余代码,适合后端进阶学习。


一、核心 Maven 依赖

xml 复制代码
<dependencies>
    <!-- Spring Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Spring Security -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    <!-- JWT 核心依赖 -->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-api</artifactId>
        <version>0.11.5</version>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-impl</artifactId>
        <version>0.11.5</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-jackson</artifactId>
        <version>0.11.5</version>
        <scope>runtime</scope>
    </dependency>
</dependencies>

二、JWT 常量配置

java 复制代码
public class JwtConstants {
    // 密钥(生产环境放入配置中心)
    public static final String SECRET_KEY = "SpringSecurityJwtSecret2026ABCDEFGHIJKLMN";
    // 令牌过期时间 2小时
    public static final long EXPIRATION = 2 * 60 * 60 * 1000;
    // 请求头名称
    public static final String TOKEN_HEADER = "Authorization";
    // 令牌前缀
    public static final String TOKEN_PREFIX = "Bearer ";
}

三、JWT 工具类

java 复制代码
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.util.Date;

@Component
public class JwtUtil {

    private final SecretKey secretKey = Keys.hmacShaKeyFor(JwtConstants.SECRET_KEY.getBytes());

    /**
     * 生成 JWT Token
     */
    public String generateToken(String username) {
        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + JwtConstants.EXPIRATION))
                .signWith(secretKey, SignatureAlgorithm.HS256)
                .compact();
    }

    /**
     * 从 Token 中获取用户名
     */
    public String getUsername(String token) {
        return getClaims(token).getSubject();
    }

    /**
     * 校验 Token 是否有效
     */
    public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder()
                    .setSigningKey(secretKey)
                    .build()
                    .parseClaimsJws(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 获取 Token 载荷
     */
    private Claims getClaims(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(secretKey)
                .build()
                .parseClaimsJws(token)
                .getBody();
    }
}

四、SpringSecurity 用户查询服务

java 复制代码
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

@Service
public class UserDetailServiceImpl implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 模拟数据库查询用户
        if (!"admin".equals(username)) {
            throw new UsernameNotFoundException("用户不存在");
        }

        // 密码 123456 加密
        String pwd = new BCryptPasswordEncoder().encode("123456");

        return User.withUsername("admin")
                .password(pwd)
                .roles("ADMIN")
                .build();
    }
}

五、JWT 认证过滤器(核心)

java 复制代码
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Resource
    private JwtUtil jwtUtil;

    @Resource
    private UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) {
        try {
            String token = request.getHeader(JwtConstants.TOKEN_HEADER);

            if (token != null && token.startsWith(JwtConstants.TOKEN_PREFIX)) {
                token = token.replace(JwtConstants.TOKEN_PREFIX, "");
                String username = jwtUtil.getUsername(token);

                if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                    UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                    if (jwtUtil.validateToken(token)) {
                        UsernamePasswordAuthenticationToken authToken =
                                new UsernamePasswordAuthenticationToken(
                                        userDetails, null, userDetails.getAuthorities());
                        authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                        SecurityContextHolder.getContext().setAuthentication(authToken);
                    }
                }
            }
            filterChain.doFilter(request, response);
        } catch (Exception e) {
            filterChain.doFilter(request, response);
        }
    }
}

六、SpringSecurity 核心配置

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.annotation.Resource;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Resource
    private JwtAuthenticationFilter jwtAuthenticationFilter;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
        return configuration.getAuthenticationManager();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers("/login").permitAll()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()

                // 未登录处理器
                .exceptionHandling().authenticationEntryPoint((request, response, e) -> {
                    response.setContentType("application/json;charset=utf-8");
                    response.getWriter().write("{\"code\":401,\"msg\":\"未登录或Token已过期\"}");
                })

                .and()
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}

七、登录控制器

java 复制代码
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;

@RestController
public class LoginController {

    @Resource
    private AuthenticationManager authenticationManager;

    @Resource
    private JwtUtil jwtUtil;

    /**
     * 登录接口,签发 JWT
     */
    @PostMapping("/login")
    public Map<String, Object> login(@RequestParam String username,
                                    @RequestParam String password) {
        Authentication auth = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(username, password)
        );

        String token = jwtUtil.generateToken(username);
        Map<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("msg", "登录成功");
        result.put("token", token);
        return result;
    }
}

八、测试接口

java 复制代码
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

    /**
     * 登录后可访问
     */
    @GetMapping("/user/info")
    public String userInfo() {
        return "当前登录用户信息";
    }

    /**
     * 仅 ADMIN 角色可访问
     */
    @GetMapping("/admin/dashboard")
    public String admin() {
        return "管理员控制台";
    }
}

九、接口使用说明

  1. 登录接口
  • 请求地址:/login
  • 请求方式:POST
  • 参数:username=admin、password=123456
  • 返回:JWT Token
  1. 业务接口
  • 请求头:Authorization: Bearer 你的Token
  • 未登录返回:401
  • 无权限返回:403

十、核心优势

  1. **无状态认证:**不依赖 Session,支持分布式、微服务
  2. **自动鉴权:**SpringSecurity 统一拦截、校验 Token
  3. **权限控制:**支持角色、权限细粒度控制
  4. **安全可靠:**BCrypt 密码加密 + JWT 防篡改签名
  5. **标准化:**企业主流架构,可直接上线

十一、总结

SpringSecurity + JWT 是后端必备安全技能。

本文提供的是一套生产级标准方案:

​ 登录、鉴权、权限、异常、加密全部完善。可直接用于:后台管理系统、微服务网关、APP 后端、企业平台。

作者介绍

专注 Java 后端、SpringBoot、SpringSecurity、微服务实战开发,

承接项目开发、架构升级、权限系统定制,欢迎 CSDN 私信交流。

相关推荐
澄风2 小时前
IDEA 代码模板配置教程(prs快捷生成private String)
java·ide·intellij-idea
弹简特2 小时前
【JavaEE25-后端部分】从“统一回执单”到“统一投诉处理”:Spring Boot 轻松搞定统一返回格式和统一异常处理
java·spring boot·后端·统一返回格式·统一异常
leo_messi942 小时前
2026版商城项目(二)-- 压力测试&缓存
java·缓存·压力测试·springcloud
ok_hahaha2 小时前
java从头开始-黑马点评-附近商户
java
丶小鱼丶2 小时前
数据结构和算法之【阻塞队列】下篇
java·数据结构
啥咕啦呛2 小时前
跟着AI学java第4天:面向对象编程巩固
java·开发语言·人工智能
lThE ANDE2 小时前
Spring Boot--@PathVariable、@RequestParam、@RequestBody
java·spring boot·后端
Treh UNFO2 小时前
Spring Boot环境配置
java·spring boot·后端
NaMM CHIN2 小时前
Spring boot整合quartz方法
java·前端·spring boot