Spring Security Oauth2整合JWT的详细步骤和核心配置

 更新时间:2025年11月01日 11:38:08   作者:心态特好  
这篇文章主要介绍了Spring Security Oauth2整合JWT的详细步骤和核心配置,包括依赖引入、JWT工具类、OAuth2和资源服务器配置以及SpringSecurity配置,文中通过代码介绍的非常详细,需要的朋友可以参考下

先说步骤:

在 Spring Security 中整合 OAuth2 与 JWT,可实现基于令牌的认证授权机制,适合分布式系统场景。以下是详细的整合步骤和核心配置:

1. 依赖引入

pom.xml中添加核心依赖(以 Spring Boot 为例):

<!-- Spring Security OAuth2 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <version>2.3.8.RELEASE</version>
</dependency>

<!-- JWT支持 -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

<!-- Spring Web(用于测试接口) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

2. 核心配置

2.1 JWT 工具类

用于生成、解析 JWT 令牌:

@Component
public class JwtTokenUtil {
    // 密钥(实际项目中需加密存储)
    private static final String SECRET = "your-secret-key";
    // 过期时间(7天)
    private static final long EXPIRATION = 604800000L;

    // 生成JWT令牌
    public String generateToken(String username) {
        Date now = new Date();
        Date expirationDate = new Date(now.getTime() + EXPIRATION);

        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(now)
                .setExpiration(expirationDate)
                .signWith(SignatureAlgorithm.HS512, 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 (Exception e) {
            return false;
        }
    }
}

2.2 OAuth2 配置(授权服务器)

配置授权服务器,指定 JWT 作为令牌格式:

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    // 配置客户端信息(如客户端ID、密钥、授权类型等)
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("client-id") // 客户端ID
                .secret(passwordEncoder().encode("client-secret")) // 客户端密钥
                .authorizedGrantTypes("password", "refresh_token") // 支持的授权类型
                .scopes("read", "write") // 权限范围
                .accessTokenValiditySeconds(3600) // 访问令牌过期时间
                .refreshTokenValiditySeconds(86400); // 刷新令牌过期时间
    }

    // 配置令牌存储(使用JWT)
    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter() {
            @Override
            protected String encode(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
                // 自定义JWT载荷(可选)
                Map<String, Object> claims = new HashMap<>(accessToken.getAdditionalInformation());
                claims.put("username", authentication.getName());
                claims.put("authorities", authentication.getAuthorities().stream()
                        .map(GrantedAuthority::getAuthority)
                        .collect(Collectors.toList()));
                DefaultOAuth2AccessToken customToken = new DefaultOAuth2AccessToken(accessToken);
                customToken.setAdditionalInformation(claims);
                return super.encode(customToken, authentication);
            }
        };
        converter.setSigningKey(jwtTokenUtil.SECRET); // 设置签名密钥
        return converter;
    }

    // 配置令牌服务
    @Bean
    public AuthorizationServerTokenServices tokenServices() {
        DefaultTokenServices services = new DefaultTokenServices();
        services.setClientDetailsService(clientDetailsService());
        services.setSupportRefreshToken(true);
        services.setTokenStore(tokenStore());
        services.setAccessTokenValiditySeconds(3600);
        services.setRefreshTokenValiditySeconds(86400);
        return services;
    }

    // JWT令牌存储
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService)
                .tokenStore(tokenStore())
                .accessTokenConverter(accessTokenConverter());
    }

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

2.3 资源服务器配置

