Spring Security OAuth2 万字详解
1. OAuth2 基础概念
1.1 什么是 OAuth2
OAuth 2.0 是一个授权框架,它允许第三方应用获取对 HTTP 服务的有限访问权限。OAuth2 的核心思想是资源所有者(用户)可以授权客户端应用访问其存储在资源服务器上的受保护资源,而无需共享其凭据(如用户名和密码)。
1.2 OAuth2 核心角色
- 资源所有者 (Resource Owner):能够授权访问受保护资源的实体,通常是最终用户
- 资源服务器 (Resource Server):托管受保护资源的服务器,能够接受并使用访问令牌来响应受保护资源请求
- 客户端 (Client):代表资源所有者及其授权发出受保护资源请求的应用程序
- 授权服务器 (Authorization Server):在成功验证资源所有者并获得授权后,向客户端发放访问令牌的服务器
1.3 OAuth2 授权流程
text
+--------+ +---------------+ | |--(A)- Authorization Request ->| Resource | | | | Owner | | |<-(B)-- Authorization Grant ---| | | | +---------------+ | | | | +---------------+ | |--(C)-- Authorization Grant -->| Authorization | | Client | | Server | | |<-(D)----- Access Token -------| | | | +---------------+ | | | | +---------------+ | |--(E)----- Access Token ------>| Resource | | | | Server | | |<-(F)--- Protected Resource ---| | +--------+ +---------------+
2. Spring Security OAuth2 架构
2.1 Spring Security OAuth2 组成
Spring Security OAuth2 包含以下核心模块:
- spring-security-oauth2-core:OAuth2 核心功能
- spring-security-oauth2-client:OAuth2 客户端支持
- spring-security-oauth2-resource-server:OAuth2 资源服务器支持
- spring-security-oauth2-jose:JWT 支持
2.2 Spring Security OAuth2 授权服务器
授权服务器负责:
- 验证客户端身份
- 验证资源所有者身份
- 获取资源所有者授权
- 颁发访问令牌和刷新令牌
2.3 Spring Security OAuth2 资源服务器
资源服务器负责:
- 验证访问令牌
- 提供对受保护资源的访问
3. 环境搭建与配置
3.1 项目依赖配置
xml
<!-- Spring Boot Starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- OAuth2 授权服务器 --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-authorization-server</artifactId> <version>0.3.1</version> </dependency> <!-- OAuth2 资源服务器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-resource-server</artifactId> </dependency> <!-- OAuth2 客户端 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-client</artifactId> </dependency> <!-- JWT 支持 --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-jose</artifactId> </dependency> <!-- 数据库支持 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency>
3.2 基础配置类
java
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean @Order(1) public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); return http.formLogin(Customizer.withDefaults()).build(); } @Bean @Order(2) public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorize -> authorize .anyRequest().authenticated() ) .formLogin(Customizer.withDefaults()); return http.build(); } @Bean public UserDetailsService userDetailsService() { UserDetails userDetails = User.withDefaultPasswordEncoder() .username("user") .password("password") .roles("USER") .build(); return new InMemoryUserDetailsManager(userDetails); } @Bean public RegisteredClientRepository registeredClientRepository() { RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString()) .clientId("client") .clientSecret("{noop}secret") .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) .redirectUri("http://localhost:8080/login/oauth2/code/client") .scope("read") .scope("write") .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()) .build(); return new InMemoryRegisteredClientRepository(registeredClient); } @Bean public JWKSource<SecurityContext> jwkSource() { KeyPair keyPair = generateRsaKey(); JWKSet jwkSet = new JWKSet(new RSAKey.Builder((RSAPublicKey) keyPair.getPublic()) .privateKey((RSAPrivateKey) keyPair.getPrivate()) .keyID(UUID.randomUUID().toString()) .build()); return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); } private static KeyPair generateRsaKey() { KeyPair keyPair; try { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(2048); keyPair = keyPairGenerator.generateKeyPair(); } catch (Exception ex) { throw new IllegalStateException(ex); } return keyPair; } @Bean public ProviderSettings providerSettings() { return ProviderSettings.builder().build(); } }
4. 授权服务器详细配置
4.1 授权服务器核心配置
java
@Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Autowired private DataSource dataSource; @Autowired private UserDetailsService userDetailsService; // Token存储方式 - 使用数据库存储 @Bean public TokenStore tokenStore() { return new JdbcTokenStore(dataSource); } // 客户端信息配置 - 使用数据库存储 @Bean public ClientDetailsService clientDetailsService() { return new JdbcClientDetailsService(dataSource); } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security .tokenKeyAccess("permitAll()") // 公开/oauth/token_key接口 .checkTokenAccess("isAuthenticated()") // 认证后可访问/oauth/check_token .allowFormAuthenticationForClients(); // 允许客户端使用表单认证 } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients .inMemory() .withClient("client1") .secret("{noop}secret1") .authorizedGrantTypes("authorization_code", "refresh_token", "password", "client_credentials") .scopes("read", "write") .redirectUris("http://localhost:8081/login/oauth2/code/client1") .accessTokenValiditySeconds(3600) // 访问令牌有效期1小时 .refreshTokenValiditySeconds(2592000) // 刷新令牌有效期30天 .and() .withClient("client2") .secret("{noop}secret2") .authorizedGrantTypes("authorization_code", "implicit") .scopes("read") .redirectUris("http://localhost:8082/login/oauth2/code/client2") .autoApprove(true); // 自动批准,无需用户确认 } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints .authenticationManager(authenticationManager) .userDetailsService(userDetailsService) .tokenStore(tokenStore()) .pathMapping("/oauth/confirm_access", "/custom/confirm_access") // 自定义授权确认页面 .pathMapping("/oauth/error", "/custom/error"); // 自定义错误页面 } }
4.2 JWT Token 配置
java
@Configuration public class JwtTokenConfig { @Bean public TokenEnhancer jwtTokenEnhancer() { return new CustomTokenEnhancer(); } @Bean public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); // 对称密钥 converter.setSigningKey("my-secret-key-12345"); // 或者使用非对称密钥 // KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory( // new ClassPathResource("jwt.jks"), "mySecretKey".toCharArray()); // converter.setKeyPair(keyStoreKeyFactory.getKeyPair("jwt")); return converter; } public static class CustomTokenEnhancer implements TokenEnhancer { @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { Map<String, Object> additionalInfo = new HashMap<>(); additionalInfo.put("organization", authentication.getName() + "ORG"); ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo); return accessToken; } } }
5. 资源服务器配置
5.1 资源服务器基础配置
java
@Configuration @EnableResourceServer public class ResourceServerConfig extends ResourceServerConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/public/**").permitAll() // 公共接口无需认证 .antMatchers("/api/admin/**").hasRole("ADMIN") // 需要ADMIN角色 .antMatchers("/api/user/**").hasRole("USER") // 需要USER角色 .anyRequest().authenticated() // 其他所有接口需要认证 .and() .exceptionHandling() .accessDeniedHandler(new CustomAccessDeniedHandler()) // 自定义访问拒绝处理器 .authenticationEntryPoint(new CustomAuthenticationEntryPoint()); // 自定义认证入口点 } @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources .resourceId("resource-server-rest-api") // 资源ID .tokenStore(tokenStore()) // Token存储方式 .stateless(true); // 无状态模式 } @Bean public TokenStore tokenStore() { return new JwtTokenStore(accessTokenConverter()); } @Bean public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setSigningKey("my-secret-key-12345"); return converter; } }
5.2 JWT 资源服务器配置(新版本)
java
@Configuration @EnableWebSecurity public class ResourceServerConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorize -> authorize .requestMatchers("/public/**").permitAll() .anyRequest().authenticated() ) .oauth2ResourceServer(oauth2 -> oauth2 .jwt(jwt -> jwt .jwtAuthenticationConverter(jwtAuthenticationConverter()) ) .authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint()) .accessDeniedHandler(new BearerTokenAccessDeniedHandler()) ); return http.build(); } private Converter<Jwt, ? extends AbstractAuthenticationToken> jwtAuthenticationConverter() { JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter(); jwtConverter.setJwtGrantedAuthoritiesConverter(new CustomJwtGrantedAuthoritiesConverter()); return jwtConverter; } @Bean public JwtDecoder jwtDecoder() { return NimbusJwtDecoder.withJwkSetUri("http://localhost:8080/oauth2/jwks").build(); } }
6. OAuth2 授权模式详解
6.1 授权码模式 (Authorization Code)
流程:
- 客户端将用户重定向到授权服务器
- 用户授权
- 授权服务器返回授权码
- 客户端使用授权码换取访问令牌
配置:
java
@Controller public class AuthorizationController { @GetMapping("/oauth/authorize") public String authorize(Model model, @RequestParam("client_id") String clientId, @RequestParam("response_type") String responseType, @RequestParam("redirect_uri") String redirectUri, @RequestParam("scope") String scope, @RequestParam("state") String state) { model.addAttribute("clientId", clientId); model.addAttribute("scopes", scope.split(" ")); model.addAttribute("state", state); return "authorize"; } @PostMapping("/oauth/authorize") public String approveAuthorization(@RequestParam Map<String, String> parameters, Principal principal) { // 处理授权逻辑 return "redirect:/oauth/confirm_access"; } }
客户端配置:
java
@Configuration @EnableOAuth2Client public class OAuth2ClientConfig { @Bean public OAuth2ProtectedResourceDetails oauth2RemoteResource() { AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails(); details.setId("client1"); details.setClientId("client1"); details.setClientSecret("secret1"); details.setAccessTokenUri("http://localhost:8080/oauth/token"); details.setUserAuthorizationUri("http://localhost:8080/oauth/authorize"); details.setScope(Arrays.asList("read", "write")); return details; } @Bean public OAuth2RestTemplate oauth2RestTemplate(OAuth2ClientContext clientContext) { return new OAuth2RestTemplate(oauth2RemoteResource(), clientContext); } }
6.2 密码模式 (Resource Owner Password Credentials)
java
// 客户端使用密码模式获取令牌 @RestController public class TokenController { @Autowired private OAuth2RestTemplate oauth2RestTemplate; @PostMapping("/login") public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) { try { // 设置用户名和密码 AccessTokenRequest accessTokenRequest = oauth2RestTemplate.getOAuth2ClientContext().getAccessTokenRequest(); accessTokenRequest.set("username", loginRequest.getUsername()); accessTokenRequest.set("password", loginRequest.getPassword()); // 获取访问令牌 OAuth2AccessToken token = oauth2RestTemplate.getAccessToken(); return ResponseEntity.ok(token); } catch (Exception e) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } } }
6.3 客户端模式 (Client Credentials)
java
@Configuration public class ClientCredentialsConfig { @Bean public OAuth2ProtectedResourceDetails clientCredentialsResourceDetails() { ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails(); details.setAccessTokenUri("http://localhost:8080/oauth/token"); details.setClientId("client1"); details.setClientSecret("secret1"); details.setScope(Arrays.asList("read", "write")); return details; } @Bean public RestTemplate clientCredentialsRestTemplate() { return new OAuth2RestTemplate(clientCredentialsResourceDetails()); } }
6.4 简化模式 (Implicit)
java
// 前端JavaScript代码示例 function implicitGrant() { const authUrl = 'http://localhost:8080/oauth/authorize?' + 'response_type=token&' + 'client_id=client2&' + 'redirect_uri=http://localhost:8082/callback&' + 'scope=read&' + 'state=' + Math.random().toString(36).substring(2); window.location.href = authUrl; } // 回调处理 function handleCallback() { const hash = window.location.hash.substring(1); const params = new URLSearchParams(hash); const accessToken = params.get('access_token'); const tokenType = params.get('token_type'); const expiresIn = params.get('expires_in'); if (accessToken) { localStorage.setItem('access_token', accessToken); // 使用访问令牌访问受保护资源 } }
7. 数据库存储配置
7.1 数据库表结构
sql
-- OAuth2 客户端信息表 CREATE TABLE oauth_client_details ( client_id VARCHAR(256) PRIMARY KEY, resource_ids VARCHAR(256), client_secret VARCHAR(256), scope VARCHAR(256), authorized_grant_types VARCHAR(256), web_server_redirect_uri VARCHAR(256), authorities VARCHAR(256), access_token_validity INTEGER, refresh_token_validity INTEGER, additional_information VARCHAR(4096), autoapprove VARCHAR(256) ); -- 访问令牌表 CREATE TABLE oauth_access_token ( token_id VARCHAR(256), token BLOB, authentication_id VARCHAR(256) PRIMARY KEY, user_name VARCHAR(256), client_id VARCHAR(256), authentication BLOB, refresh_token VARCHAR(256) ); -- 刷新令牌表 CREATE TABLE oauth_refresh_token ( token_id VARCHAR(256), token BLOB, authentication BLOB ); -- 授权码表 CREATE TABLE oauth_code ( code VARCHAR(256), authentication BLOB );
7.2 JPA 实体类
java
@Entity @Table(name = "oauth_client_details") public class OAuthClientDetails { @Id @Column(name = "client_id") private String clientId; @Column(name = "resource_ids") private String resourceIds; @Column(name = "client_secret") private String clientSecret; @Column(name = "scope") private String scope; @Column(name = "authorized_grant_types") private String authorizedGrantTypes; @Column(name = "web_server_redirect_uri") private String webServerRedirectUri; @Column(name = "authorities") private String authorities; @Column(name = "access_token_validity") private Integer accessTokenValidity; @Column(name = "refresh_token_validity") private Integer refreshTokenValidity; @Column(name = "additional_information") private String additionalInformation; @Column(name = "autoapprove") private String autoapprove; // getters and setters }
8. 自定义用户认证
8.1 自定义 UserDetailsService
java
@Service public class CustomUserDetailsService implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("用户不存在: " + username)); return org.springframework.security.core.userdetails.User.builder() .username(user.getUsername()) .password(user.getPassword()) .authorities(getAuthorities(user)) .accountExpired(!user.isAccountNonExpired()) .accountLocked(!user.isAccountNonLocked()) .credentialsExpired(!user.isCredentialsNonExpired()) .disabled(!user.isEnabled()) .build(); } private Collection<? extends GrantedAuthority> getAuthorities(User user) { return user.getRoles().stream() .map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName())) .collect(Collectors.toList()); } }
8.2 自定义认证提供者
java
@Component public class CustomAuthenticationProvider implements AuthenticationProvider { @Autowired private UserDetailsService userDetailsService; @Autowired private PasswordEncoder passwordEncoder; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String username = authentication.getName(); String password = authentication.getCredentials().toString(); UserDetails user = userDetailsService.loadUserByUsername(username); if (passwordEncoder.matches(password, user.getPassword())) { return new UsernamePasswordAuthenticationToken( username, password, user.getAuthorities()); } else { throw new BadCredentialsException("密码错误"); } } @Override public boolean supports(Class<?> authentication) { return authentication.equals(UsernamePasswordAuthenticationToken.class); } }
9. 安全配置最佳实践
9.1 密码编码器配置
java
@Configuration public class PasswordEncoderConfig { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
9.2 CORS 配置
java
@Configuration public class CorsConfig { @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000")); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type", "X-Requested-With")); configuration.setExposedHeaders(Arrays.asList("Authorization")); configuration.setAllowCredentials(true); configuration.setMaxAge(3600L); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; } }
9.3 CSRF 防护配置
java
@Configuration public class CsrfConfig { @Bean public CsrfTokenRepository csrfTokenRepository() { CookieCsrfTokenRepository repository = CookieCsrfTokenRepository.withHttpOnlyFalse(); repository.setCookiePath("/"); return repository; } }
10. 高级特性
10.1 令牌增强器
java
@Component public class CustomTokenEnhancer implements TokenEnhancer { @Autowired private UserService userService; @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { Map<String, Object> additionalInfo = new HashMap<>(); // 添加用户信息 String username = authentication.getName(); User user = userService.findByUsername(username); additionalInfo.put("user_id", user.getId()); additionalInfo.put("email", user.getEmail()); additionalInfo.put("full_name", user.getFullName()); additionalInfo.put("department", user.getDepartment()); // 添加自定义声明 additionalInfo.put("issuer", "my-company"); additionalInfo.put("issued_at", System.currentTimeMillis()); ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo); return accessToken; } }
10.2 自定义授权批准处理
java
@Controller @SessionAttributes("authorizationRequest") public class CustomAuthorizationEndpoint { @GetMapping("/custom/confirm_access") public String getAccessConfirmation(Model model, HttpServletRequest request) throws Exception { AuthorizationRequest authorizationRequest = (AuthorizationRequest) model.asMap().get("authorizationRequest"); model.addAttribute("clientId", authorizationRequest.getClientId()); model.addAttribute("scopes", authorizationRequest.getScope()); return "access_confirmation"; } @PostMapping("/custom/confirm_access") public String handleAccessConfirmation(@RequestParam Map<String, String> parameters, HttpServletRequest request) { // 处理用户授权确认 return "redirect:/oauth/authorize"; } }
10.3 令牌撤销端点
java
@RestController public class TokenRevocationEndpoint { @Autowired private TokenStore tokenStore; @PostMapping("/oauth/revoke") public ResponseEntity<?> revokeToken(@RequestParam("token") String token) { OAuth2AccessToken accessToken = tokenStore.readAccessToken(token); if (accessToken != null) { tokenStore.removeAccessToken(accessToken); // 如果有关联的刷新令牌,也一并撤销 OAuth2RefreshToken refreshToken = accessToken.getRefreshToken(); if (refreshToken != null) { tokenStore.removeRefreshToken(refreshToken); } } return ResponseEntity.ok().build(); } }
11. 测试与调试
11.1 测试配置
java
@SpringBootTest @AutoConfigureTestDatabase class OAuth2IntegrationTest { @Autowired private TestRestTemplate restTemplate; @Test void testPasswordGrant() { HttpHeaders headers = new HttpHeaders(); headers.setBasicAuth("client1", "secret1"); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); MultiValueMap<String, String> body = new LinkedMultiValueMap<>(); body.add("grant_type", "password"); body.add("username", "user"); body.add("password", "password"); body.add("scope", "read write"); HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(body, headers); ResponseEntity<String> response = restTemplate.postForEntity( "/oauth/token", request, String.class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getBody()).contains("access_token"); } @Test void testAccessProtectedResource() { // 首先获取访问令牌 String accessToken = obtainAccessToken(); HttpHeaders headers = new HttpHeaders(); headers.setBearerAuth(accessToken); HttpEntity<String> entity = new HttpEntity<>(headers); ResponseEntity<String> response = restTemplate.exchange( "/api/protected", HttpMethod.GET, entity, String.class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); } private String obtainAccessToken() { // 实现获取访问令牌的逻辑 return "mock-access-token"; } }
11.2 调试工具类
java
@Component public class OAuth2DebugUtils { public static void printTokenDetails(OAuth2AccessToken token) { System.out.println("=== Token Details ==="); System.out.println("Token Value: " + token.getValue()); System.out.println("Token Type: " + token.getTokenType()); System.out.println("Expires In: " + token.getExpiresIn()); System.out.println("Scope: " + token.getScope()); if (token.getAdditionalInformation() != null) { System.out.println("Additional Info: " + token.getAdditionalInformation()); } if (token instanceof DefaultOAuth2AccessToken) { DefaultOAuth2AccessToken defaultToken = (DefaultOAuth2AccessToken) token; System.out.println("Expiration: " + defaultToken.getExpiration()); } } public static void printAuthenticationDetails(OAuth2Authentication authentication) { System.out.println("=== Authentication Details ==="); System.out.println("Name: " + authentication.getName()); System.out.println("Authorities: " + authentication.getAuthorities()); System.out.println("Client ID: " + authentication.getOAuth2Request().getClientId()); System.out.println("Scope: " + authentication.getOAuth2Request().getScope()); System.out.println("Approved: " + authentication.getOAuth2Request().isApproved()); } }
12. 生产环境配置
12.1 安全配置
yaml
# application-prod.yml security: oauth2: client: client-id: ${CLIENT_ID} client-secret: ${CLIENT_SECRET} authorization: check-token-access: isAuthenticated() resource: token-info-uri: http://auth-server/oauth/check_token server: ssl: key-store: classpath:keystore.p12 key-store-password: ${KEYSTORE_PASSWORD} key-store-type: PKCS12 key-alias: tomcat port: 8443 logging: level: org.springframework.security: DEBUG org.springframework.security.oauth2: DEBUG
12.2 性能优化配置
java
@Configuration public class CacheConfig { @Bean public CacheManager cacheManager() { CaffeineCacheManager cacheManager = new CaffeineCacheManager(); cacheManager.setCaffeine(Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.MINUTES) .maximumSize(1000)); return cacheManager; } @Bean public TokenStore tokenStore() { return new JdbcTokenStore(dataSource); } @Bean @Primary public DefaultTokenServices tokenServices() { DefaultTokenServices defaultTokenServices = new DefaultTokenServices(); defaultTokenServices.setTokenStore(tokenStore()); defaultTokenServices.setSupportRefreshToken(true); defaultTokenServices.setReuseRefreshToken(false); defaultTokenServices.setAccessTokenValiditySeconds(3600); // 1小时 defaultTokenServices.setRefreshTokenValiditySeconds(2592000); // 30天 return defaultTokenServices; } }
总结
Spring Security OAuth2 提供了一个强大而灵活的框架来实现安全的 API 授权。通过本文的详细讲解,您应该能够:
- 理解 OAuth2 的核心概念和授权流程
- 配置授权服务器和资源服务器
- 实现各种授权模式
- 自定义用户认证和授权逻辑
- 配置数据库存储和 JWT 令牌
- 实现生产级别的安全配置