Spring Boot 3.X
JDK17+
Spring Boot 3.x(需要JDK17+)
认证服务
一、 pom引入
pom
<!-- 安全基础组件 ->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 认证服务 ->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
</dependency>
1.1 主要自动配置代码
Spring Boot 框架通过AutoConfiguration.class 来实现自动加载
注:因包含spring-boot-starter-security框架,默认会生成WebSecurity的安全过滤链
通过Import加载OAuth2AuthorizationServerConfiguration和OAuth2AuthorizationServerWebSecurityConfiguration
java
@AutoConfiguration(before = { OAuth2ResourceServerAutoConfiguration.class, SecurityAutoConfiguration.class,
UserDetailsServiceAutoConfiguration.class })
@ConditionalOnClass(OAuth2Authorization.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@Import({ OAuth2AuthorizationServerConfiguration.class, OAuth2AuthorizationServerWebSecurityConfiguration.class })
public class OAuth2AuthorizationServerAutoConfiguration {
}
OAuth2AuthorizationServerConfiguration
- OAuth2AuthorizationServerProperties 加载默认配置或是自定义的配置信息
- RegisteredClientRepository 客户端信息的配置
- AuthorizationServerSettings 授权服务器设置
java
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(OAuth2AuthorizationServerProperties.class)
class OAuth2AuthorizationServerConfiguration {
private final OAuth2AuthorizationServerPropertiesMapper propertiesMapper;
//通过OAuth2AuthorizationServerProperties 读取默认配置或是在配置文件中配置的参数
OAuth2AuthorizationServerConfiguration(OAuth2AuthorizationServerProperties properties) {
this.propertiesMapper = new OAuth2AuthorizationServerPropertiesMapper(properties);
}
//初始化Bean,当未定义RegisteredClientRepository 的Bean时,才会执行该方法
@Bean
@ConditionalOnMissingBean
@Conditional(RegisteredClientsConfiguredCondition.class)
RegisteredClientRepository registeredClientRepository() {
return new InMemoryRegisteredClientRepository(this.propertiesMapper.asRegisteredClients());
}
//初始化Bean,当未定义AuthorizationServerSettings 的Bean时,才会执行该方法
@Bean
@ConditionalOnMissingBean
AuthorizationServerSettings authorizationServerSettings() {
return this.propertiesMapper.asAuthorizationServerSettings();
}
}
AuthorizationServerSettings asAuthorizationServerSettings() {
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
OAuth2AuthorizationServerProperties.Endpoint endpoint = this.properties.getEndpoint();
OAuth2AuthorizationServerProperties.OidcEndpoint oidc = endpoint.getOidc();
AuthorizationServerSettings.Builder builder = AuthorizationServerSettings.builder();
map.from(this.properties::getIssuer).to(builder::issuer);
map.from(this.properties::isMultipleIssuersAllowed).to(builder::multipleIssuersAllowed);
map.from(endpoint::getAuthorizationUri).to(builder::authorizationEndpoint);
map.from(endpoint::getDeviceAuthorizationUri).to(builder::deviceAuthorizationEndpoint);
map.from(endpoint::getDeviceVerificationUri).to(builder::deviceVerificationEndpoint);
map.from(endpoint::getTokenUri).to(builder::tokenEndpoint);
map.from(endpoint::getJwkSetUri).to(builder::jwkSetEndpoint);
map.from(endpoint::getTokenRevocationUri).to(builder::tokenRevocationEndpoint);
map.from(endpoint::getTokenIntrospectionUri).to(builder::tokenIntrospectionEndpoint);
map.from(oidc::getLogoutUri).to(builder::oidcLogoutEndpoint);
map.from(oidc::getClientRegistrationUri).to(builder::oidcClientRegistrationEndpoint);
map.from(oidc::getUserInfoUri).to(builder::oidcUserInfoEndpoint);
return builder.build();
}
OAuth2AuthorizationServerWebSecurityConfiguration
一、生成authorizationServerSecurityFilterChain的过了链
java
@Configuration(proxyBeanMethods = false)
@ConditionalOnDefaultWebSecurity
@ConditionalOnBean({ RegisteredClientRepository.class, AuthorizationServerSettings.class })
class OAuth2AuthorizationServerWebSecurityConfiguration {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
//生成默认的Oauth2认证的安全配置
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
//将OAuth2AuthorizationServerConfigurer在HttpSecurity 生效
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).oidc(withDefaults());
//资源服务的配置
http.oauth2ResourceServer((resourceServer) -> resourceServer.jwt(withDefaults()));
//认证异常的处理,这里默认是登录页面
http.exceptionHandling((exceptions) -> exceptions.defaultAuthenticationEntryPointFor(
new LoginUrlAuthenticationEntryPoint("/login"), createRequestMatcher()));
return http.build();
}
@Bean
@Order(SecurityProperties.BASIC_AUTH_ORDER)
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
//访问所有的请求都需要用户已登录,并设置默认的登录操作
http.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()).formLogin(withDefaults());
return http.build();
}
private static RequestMatcher createRequestMatcher() {
MediaTypeRequestMatcher requestMatcher = new MediaTypeRequestMatcher(MediaType.TEXT_HTML);
requestMatcher.setIgnoredMediaTypes(Set.of(MediaType.ALL));
return requestMatcher;
}
}
OAuth2AuthorizationServerConfigurer
其中对应配置的地址可以通过AuthorizationServerSettings 来进行修改
createConfigurers
获取OAuth2所需涉及的所有Filter配置,这个方法是当前类通过常亮两创建的
java
private final Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> configurers = this.createConfigurers();
创建了8个AbstractOAuth2Configurer类
java
private Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> createConfigurers() {
Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> configurers = new LinkedHashMap();
//OAuth2客户端授权配置
//OAuth2ClientAuthenticationFilter以及地址tokenEndpointUri、tokenIntrospectionEndpointUri、tokenRevocationEndpointUri、deviceAuthorizationEndpointUri
configurers.put(OAuth2ClientAuthenticationConfigurer.class, new OAuth2ClientAuthenticationConfigurer((x$0) -> this.postProcess(x$0)));
//OAuth2授权服务器元数据站点配置
//OAuth2AuthorizationServerMetadataEndpointFilter以及地址/.well-known/oauth-authorization-server
configurers.put(OAuth2AuthorizationServerMetadataEndpointConfigurer.class, new OAuth2AuthorizationServerMetadataEndpointConfigurer((x$0) -> this.postProcess(x$0)));
//OAuth2授权服务站点配置
//OAuth2AuthorizationEndpointFilter以及地址/oauth2/authorize
configurers.put(OAuth2AuthorizationEndpointConfigurer.class, new OAuth2AuthorizationEndpointConfigurer((x$0) -> this.postProcess(x$0)));
//OAuth2 Token站点配置
//OAuth2TokenEndpointFilter以及地址/oauth2/token
configurers.put(OAuth2TokenEndpointConfigurer.class, new OAuth2TokenEndpointConfigurer((x$0) -> this.postProcess(x$0)));
//OAuth2 Token内部站点配置
//OAuth2TokenIntrospectionEndpointFilter以及地址/oauth2/introspect
configurers.put(OAuth2TokenIntrospectionEndpointConfigurer.class, new OAuth2TokenIntrospectionEndpointConfigurer((x$0) -> this.postProcess(x$0)));
//OAuth2 Token撤销站点配置
//OAuth2TokenRevocationEndpointFilter以及地址/oauth2/revoke
configurers.put(OAuth2TokenRevocationEndpointConfigurer.class, new OAuth2TokenRevocationEndpointConfigurer((x$0) -> this.postProcess(x$0)));
//OAuth2设备授权端点配置器
//OAuth2DeviceAuthorizationEndpointFilter以及地址/oauth2/device_authorization
configurers.put(OAuth2DeviceAuthorizationEndpointConfigurer.class, new OAuth2DeviceAuthorizationEndpointConfigurer((x$0) -> this.postProcess(x$0)));
//OAuth2设备验证端点配置器
//OAuth2DeviceVerificationEndpointFilter以及地址/oauth2/device_verification
configurers.put(OAuth2DeviceVerificationEndpointConfigurer.class, new OAuth2DeviceVerificationEndpointConfigurer((x$0) -> this.postProcess(x$0)));
return configurers;
}
通过父类的build调用doBuild在调用configure方法
java
public void configure(HttpSecurity httpSecurity) {
//循环createConfigurers生成的所有AbstractOAuth2Configurer类,将生成的Filter添加到HttpSecurity中的filters中。
this.configurers.values().forEach((configurer) -> configurer.configure(httpSecurity));
//从Bean中获取AuthorizationServerSettings
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
//创建AuthorizationServerContextFilter
AuthorizationServerContextFilter authorizationServerContextFilter = new AuthorizationServerContextFilter(authorizationServerSettings);
//将该Filter添加到SecurityContextHolderFilter类之后
httpSecurity.addFilterAfter((Filter)this.postProcess(authorizationServerContextFilter), SecurityContextHolderFilter.class);
JWKSource<SecurityContext> jwkSource = OAuth2ConfigurerUtils.getJwkSource(httpSecurity);
if (jwkSource != null) {
String jwkSetEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ? OAuth2ConfigurerUtils.withMultipleIssuersPattern(authorizationServerSettings.getJwkSetEndpoint()) : authorizationServerSettings.getJwkSetEndpoint();
NimbusJwkSetEndpointFilter jwkSetEndpointFilter = new NimbusJwkSetEndpointFilter(jwkSource, jwkSetEndpointUri);
httpSecurity.addFilterBefore((Filter)this.postProcess(jwkSetEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
}
}
OAuth2AuthorizationEndpointFilter
过滤链,执行doFilter中的方法,执行到doFilterInternal来完成对应的操作
java
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//请求地址和方法是否匹配/oauth2/authorize
if (!this.authorizationEndpointMatcher.matches(request)) {
filterChain.doFilter(request, response);
} else {
try {
//通过代理DelegatingAuthenticationConverter类,循环执行下来3个类中的convert方法
//1. OAuth2AuthorizationCodeAuthenticationConverter:
//1.1 其中grant_type支持authorization_code,
//1.2 通过SecurityContextHolder上下文获取Authentication,
//1.3 获取请求参数中的code,redirect_uri并判断参数不为空
//1.4 返回OAuth2AuthorizationCodeAuthenticationToken
//2. OAuth2AuthorizationCodeRequestAuthenticationConverter:
//2.1 请求方法仅支持GET,且请求参数包含response_type,scope,且对应值不为null,scope的值包含openid。
//2.2 返回OAuth2AuthorizationCodeRequestAuthenticationToken
//3. OAuth2AuthorizationConsentAuthenticationConverter
//3.1 请求方式是POST,且response_type的值不为null,且client_id、state
//3.2 当socpe可为空,返回OAuth2AuthorizationConsentAuthenticationToken
Authentication authentication = this.authenticationConverter.convert(request);
if (authentication instanceof AbstractAuthenticationToken) {
((AbstractAuthenticationToken)authentication).setDetails(this.authenticationDetailsSource.buildDetails(request));
}
//通过ProviderManager执行authenticate里面的方法, 循环List<AuthenticationProvider> providers
//判断对应的类是否执行Authentication,这里选取上面可能返回3中类型中的Authentication
//1. OAuth2AuthorizationCodeAuthenticationProvider
//1.1 仅支持OAuth2AuthorizationCodeAuthenticationToken
//1.2 通过OAuth2AuthorizationService根据findByToken方法,根据code返回对应的OAuth2Authorization
//1.3 获取请求参数中的code,redirect_uri等信息和存储的对应字段信息是否一致,不一致则报对应异常
//1.4 返回OAuth2AccessTokenAuthenticationToken
//2. OAuth2AuthorizationCodeRequestAuthenticationProvider
//2.1 仅支持OAuth2AuthorizationCodeRequestAuthenticationToken
//2.2 通过RegisteredClientRepository的findByClientId获取配置的客户端信息,并校验客户端信息是否正确
//2.3 判断用户是否登录,如未登录则跳转到登录
//2.4 拼接对应信息,返回OAuth2AuthorizationCodeRequestAuthenticationToken
//3. OAuth2AuthorizationConsentAuthenticationProvider
//3.1 仅支持OAuth2AuthorizationConsentAuthenticationToken类的Authentication
//3.2 OAuth2AuthorizationService的findByToken方法,根据state获取OAuth2Authorization
//3.3 通过RegisteredClientRepository获取到客户端信息和存储中获取到的信息进行对应的判断等。
//3.4 返回OAuth2AuthorizationCodeRequestAuthenticationToken
Authentication authenticationResult = this.authenticationManager.authenticate(authentication);
//如果未认证,则继续进行执行下个Filter
if (!authenticationResult.isAuthenticated()) {
filterChain.doFilter(request, response);
return;
}
if (authenticationResult instanceof OAuth2AuthorizationConsentAuthenticationToken) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Authorization consent is required");
}
this.sendAuthorizationConsent(request, response, (OAuth2AuthorizationCodeRequestAuthenticationToken)authentication, (OAuth2AuthorizationConsentAuthenticationToken)authenticationResult);
return;
}
this.sessionAuthenticationStrategy.onAuthentication(authenticationResult, request, response);
//成功之后的跳转:在这里完成对code的拼接。
//在初始化OAuth2AuthorizationEndpointFilter中完成this.authenticationSuccessHandler = this::sendAuthorizationResponse;
this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, authenticationResult);
} catch (OAuth2AuthenticationException var6) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Authorization request failed: %s", var6.getError()), var6);
}
this.authenticationFailureHandler.onAuthenticationFailure(request, response, var6);
}
}
}
OAuth2TokenEndpointFilter
java
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//1. 在初始化 this.tokenEndpointMatcher = new AntPathRequestMatcher(tokenEndpointUri, HttpMethod.POST.name());
//2. 判断请求是否和当前匹配
if (!this.tokenEndpointMatcher.matches(request)) {
filterChain.doFilter(request, response);
} else {
try {
//请求参数必须包含grant_type
String[] grantTypes = request.getParameterValues("grant_type");
if (grantTypes == null || grantTypes.length != 1) {
throwError("invalid_request", "grant_type");
}
//
//JwtClientAssertionAuthenticationConverter ;
//ClientSecretBasicAuthenticationConverter ;
//ClientSecretPostAuthenticationConverter 处理参数带有client_id以及client_secret;
//PublicClientAuthenticationConverter ;
//X509ClientCertificateAuthenticationConverter ;
Authentication authorizationGrantAuthentication = this.authenticationConverter.convert(request);
if (authorizationGrantAuthentication == null) {
throwError("unsupported_grant_type", "grant_type");
}
if (authorizationGrantAuthentication instanceof AbstractAuthenticationToken) {
((AbstractAuthenticationToken)authorizationGrantAuthentication).setDetails(this.authenticationDetailsSource.buildDetails(request));
}
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken)this.authenticationManager.authenticate(authorizationGrantAuthentication);
this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, accessTokenAuthentication);
} catch (OAuth2AuthenticationException var7) {
SecurityContextHolder.clearContext();
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Token request failed: %s", var7.getError()), var7);
}
this.authenticationFailureHandler.onAuthenticationFailure(request, response, var7);
}
}
}
资源服务
总结
过滤链的选择
FilterChainProxy
java
private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest) request);
HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse) response);
//获取当前请求所匹配的Filter
List<Filter> filters = getFilters(firewallRequest);
if (filters == null || filters.size() == 0) {
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.of(() -> "No security for " + requestLine(firewallRequest)));
}
firewallRequest.reset();
this.filterChainDecorator.decorate(chain).doFilter(firewallRequest, firewallResponse);
return;
}
if (logger.isDebugEnabled()) {
logger.debug(LogMessage.of(() -> "Securing " + requestLine(firewallRequest)));
}
FilterChain reset = (req, res) -> {
if (logger.isDebugEnabled()) {
logger.debug(LogMessage.of(() -> "Secured " + requestLine(firewallRequest)));
}
// Deactivate path stripping as we exit the security filter chain
firewallRequest.reset();
chain.doFilter(req, res);
};
this.filterChainDecorator.decorate(reset, filters).doFilter(firewallRequest, firewallResponse);
}
private List<Filter> getFilters(HttpServletRequest request) {
int count = 0;
//循环所有过滤链
for (SecurityFilterChain chain : this.filterChains) {
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Trying to match request against %s (%d/%d)", chain, ++count,
this.filterChains.size()));
}
//通过请求中的方法和地址来选取对应的过滤链
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}
通过上面的 chain.matches(request)来选取对应的过滤链,通过匹配请求地址和请求方式来选择对应的过滤链。
java
public boolean matches(HttpServletRequest request) {
return this.requestMatcher.matches(request);
}
