Spring Authorization Server + Oauth2 配置认证服务器与资源服务器

3,628 阅读4分钟

认证、授权服务器

一、文章前述

当一个团队下的项目过多的时候,可能存在每个项目都有各自的认证方式,各自的用户信息库。当团队需要对用户的某些类型的信息做敏感处理、用户信息有针对性的提供等,就需要每一个项目都去修改各自的用户库、代码逻辑,并且改动过程中各自为政,可能出现同一团队信息对外展现形式不统一,导致一些用户认知系统差异并且各自为政会导致重复劳动。在这时候,认证授权服务器就应运而生,同一管理用户信息,规范管理用户信息对外授权,起到一个统一、安全的作用。
目前线上有很多主流的认证、授权方式,比如Oauth2.0,、OIDC(基于Oauth2.0)等。接下来介绍的就是目前的主流实现Oauth2.0认证授权的框架,SpringSecurity。

二、认证服务器

Spring Authorization Server 是 Spring Security 新推出的认证服务器框架。该框架也集成了OIDC,提供了客户端的注册和查询功能。下面开始介绍认证服务器的原理与配置。

整体认证流程

认证获取用户资源时序图 (1).png

1、Spring Security

Spring Authorization Server 框架是基于Spring Security 开发,所以在学习 Spring Authorization Server 配置之前,了解一下 Spring Security 框架是有必要的。

Spring Security 架构流程

Spring Security 是基于 Filter 的职责链模式,下面图片是整体的结构:

Pasted image 20230510210600.png 该结构为多SecurityFilterChain模式,/api/** 会一直第一个链条,其余的会按照顺序一个一个向下判断 SecurityFilterChain ,直到找到一个符合的链条。 DelefatingFilterProxy:处于 Spring-Web 中的一个委托用来执行 Spring Security 链条的前置 Filter。具体执行的是:FilterChainProxy SecurityFilterChain:真正执行配置 Spring Security 安全流程的责任链,该责任链的可以动态配置以实现开闭原则。

Spring Security 认证配置

/**  
* @author 长安  
*/  
@Slf4j  
@Configuration(proxyBeanMethods = false)  
public class DefaultSecurityConfig {  
  
JwtDecoder jwtDecoder;  
  
@Autowired  
public void setJwtDecoder(JwtDecoder jwtDecoder) {  
this.jwtDecoder = jwtDecoder;  
}  
  
// @formatter:off  
@Bean  
@Order(200)  
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {  
  
http  
.cors().and()  
// 配置路径权限
.authorizeHttpRequests(authorize ->  
authorize  
.requestMatchers("/auth/**").hasRole("USER")  
.requestMatchers("/unAuth/**", "/login", "/favicon.ico").permitAll()  
.requestMatchers(  
// 另一种配置的方式
new AntPathRequestMatcher("/h2/**", HttpMethod.GET.name()),  
new AntPathRequestMatcher("/h2/**", HttpMethod.POST.name())  
).permitAll()  
.requestMatchers("/").authenticated()  
.anyRequest().authenticated()  
)  
.headers().frameOptions().sameOrigin()  
.and()  
.csrf().disable()
// 开启 formlogin ,也就是登录页面
.formLogin()  
.usernameParameter("un")  
.passwordParameter("pw")  
// .successForwardUrl("/")  
// .failureForwardUrl("/login")  
.and()  
// 这个 Filter 可以验证 BearerToken,解析认证 
.addFilterAfter(new BearerTokenAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class)  
;  
return http.build();
}  
// @formatter:on  
  
// 密码加密方式,配置该方式后,切记数据库用户密码也要使用加密的
@Bean  
PasswordEncoder passwordEncoder() {  
return new BCryptPasswordEncoder();  
}  
  
AuthenticationManager authenticationManager() {  
return new ProviderManager(Arrays.asList(new JwtAuthenticationProvider(jwtDecoder)));  
}  
  
}

Spring Authorization Server 认证配置

Spring Authorization Server 框架里面已经配置了关于Oauth2与OIDC的过滤器与认证器。源码暂时不介绍,后续有机会介绍。
版本: org.springframework.security:spring-security-oauth2-authorization-server:1.0.1
@Configuration  
public class AuthorizationServerConfiguration2 {  
  
/**  
* 配置 oauth2 相关接口的拦截执行链, 具体的接口配置在 AuthorizationServerSettings 中。  
* @param http  
* @return  
* @throws Exception  
*/  
@Bean  
@Order(1)  
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)  
throws Exception {  
// 这里配置了当前 FilterChain 只会拦截oauth2相关的请求  
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);  
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)  
.authorizationEndpoint(oAuth2AuthorizationEndpointConfigurer -> {  
oAuth2AuthorizationEndpointConfigurer.authenticationProviders(authenticationProviders -> {  
// 配置授权码生成策略
for (AuthenticationProvider authenticationProvider : authenticationProviders) {  
log.info(" consumer authenticationProvider , current -> {} ", authenticationProvider.getClass().getName());  
if(authenticationProvider instanceof OAuth2AuthorizationCodeRequestAuthenticationProvider oAuth2AuthorizationCodeRequestAuthenticationProvider) {  
  
oAuth2AuthorizationCodeRequestAuthenticationProvider.setAuthorizationCodeGenerator(authorizationCodeGenerator());  
} else if(authenticationProvider instanceof OAuth2AuthorizationConsentAuthenticationProvider oAuth2AuthorizationConsentAuthenticationProvider) {  
  
oAuth2AuthorizationConsentAuthenticationProvider.setAuthorizationCodeGenerator(authorizationCodeGenerator());  
}  
}  
});  
})  
.authorizationService(new InMemoryOAuth2AuthorizationService()) // 认证后token存储配置,默认在内存,可配置Redis与DB  
.oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0  
http  
// Redirect to the login page when not authenticated from the  
// authorization endpoint  
.exceptionHandling((exceptions) -> exceptions  
.authenticationEntryPoint(  
new LoginUrlAuthenticationEntryPoint("/login"))  
)  
// 使用 access_token 的方式
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)  
;  
return http.build();  
}  

