Spring Security中集成JWT的完整实战指南

 更新时间:2026年05月17日 10:06:00   作者:身如柳絮随风扬  
本文深入探讨了现代Web应用中的认证授权方案,对比分析了JWT、Session-Cookie、OAuth2/OIDC、SAML 2.0等主流方案的优缺点及适用场景,感兴趣的小伙伴可以了解下

在构建现代 Web 应用和微服务时,认证与授权是绕不开的核心话题。JWT(JSON Web Token)凭借无状态、跨域友好等特性成为业界宠儿,但并非唯一选择。同时,很多人疑惑:Spring Security 中到底怎么和 JWT 一起使用?随机数又在哪些安全场景中发挥作用? 本文将从替代方案对比、随机数应用、Spring Security + JWT 实战三个方面,为你彻底理清这些面试高频问题。

一、除了 JWT,你还用过哪些认证/授权方案

在实际项目中,我根据场景还使用过以下方案:

方案原理适用场景优点缺点
传统 Session-Cookie服务端存储 Session,客户端存储 Cookie单体应用、用户量不大实现简单,主动失效方便占用服务端内存,横向扩展需共享 Session
OAuth2 / OIDC第三方授权,颁发 Access Token + Refresh Token允许用户授权第三方应用访问资源(如“使用微信登录”)标准协议,广泛支持实现复杂,需要引入授权服务器
SAML 2.0基于 XML 的安全断言标记语言,通过 SSO 认证企业级单点登录(如 Microsoft ADFS)成熟的企业标准XML 臃肿,配置复杂
API Key客户端携带固定 Key 调用 API内部服务调用、简单开放 API实现极简易泄露,无有效期,无用户身份
Basic AuthHTTP 头携带 base64(user:pass)内部测试、简单对接HTTP 原生支持明文传输(必须配合 HTTPS),无法注销

1.1 什么时候选择 JWT

  • 分布式/微服务架构:无需共享存储,自带用户信息。
  • 移动端/跨域:Cookie 限制少,头字段通用。
  • 短期授权:如 API 调用、单点登录的临时凭证。

1.2 项目中的真实选型

  • 内部运维系统:Session + Redis 共享存储。
  • 开放平台 API:API Key + Secret 签名(防止重放)。
  • 用户端 App + Web:JWT(无状态,便于水平扩展)。
  • 企业 SSO 接入:OAuth2SAML(对接公司统一认证)。

二、随机数在安全中的妙用(你一定用过!)

随机数并非仅仅“随机生成一个数字”,在安全领域有广泛且关键的用途:

应用场景随机数作用示例
密码加盐(Salt)为每个密码生成唯一随机数,与密码一起哈希,抵抗彩虹表攻击hash(password + salt)
验证码随机生成数字/字母,短期有效,防止暴力 破解短信验证码 123456
CSRF Token每个会话或每个请求生成随机 Token,防御跨站请求伪造Spring Security 默认开启 _csrf
重置密码 Token用户请求重置密码时,生成不可猜测的随机 Token 发送至邮箱token = UUID.randomUUID()
会话 ID随机生成会话标识,难以伪造JSESSIONID
OAuth2 State 参数防止 CSRF 攻击,随机字符串与请求绑定state=abc123
API 签名 Nonce一次性的随机数,防止请求重放攻击微信支付 nonce_str
ID 生成器(雪花算法)随机+时间+机器号,生成全局唯一 IDSnowflake ID

2.1 Java 中生成随机数的正确姿势

// 安全的随机数(推荐)
SecureRandom secureRandom = new SecureRandom();
byte[] bytes = new byte[16];
secureRandom.nextBytes(bytes);
String token = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
// UUID(部分随机,不应用于高安全场景)
String uuid = UUID.randomUUID().toString();
// 生成指定位数的数字验证码
String code = String.valueOf(secureRandom.nextInt(900000) + 100000);

三、Spring Security 中如何使用 JWT?——完整实战

Spring Security 本身不直接提供 JWT 支持,需要通过 过滤器 解析 JWT 并手动将认证信息存入 SecurityContext。下面是标准做法。

3.1 整体架构流程图

3.2 关键组件与代码实现

3.2.1 添加依赖(JJWT)

<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>

3.2.2 JwtTokenUtil(生成与解析)

@Component
public class JwtTokenUtil {
    @Value("${jwt.secret}")
    private String secret;
    @Value("${jwt.expiration}")
    private Long expiration;

    public String generateToken(String username) {
        return Jwts.builder()
            .setSubject(username)
            .setIssuedAt(new Date())
            .setExpiration(new Date(System.currentTimeMillis() + expiration))
            .signWith(SignatureAlgorithm.HS256, secret)
            .compact();
    }

    public String getUsernameFromToken(String token) {
        return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody().getSubject();
    }

    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
            return true;
        } catch (JwtException | IllegalArgumentException e) {
            return false;
        }
    }
}

3.2.3 JwtAuthenticationFilter(继承 OncePerRequestFilter)

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                    HttpServletResponse response, 
                                    FilterChain chain) throws IOException, ServletException {
        String token = extractToken(request);
        if (token != null && jwtTokenUtil.validateToken(token)) {
            String username = jwtTokenUtil.getUsernameFromToken(token);
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            UsernamePasswordAuthenticationToken authentication = 
                new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        chain.doFilter(request, response);
    }

    private String extractToken(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}

3.2.4 SecurityConfig 配置

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Autowired
    private JwtAuthenticationFilter jwtFilter;
    @Autowired
    private UserDetailsService userDetailsService;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeRequests()
                .antMatchers("/login", "/register").permitAll()
                .anyRequest().authenticated()
            .and()
            .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }

    @Bean
    public AuthenticationManager authenticationManager() throws Exception {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService);
        provider.setPasswordEncoder(passwordEncoder());
        return new ProviderManager(provider);
    }

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

