java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Spring Security Oauth2整合JWT

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

作者:心态特好

这篇文章主要介绍了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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文