SpringSecurity认证服务器:OAuth2授权服务器实现
文章目录
- 引言
- 一、Spring Authorization Server基础
- 二、基础配置与授权服务器设置
- 三、用户认证与客户端管理
- 四、令牌定制与自定义声明
- 五、授权确认页面定制
- 总结
引言
在微服务架构中,统一的身份认证和授权机制至关重要。OAuth2作为行业标准的授权框架,被广泛应用于各类应用系统。Spring Authorization Server项目提供了对OAuth2授权服务器的原生支持,本文将探讨如何使用Spring Security实现一个功能完备的OAuth2授权服务器,包括客户端注册、授权流程配置以及令牌管理,帮助开发者构建安全可靠的认证授权中心。
一、Spring Authorization Server基础
Spring Authorization Server是Spring Security团队开发的框架,用于构建符合OAuth2.1和OpenID Connect 1.0规范的授权服务器。要使用它,首先需要添加相关依赖:
// 在pom.xml中添加以下依赖 org.springframework.boot spring-boot-starter-security org.springframework.security spring-security-oauth2-authorization-server 1.1.0 org.springframework.boot spring-boot-starter-web
Spring Authorization Server支持OAuth2.1规范中的多种授权类型,包括授权码模式、客户端凭证模式、刷新令牌模式等,还支持OpenID Connect扩展,可以签发ID令牌用于身份认证。
二、基础配置与授权服务器设置
配置OAuth2授权服务器需要设置安全过滤链和基本参数。以下是核心配置示例:
@Configuration @EnableWebSecurity public class AuthorizationServerConfig { @Bean @Order(1) public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); http.getConfigurer(OAuth2AuthorizationServerConfigurer.class) .oidc(Customizer.withDefaults()); // 启用OpenID Connect http .exceptionHandling(exceptions -> exceptions .authenticationEntryPoint( new LoginUrlAuthenticationEntryPoint("/login")) ) .oauth2ResourceServer(oauth2 -> oauth2 .jwt(Customizer.withDefaults())); return http.build(); } @Bean @Order(2) public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorize -> authorize .requestMatchers("/assets/**", "/login").permitAll() .anyRequest().authenticated() ) .formLogin(Customizer.withDefaults()); return http.build(); } @Bean public RegisteredClientRepository registeredClientRepository(PasswordEncoder passwordEncoder) { RegisteredClient client = RegisteredClient.withId(UUID.randomUUID().toString()) .clientId("client") .clientSecret(passwordEncoder.encode("secret")) .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .redirectUri("http://127.0.0.1:8080/login/oauth2/code/client") .scope(OidcScopes.OPENID) .scope("read") .scope("write") .clientSettings(ClientSettings.builder() .requireAuthorizationConsent(true) .build()) .tokenSettings(TokenSettings.builder() .accessTokenTimeToLive(Duration.ofMinutes(30)) .refreshTokenTimeToLive(Duration.ofDays(1)) .build()) .build(); return new InMemoryRegisteredClientRepository(client); } @Bean public JWKSource jwkSource() { KeyPair keyPair = generateRsaKey(); RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); RSAKey rsaKey = new RSAKey.Builder(publicKey) .privateKey(privateKey) .keyID(UUID.randomUUID().toString()) .build(); JWKSet jwkSet = new JWKSet(rsaKey); return new ImmutableJWKSet(jwkSet); } @Bean public AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder() .issuer("http://auth-server:9000") .build(); } // 生成RSA密钥对 private static KeyPair generateRsaKey() { try { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(2048); return keyPairGenerator.generateKeyPair(); } catch (Exception ex) { throw new IllegalStateException(ex); } } }
这个配置类定义了两个安全过滤链(一个用于授权服务器端点,另一个用于常规Web安全),注册了客户端信息,配置了JWT签名密钥,并设置了授权服务器的基本参数。
三、用户认证与客户端管理
授权服务器需要管理用户和客户端信息。以下是用户认证服务的实现:
@Bean public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) { UserDetails user = User.builder() .username("user") .password(passwordEncoder.encode("password")) .roles("USER") .build(); UserDetails admin = User.builder() .username("admin") .password(passwordEncoder.encode("admin")) .roles("USER", "ADMIN") .build(); return new InMemoryUserDetailsManager(user, admin); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
在实际应用中,通常需要将客户端信息存储在数据库中。可以实现JdbcRegisteredClientRepository或自定义RegisteredClientRepository:
@Component public class JpaRegisteredClientRepository implements RegisteredClientRepository { private final ClientRepository clientRepository; // 实现findById、findByClientId和save方法 // 将RegisteredClient对象与数据库实体进行转换 // ... }
四、令牌定制与自定义声明
OAuth2授权服务器默认使用JWT作为访问令牌格式。可以自定义令牌内容,添加额外的用户信息或业务相关的声明:
@Component public class CustomJwtTokenCustomizer implements OAuth2TokenCustomizer { @Override public void customize(JwtEncodingContext context) { if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) { Authentication principal = context.getPrincipal(); JwtClaimsSet.Builder claims = context.getClaims(); claims.claim("user_id", principal.getName()); // 添加用户角色 Set authorities = principal.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.toSet()); claims.claim("roles", authorities); // 添加其他自定义声明 claims.claim("custom_claim", "custom_value"); } } }
要启用这个自定义器,需要在配置类中注册它:
@Bean public OAuth2TokenCustomizer jwtTokenCustomizer() { return new CustomJwtTokenCustomizer(); }
五、授权确认页面定制
在授权码流程中,用户需要确认授予客户端的权限范围。可以自定义授权确认页面,提供更好的用户体验:
@Controller public class AuthorizationConsentController { private final RegisteredClientRepository clientRepository; @GetMapping("/oauth2/consent") public String consentPage( @RequestParam("client_id") String clientId, @RequestParam("scope") String scope, Model model) { RegisteredClient client = clientRepository.findByClientId(clientId); model.addAttribute("clientName", client.getClientName()); model.addAttribute("scopes", Arrays.asList(scope.split(" "))); return "consent"; } }
在配置中启用自定义确认页面:
authorizationServerConfigurer.authorizationEndpoint( authorizationEndpoint -> authorizationEndpoint.consentPage("/oauth2/consent") );
总结
Spring Authorization Server为构建OAuth2授权服务器提供了强大而灵活的支持。通过适当的配置,可以快速实现一个功能完备的认证授权中心,支持多种授权类型和客户端认证方法。本文介绍了Spring Authorization Server的基础配置、客户端注册、用户认证、令牌定制以及授权确认页面的定制方法。在实际应用中,还需要考虑令牌撤销、刷新策略、安全加固等问题,以构建一个安全可靠的OAuth2认证授权体系。合理利用Spring Security的模块化设计和丰富功能,结合业务需求进行定制,可以构建出既满足安全要求又具有良好用户体验的认证授权服务。