// 这里是配置一个内存 Client 
// @Bean  
// public RegisteredClientRepository registeredClientRepository() {  
// RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())  
// .clientId("messaging-client")  
// .clientSecret(new BCryptPasswordEncoder().encode("secret"))  
// .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)  
// .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)  
// .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)  
// .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)  
// .redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")  
// .redirectUri("http://127.0.0.1:8080/authorized")  
// .scope(OidcScopes.OPENID)  
// .scope(OidcScopes.PROFILE)  
// .scope("message.read")  
// .scope("message.write")  
// .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())  
// .build();  
//  
// return new InMemoryRegisteredClientRepository(registeredClient);  
// }  

// 配置数据库查询的 Client 
@Bean  
public RegisteredClientRepository registeredClientRepository(@Qualifier("customJdbcTemplate") JdbcTemplate jdbcTemplate) {  
  
return new JdbcRegisteredClientRepository(jdbcTemplate);  
}  

// 配置JWT的生成方式
@Bean  
public JWKSource<SecurityContext> 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);  
}  
  
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;  
}  

// 配置JWT的生成方式
@Bean  
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {  
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);  
}  
  
@Bean  
public AuthorizationServerSettings authorizationServerSettings() {  
return AuthorizationServerSettings.builder().build();  
}  
  
/**  
* 配置自定义的授权码 authorization_code,默认实现:OAuth2AuthorizationCodeGenerator  
* - 下面简单配置成为了 UUID 的形式。  
*/  
public OAuth2TokenGenerator<OAuth2AuthorizationCode> authorizationCodeGenerator() {  
return context -> {  
log.info(" use custom authorization_code creator ");  
if (context.getTokenType() == null ||  
!OAuth2ParameterNames.CODE.equals(context.getTokenType().getValue())) {  
return null;  
}  
String authorizationCode = UUID.randomUUID().toString().replaceAll("-", "");  
Instant issuedAt = Instant.now();  
Instant expiresAt = issuedAt.plus(context.getRegisteredClient().getTokenSettings().getAuthorizationCodeTimeToLive());  
return new OAuth2AuthorizationCode(authorizationCode, issuedAt, expiresAt);  
};  
}  
}

Resource Server 授权配置

授权服务器配置。
@Configuration  
@EnableMethodSecurity(securedEnabled = true)  
public class ResourceConfig {  
  
  
@Bean  
@Order(-100)  
SecurityFilterChain resourceSecurityFilterChain(HttpSecurity http) throws Exception {  
// 配置授权服务器认证方式
http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);  
// 配置授权职责链端点
RequestMatcher requestMatcher = new AntPathRequestMatcher("/resource/**");  
http.securityMatcher(requestMatcher);  
  
http.authorizeHttpRequests(matcherRegistry -> {  
matcherRegistry.anyRequest().authenticated();  
});  
return http.build();  
}
}

配置如上,完成一个简单的基于授权码的认证授权服务器,后面还会找个时间写一个Spring Security源码解析的文章。 后续有问题可以在公众号中咨询,看到会回复。

备注

Pasted image 20230504113822.png

示例一个认证整体流程的Filter,如果需要自定义扩展,可以实现下面几个类

OAuth2AuthorizationEndpointFilter

OAuth2AuthorizationEndpointFilter:OAuth 2.0授权代码授予的过滤器,它处理OAuth 2.0授权请求和同意的处理。

  • 默认接口:/oauth2/authorize

AuthenticationConverter:解析请求request中的client信息,将其封装成Authentication对象,用于后期处理。

  • OAuth2AuthorizationCodeAuthenticationConverter:授权码处理converter
  • OAuth2AuthorizationConsentAuthenticationConverter:处理同意的请求

AuthenticationManager:处理身份认证请求。默认处理器是:ProviderManager

  • AuthenticationProvider: 处理特定身份认证的实现。
    • OAuth2AuthorizationCodeAuthenticationProvider:授权码认证的provider
  • OAuth2AuthorizationCodeAuthenticationProvider.OAuth2AuthorizationService 配置这个参数用来解析token