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)

流程:

  1. 客户端将用户重定向到授权服务器
  2. 用户授权
  3. 授权服务器返回授权码
  4. 客户端使用授权码换取访问令牌

配置:

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 授权。通过本文的详细讲解,您应该能够:

  1. 理解 OAuth2 的核心概念和授权流程
  2. 配置授权服务器和资源服务器
  3. 实现各种授权模式
  4. 自定义用户认证和授权逻辑
  5. 配置数据库存储和 JWT 令牌
  6. 实现生产级别的安全配置

Read more

Git下载安装(保姆教程)

Git下载安装(保姆教程)

目录 1、Git下载  2、Git安装(windows版) (1)启动安装程序 (2)阅读许可协议 (3)选择安装路径 (4)选择组件 (5)选择开始菜单文件夹 (6)选择默认编辑器 (7)选择仓库的初始分支 (8)PATH环境配置 (9)选择SSH可执行文件 (10)选择HTTPS传输库 (11)配置行尾转换 (12)配置终端模拟器 (13)创建git pull的默认行为 (14)配置Git凭证  (15)配置额外选项   (16)等待安装完成 1、Git下载 (1)访问Git官方下载网站:https://git-scm.com/downloads          网盘直接下载:https://pan.

By Ne0inhk
GitHub 7大爆款Skills开源项目:Anthropic官方Skill Creator元技能+Superpowers 27k星任务拆解+Code Review自动代码审查

GitHub 7大爆款Skills开源项目:Anthropic官方Skill Creator元技能+Superpowers 27k星任务拆解+Code Review自动代码审查

Claude Agent Skills工具箱|GitHub 7大开源项目:Anthropic Skill Creator+Superpowers+Code Review+Context Engineering,AI元技能开发与上下文优化指南 技术背景:为什么Claude Skills是2025年AI Agent开发的必选项 随着Anthropic Claude在大模型领域的持续领跑,其Skills(技能)生态已成为AI Agent工程化落地的关键基础设施。与传统Prompt工程不同,Skills通过结构化的SKILL.md文件,将AI能力封装为可复用、可共享、可迭代的模块化组件,实现从"对话式交互"到"任务式执行"的范式转变。 当前,GitHub开源社区已涌现大量高质量的Claude Skills项目,涵盖元技能开发、代码审查自动化、上下文工程优化等核心场景。本文系统梳理7大高星开源项目,附完整技术解析与安全实践指南。 文章目录 * Claude Agent

By Ne0inhk
【2026 最新】玩转 Obsidian 简约美化 + 插件推荐 + Git 多端同步全流程教程

【2026 最新】玩转 Obsidian 简约美化 + 插件推荐 + Git 多端同步全流程教程

前言 这篇文章分享我个人在 Windows 上把 Obsidian 打造成“简约但好用”的一套方案:主题美化、常用配置、插件推荐,以及用 Git 实现多端同步。 一、下载安装 Obsidian 下载安装可以查看我的这篇文章: 【2025 最新】最好用必备笔记软件 Obsidian 的下载安装与使用教程-ZEEKLOG博客https://blog.ZEEKLOG.net/2301_80035882/article/details/145573354?sharetype=blogdetail&sharerId=145573354&sharerefer=PC&sharesource=2301_80035882&spm=1011.2480.3001.8118 二、

By Ne0inhk