Spring Security OAuth2案例

wen java案例 2

Spring Security OAuth2实战指南:从零搭建企业级授权中心(附完整案例)

目录导读

  1. OAuth2核心概念与授权流程回顾
  2. 项目环境准备与技术选型
  3. 案例实战:构建授权服务器(Authorization Server)
  4. 案例实战:构建资源服务器(Resource Server)
  5. 案例实战:客户端(Client)接入与令牌刷新
  6. 常见问题问答(FAQ)
  7. 最佳实践与安全加固建议

OAuth2核心概念与授权流程回顾

在深入Spring Security OAuth2案例之前,我们必须理清OAuth2协议的四个关键角色:资源所有者(用户)客户端(第三方应用)授权服务器资源服务器,最常用的授权模式是授权码模式(Authorization Code),它适用于有后端的Web应用。

Spring Security OAuth2案例

其核心流程为:

  1. 用户访问客户端,客户端将用户重定向到授权服务器。
  2. 用户登录并同意授权。
  3. 授权服务器返回授权码(code)给客户端。
  4. 客户端用授权码换取访问令牌(Access Token)和刷新令牌(Refresh Token)。
  5. 客户端携带Access Token访问资源服务器。

案例核心:我们将用Spring Boot 2.7.x + Spring Security OAuth2实现一个完整的授权码模式,并解决JWT令牌的配置与自定义用户信息扩展问题。


项目环境准备与技术选型

我们采用以下技术栈构建案例:

  • JDK 1.8+
  • Spring Boot 2.7.18(注意:Spring Boot 2.7是集成Spring Security OAuth2的稳定版本,3.x已改用OAuth2 Client/Resource Server分离)
  • Spring Security OAuth2 Autoconfigure(旧版依赖,但仍广泛用于企业存量项目)
  • JWT(JSON Web Token)作为令牌格式
  • Maven 构建工具
  • MySQL + Spring Data JPA (用于存储客户端详情)

Maven关键依赖(pom.xml)

<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <version>2.3.8.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-jwt</artifactId>
    <version>1.1.1.RELEASE</version>
</dependency>

案例实战:构建授权服务器(Authorization Server)

步骤1:配置授权服务器核心类

创建AuthorizationServerConfig,继承AuthorizationServerConfigurerAdapter并重写三个核心configure方法。

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private DataSource dataSource;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource)
               .withClient("client-app")
               .secret(passwordEncoder().encode("secret123"))
               .redirectUris("http://localhost:8082/login/oauth2/code/custom")
               .authorizedGrantTypes("authorization_code", "refresh_token")
               .scopes("read", "write")
               .accessTokenValiditySeconds(3600)
               .refreshTokenValiditySeconds(86400);
    }
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.authenticationManager(authenticationManager)
                 .tokenStore(tokenStore())
                 .accessTokenConverter(accessTokenConverter());
    }
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }
    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("test-secret-key-change-in-production");
        return converter;
    }
}

步骤2:配置Spring Security 认证管理器

定义WebSecurityConfig,允许登录页面和令牌端点访问,并对其他请求进行认证。

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("admin").password(passwordEncoder().encode("123456")).roles("USER");
    }
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

案例实战:构建资源服务器(Resource Server)

创建一个独立Spring Boot服务(端口9090),用于校验Access Token并返回受保护资源。

配置类:

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/api/**").authenticated()
            .anyRequest().permitAll();
    }
    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("test-secret-key-change-in-production"); // 与授权服务器一致
        return converter;
    }
    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }
}

资源控制器:

@RestController
@RequestMapping("/api")
public class UserResourceController {
    @GetMapping("/profile")
    public Map<String, String> getProfile(OAuth2Authentication auth) {
        Map<String, String> result = new HashMap<>();
        result.put("username", auth.getName());
        result.put("clientId", auth.getOAuth2Request().getClientId());
        return result;
    }
}

案例实战:客户端(Client)接入与令牌刷新

客户端应用(端口8082)使用Spring Security OAuth2 Client依赖,通过@EnableOAuth2Sso实现SSO登录。

application.yml关键配置:

server:
  port: 8082
security:
  oauth2:
    client:
      client-id: client-app
      client-secret: secret123
      access-token-uri: http://localhost:8080/oauth/token
      user-authorization-uri: http://localhost:8080/oauth/authorize
    resource:
      user-info-uri: http://localhost:9090/api/profile

刷新令牌实现:在后端定时任务或拦截器中,使用RestTemplate调用授权服务器的/oauth/token端点,grant_type=refresh_token获取新令牌。

private String refreshAccessToken(String refreshToken) {
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
    MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
    body.add("grant_type", "refresh_token");
    body.add("refresh_token", refreshToken);
    body.add("client_id", "client-app");
    body.add("client_secret", "secret123");
    HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(body, headers);
    ResponseEntity<Map> response = restTemplate.postForEntity("http://localhost:8080/oauth/token", entity, Map.class);
    return (String) response.getBody().get("access_token");
}

常见问题问答(FAQ)

Q1:为什么在Spring Boot 2.7之后官方废弃了@EnableAuthorizationServer

答:官方将OAuth2授权服务器迁移至Spring Authorization Server项目(独立于Spring Security),新版推荐使用OAuth2AuthorizationServerConfigurer,但存量企业项目中使用2.3.x依赖仍是主流,且维护稳定。

Q2:JWT令牌中如何自定义用户信息(例如ID、角色)?

答:在授权服务器中重写JwtAccessTokenConverterenhance方法,或自定义TokenEnhancer,示例:((DefaultOAuth2AccessToken) accessToken).getAdditionalInformation().put("userId", "12345")

Q3:资源服务器如何校验JWT有效性,无需每次都请求授权服务器?

答:使用JWT时,资源服务器通过签名密钥(对称密钥或公钥)本地校验,对称密钥需保证两端一致;非对称时,资源服务器配置JwtAccessTokenConvertersetVerifierKey为公钥即可。

Q4:如果刷新令牌失效了,客户端应如何处理?

答:最好实现“静默登录”兜底:当刷新令牌请求返回invalid_grant错误时,清除本地会话并重新引导用户走授权码流程,切勿无限重试。


最佳实践与安全加固建议

  • 令牌传输: 生产环境务必使用HTTPS,避免令牌在明文网络中传输。
  • 客户端密钥: client-secret不要硬编码在配置文件,建议使用环境变量或配置中心(如Nacos、Consul)。
  • JWT签名密钥: 生产环境更换为RSA非对称加密,非对称密钥对分开管理,且定期轮换。
  • 令牌存储: 如果使用Redis存储令牌,需将TokenStore改为RedisTokenStore,便于集中管理黑名单。
  • Scope权限细化: 避免使用通配符all,按业务拆分read_userwrite_order等细粒度权限。

本案例完整覆盖了Spring Security OAuth2的授权服务器、资源服务器、客户端三方角色,从配置到代码实战解决了核心流程,理解该案例后,您可轻松扩展到微服务架构下的单点登录、API网关鉴权等场景,OAuth2不是银弹,合理搭配JWT、刷新令牌和细粒度权限控制,才能构建安全而灵活的企业级授权体系。

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