配置受保护的资源,验证 JWT 令牌:

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    // 资源ID(需与授权服务器配置一致)
    private static final String RESOURCE_ID = "resource-id";

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources
                .resourceId(RESOURCE_ID)
                .tokenStore(tokenStore()); // 使用JWT令牌存储
    }

    // 配置资源访问规则
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/public/**").permitAll() // 公开接口
                .antMatchers("/api/**").authenticated() // 需认证的接口
                .antMatchers("/admin/**").hasRole("ADMIN"); // 需ADMIN角色
    }

    @Bean
    public TokenStore tokenStore() {
        // 配置JWT验证
        return new JwtTokenStore(new JwtAccessTokenConverter() {{
            setSigningKey(jwtTokenUtil.SECRET);
        }});
    }
}

2.4 Spring Security 配置

配置用户认证信息和密码加密:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        // 内存用户(实际项目中替换为数据库查询)
        UserDetails user = User.withUsername("user")
                .password(passwordEncoder().encode("123456"))
                .roles("USER")
                .build();
        UserDetails admin = User.withUsername("admin")
                .password(passwordEncoder().encode("123456"))
                .roles("ADMIN")
                .build();
        return new InMemoryUserDetailsManager(user, admin);
    }

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

3. 测试接口

编写测试接口验证效果:

@RestController
public class TestController {

    // 公开接口
    @GetMapping("/public/hello")
    public String publicHello() {
        return "Public Hello!";
    }

    // 需认证的接口
    @GetMapping("/api/hello")
    public String apiHello(Authentication authentication) {
        return "API Hello, " + authentication.getName() + "!";
    }

    // 需ADMIN角色的接口
    @GetMapping("/admin/hello")
    public String adminHello(Authentication authentication) {
        return "Admin Hello, " + authentication.getName() + "!";
    }
}

4. 测试流程

4.1 获取令牌

通过password模式请求授权服务器的令牌端点:

POST http://localhost:8080/oauth/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic Y2xpZW50LWlkOmNsaWVudC1zZWNyZXQ=  # client-id:client-secret的Base64编码

grant_type=password&username=user&password=123456

返回结果(JWT 格式的 access_token):

{
  "access_token": "eyJhbGciOiJIUzUxMiJ9...",
  "token_type": "bearer",
  "refresh_token": "eyJhbGciOiJIUzUxMiJ9...",
  "expires_in": 3599,
  "scope": "read write"
}

4.2 访问受保护资源

使用获取的access_token访问接口:

GET http://localhost:8080/api/hello
Authorization: Bearer eyJhbGciOiJIUzUxMiJ9...

返回:API Hello, user!

关键注意事项

  1. 密钥安全:JWT 签名密钥需妥善保管,避免硬编码(可通过配置中心或环境变量注入)。
  2. 令牌过期:合理设置access_tokenrefresh_token的过期时间,平衡安全性和用户体验。
  3. 自定义载荷:可在 JWT 中添加用户角色、权限等信息,减少资源服务器查询数据库的次数。
  4. HTTPS:生产环境必须使用 HTTPS 传输令牌,防止中间人攻击。

通过以上配置,即可实现 Spring Security OAuth2 与 JWT 的整合,支持基于令牌的认证授权。

在来讲几个简单的概念:

JWT 令牌包含了用户信息(或其他数据),并通过数字签名来保证这些信息的完整性和真实性

具体来说,JWT 令牌由三部分组成(用 . 分隔):

  1. 头部(Header):说明签名算法(比如 HMAC、RSA 等)。
  2. 载荷(Payload):存放实际要传递的信息(比如用户 ID、角色、过期时间等,这些就是 “用户信息”)。
  3. 签名(Signature):用头部指定的算法,结合服务器的密钥(或私钥),对 “头部 + 载荷” 进行加密生成的数字签名。

所以,JWT 不只是数字签名,而是 “信息 + 签名” 的组合体。签名的作用是:

  • 证明载荷里的信息没被篡改(因为篡改后签名会失效);
  • 证明信息确实来自可信的服务器(因为只有服务器有密钥能生成正确的签名)。

简单讲,JWT 就像一封 “带防伪签名的信”:信里写了内容(用户信息),信封上的签名(数字签名)保证信没被拆过、没被改,且确实是发件人(服务器)发的。

JWT可以使用HMAC算法或使用RSA的公钥/私钥对来签名,防止被篡改。 官网:https://jwt.io/
 JWT 令牌的优点:
jwt 基于json,非常方便解析。
可以在令牌中自定义丰富的内容,易扩展。
通过非对称加密算法及数字签名技术,JWT防止篡改,安全性高。
资源服务使用JWT可不依赖认证服务即可完成授权。
缺点:
JWT 令牌较长,占存储空间比较大

总结 

到此这篇关于Spring Security Oauth2整合JWT的详细步骤和核心配置的文章就介绍到这了,更多相关Spring Security Oauth2整合JWT内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Spring boot 处理大文件上传完整代码

    Spring boot 处理大文件上传完整代码

    这篇文章主要介绍了Spring boot 处理大文件上传,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-07-07
  • Java开源工具iText生成PDF简单实例

    Java开源工具iText生成PDF简单实例

    这篇文章主要介绍了Java开源工具iText生成PDF简单实例,本文给出了3段代码实例,讲解创建一个简单PDF文件,在PDF中添加表格以及在PDF中添加图片,需要的朋友可以参考下
    2015-07-07
  • 一文搞懂Java顶层类之Object类的使用

    一文搞懂Java顶层类之Object类的使用

    java.lang.Object类是Java语言中的根类,即所有类的父类。它中描述的所有方法子类都可以使用。本文主要介绍了Object类中toString和equals方法的使用,感兴趣的小伙伴可以了解一下
    2022-11-11
  • 使用Java实现文件流转base64

    使用Java实现文件流转base64

    这篇文章主要为大家详细介绍了如何使用Java实现文件流转base64效果,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-03-03
  • Java中ByteArrayInputStream和ByteArrayOutputStream用法详解

    Java中ByteArrayInputStream和ByteArrayOutputStream用法详解

    这篇文章主要介绍了Java中ByteArrayInputStream和ByteArrayOutputStream用法详解, ByteArrayInputStream 的内部额外的定义了一个计数器,它被用来跟踪 read() 方法要读取的下一个字节
    2022-06-06
  • Java正则多字符串匹配替换

    Java正则多字符串匹配替换

    正则表达式异常强大,一直理解不深,用的也不深,这次项目中尝试,体会到了它的强大之处。字符串查找,匹配,替换,正则无不能做,特别是灵活的运用子串匹配得到的变量值$1,$2,再进行二次处理能够达到很巧妙的效果。
    2013-02-02
  • Vue3源码解读effectScope API及实现原理

    Vue3源码解读effectScope API及实现原理

    这篇文章主要为大家介绍了Vue3源码解读effectScope API及实现原理,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03
  • SpringDataElasticsearch与SpEL表达式实现ES动态索引

    SpringDataElasticsearch与SpEL表达式实现ES动态索引

    这篇文章主要介绍了SpringDataElasticsearch与SpEL表达式实现ES动态索引,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的朋友可以参考一下
    2022-09-09
  • java数组排序示例分享

    java数组排序示例分享

    这篇文章主要介绍了java数组排序示例,需要的朋友可以参考下
    2014-03-03
  • Java中的缓冲区(Buffer)及其作用是什么详解

    Java中的缓冲区(Buffer)及其作用是什么详解

    缓冲区用于提升I/O效率,Java提供字符流、字节流及ByteBuffer类型,拥有capacity、position等属性,这篇文章主要介绍了Java中的缓冲区(Buffer)及其作用是什么的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-05-05

最新评论