Java实现OAuth2资源服务器案例

wen java案例 1

从零构建企业级OAuth2资源服务器:Java实战指南与安全陷阱规避


目录导读

  1. OAuth2资源服务器核心概念与Spring Security 5.x架构解析
  2. 环境准备与依赖管理(Spring Boot 3.x + NimbusJoseJwt)
  3. 资源服务器配置三件套:过滤器链、JWT解码器、自定义鉴权策略
  4. 实战案例:基于RSA公钥的JWT令牌校验与用户信息绑定
  5. 高频问答:令牌过期、跨域CORS、无状态会话及性能优化
  6. 安全加固:如何防御重放攻击与密钥轮换

OAuth2资源服务器核心概念与Spring Security 5.x架构解析

在微服务架构中,资源服务器(Resource Server)是保护API的“守门员”,它不直接颁发令牌,而是通过JWT(JSON Web Token)不透明令牌验证客户端请求的合法性,Spring Security 5.x/6.x通过oauth2ResourceServer()DSL提供声明式配置,其核心职责分为三层:

Java实现OAuth2资源服务器案例

  • 认证层:解析Authorization: Bearer <token>头,通过JwtDecoder校验签名、有效期及iss(签发方)等声明。
  • 授权层:将JWT中的scope或自定义authorities映射为Spring的GrantedAuthority,用于@PreAuthorize("hasAuthority('SCOPE_read')")注解。
  • 异常处理:自定义AuthenticationEntryPoint返回JSON格式的401/403响应,而非Spring Boot默认的HTML错误页。

环境准备与依赖管理

Maven依赖(关键版本)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

注意:避免引入spring-boot-starter-oauth2-client(它是客户端库),否则会冲突。

资源服务器配置三件套

(1)过滤器链配置(核心类:SecurityFilterChain):

@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .csrf(csrf -> csrf.disable()) // 无状态API必须关闭
        .sessionManagement(session -> session.sessionCreationPolicy(STATELESS))
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/api/admin/**").hasAuthority("ROLE_ADMIN")
            .requestMatchers("/api/public/**").permitAll()
            .anyRequest().authenticated()
        )
        .oauth2ResourceServer(oauth2 -> oauth2
            .jwt(jwt -> jwt.decoder(jwtDecoder()))
            .authenticationEntryPoint(customEntryPoint()) // 自定义401响应
        );
    return http.build();
}

(2)JWT解码器(支持RSA公钥JWK Set):

@Bean
JwtDecoder jwtDecoder() {
    return NimbusJwtDecoder.withJwkSetUri("https://auth.example.com/.well-known/jwks.json")
        .jwsAlgorithm(RS256)
        .build();
}

(3)自定义鉴权策略:解析JWT中的user_id声明并载入用户详情。

实战案例:基于RSA公钥的JWT令牌校验

业务场景:授权服务器(如Keycloak)签发的JWT,包含sub(用户名)、dept(部门)声明。
实现步骤

  • 步骤1:在application.yml中配置JWK Set URI或本地公钥。
  • 步骤2:创建JwtAuthenticationConverter定制GrantedAuthority
    JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
    converter.setJwtGrantedAuthoritiesConverter(jwt -> {
      List<String> scopes = jwt.getClaimAsStringList("scope");
      return scopes.stream().map(SimpleGrantedAuthority::new).toList();
    });
  • 步骤3:编写受保护接口:
    @GetMapping("/api/user/info")
    @PreAuthorize("hasAuthority('SCOPE_profile')")
    public UserInfo getUserInfo(@AuthenticationPrincipal Jwt jwt) {
      String userName = jwt.getClaimAsString("preferred_username");
      return userService.findByUsername(userName);
    }

测试指令

curl -H "Authorization: Bearer $JWT_TOKEN" https://api.example.com/api/user/info

高频问答(FAQ)

Q1:令牌过期后,为何Spring返回500而非401?
A:因为JwtDecoder抛出的JwtException默认未被映射到AuthenticationEntryPoint,需通过oauth2ResourceServerauthenticationEntryPoint定制捕获ExpiredJwtException并返回{"error":"token_expired"},HTTP状态码设为401。

Q2:如何解决前端跨域调用资源服务器?
A:在过滤器链中引入CORS配置,注意顺序cors()必须在oauth2ResourceServer()之前,JWT不依赖Cookie,无需设置Allow-Credentials,但需允许Authorization头。

Q3:资源服务器如何做到无状态且高性能?
A:JWT天然无状态,服务端不存储会话,为提高性能,可添加Redis二级缓存存储Jti(JWT ID)用于黑名单校验,并启用JwtDecoderCacheControl(仅缓存公钥,不缓存令牌)。

安全加固:防御重放攻击与密钥轮换

  • 重放攻击防御:检查jti声明(唯一令牌ID),若已在Redis的黑名单中则拒绝;同时比较iat(签发时间)与当前时间差应小于设定阈值(例如5分钟)。
  • 密钥轮换:授权服务器定期更换RSA密钥对,资源服务器通过每次启动时调用JWK Set URI并监听Set-Cookie中的ETag变化,动态刷新公钥,配置示例:
    NimbusJwtDecoder decoder = NimbusJwtDecoder.withJwkSetUri(uri)
      .restOperations(restTemplateWithRetry) // 增强网络容错
      .build();

本案例覆盖了从依赖、配置到业务集成的完整链路,读者在落地时务必关注两类问题:一是令牌格式(JWT vs Opaque)的选择,二是异常语义的统一(RFC 6750规范的WWW-Authenticate响应头),建议结合Postman进行多角色(Admin/User)权限矩阵测试,并在CI/CD流水线中嵌入TestRestTemplate集成测试,确保资源服务器的健壮性。

抱歉,评论功能暂时关闭!