3.2.5 登录接口(生成 JWT)

@RestController
public class AuthController {
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginRequest request) {
        try {
            authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())
            );
            String token = jwtTokenUtil.generateToken(request.getUsername());
            return ResponseEntity.ok(new JwtResponse(token));
        } catch (BadCredentialsException e) {
            return ResponseEntity.status(401).body("用户名或密码错误");
        }
    }
}

3.3 Spring Security 在 JWT 中所起的作用

角色说明
AuthenticationManager负责验证用户名密码
SecurityContextHolder存储已认证的用户信息,供后续业务代码使用(如 @PreAuthorize
过滤器链我们插入的 JwtAuthenticationFilterUsernamePasswordAuthenticationFilter 之前执行
UserDetailsService加载用户权限,用于生成 Authentication 对象

核心:Spring Security 负责管理认证状态,JWT 只是传递凭证的一种形式。我们编写的过滤器将 JWT 解析后转换为 Spring Security 能识别的 Authentication 对象。

四、总结与面试回答模板

面试官问:除了 JWT 你还用过其他认证方案吗?随机数用过没?

参考回答

“用过。在传统单体项目中我使用 Session + Redis 共享存储;对接第三方登录时使用 OAuth2;内部服务间调用使用 API Key + 签名。随机数在安全方面应用广泛:比如用户密码的 盐值、短信验证码、重置密码的 Token、OAuth2 的 state 参数 以及防止重放攻击的 Nonce。我们项目中用 SecureRandom 生成高强度的随机数,比 Random 更安全。”

面试官问:你在 JWT 哪儿使用过 Spring Security?

参考回答:“在 Spring Boot 项目中,我将 JWT 作为无状态认证方案集成到 Spring Security。具体做法是:

  1. 编写 JwtAuthenticationFilter 继承 OncePerRequestFilter,解析请求头中的 JWT。
  2. 如果 JWT 有效,从中提取用户名,调用 UserDetailsService 加载权限,构造 UsernamePasswordAuthenticationToken 并存入 SecurityContextHolder
  3. SecurityConfig 中禁用 Session,设置 SessionCreationPolicy.STATELESS,并将自定义过滤器加到 UsernamePasswordAuthenticationFilter 之前。
  4. 登录接口使用 AuthenticationManager 校验用户名密码,校验通过后生成 JWT 返回。

Spring Security 在这里提供认证管理权限控制的骨架,JWT 只是凭证载体。”

到此这篇关于Spring Security中集成JWT的完整实战指南的文章就介绍到这了,更多相关Spring Security集成JWT内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java判空的一些常见方法

    Java判空的一些常见方法

    这篇文章主要给大家分享介绍了Java判空的一些常见方法,在程序中必须进行严格的判空处理,避免对空对象的异常操作,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-07-07
  • MyBatis-plus中的模糊查询解读

    MyBatis-plus中的模糊查询解读

    这篇文章主要介绍了MyBatis-plus中的模糊查询解读,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-05-05
  • SpringBoot+Redis哨兵模式的实现

    SpringBoot+Redis哨兵模式的实现

    本文主要介绍了SpringBoot+Redis哨兵模式的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-05-05
  • spring @Scheduled定时任务注解使用方法及注意事项小结

    spring @Scheduled定时任务注解使用方法及注意事项小结

    Spring的@Scheduled注解用于定时任务调度,默认单线程依次执行,可以通过配置多线程调度器或使用@Async注解实现并行执行,常见参数包括cron、fixedRate、fixedDelay、initialDelay等,本文介绍spring @Scheduled定时任务注解使用方法,感兴趣的朋友一起看看吧
    2025-02-02
  • MyBatis注解CRUD与执行流程深入探究

    MyBatis注解CRUD与执行流程深入探究

    这篇文章主要介绍了MyBatis注解CRUD与执行流程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2023-02-02
  • RateLimit-使用guava来做接口限流代码示例

    RateLimit-使用guava来做接口限流代码示例

    这篇文章主要介绍了RateLimit-使用guava来做接口限流代码示例,具有一定借鉴价值,需要的朋友可以参考下
    2018-01-01
  • 关于SpingMVC的<context:component-scan>包扫描踩坑记录

    关于SpingMVC的<context:component-scan>包扫描踩坑记录

    这篇文章主要介绍了关于SpingMVC的<context:component-scan>包扫描踩坑记录,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • 浅谈springboot项目中定时任务如何优雅退出

    浅谈springboot项目中定时任务如何优雅退出

    这篇文章主要介绍了浅谈springboot项目中定时任务如何优雅退出?具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • 关于Spring的@Transaction导致数据库回滚全部生效问题(又删库跑路)

    关于Spring的@Transaction导致数据库回滚全部生效问题(又删库跑路)

    使用@Transactional一键开启声明式事务, 这就真的事务生效了?过于信任框架总有“意外惊喜”。本文通过案例给大家详解关于Spring的@Transaction导致数据库回滚全部生效问题,感兴趣的朋友一起看看吧
    2021-05-05
  • idea导入若依项目教程

    idea导入若依项目教程

    文章介绍了如何在IntelliJ IDEA中导入若依管理系统项目,并详细步骤包括克隆项目、修改配置文件、创建数据库、运行项目和前端展示
    2025-03-03

最新评论