Spring Boot 3.X Oauth2 认证服务与资源服务

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

  1. OAuth2AuthorizationServerProperties 加载默认配置或是自定义的配置信息
  2. RegisteredClientRepository 客户端信息的配置
  3. 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);
}
相关推荐
代码AI弗森4 小时前
一文理清楚“算力申请 / 成本测算 / 并发评估”
java·服务器·数据库
Java开发的小李4 小时前
SpringBoot + Redis 实现分布式 Session 共享(解决多实例登录状态丢失问题)
spring boot·redis·分布式
Old Uncle Tom5 小时前
OpenClaw 记忆系统 -- 记忆预加载
java·数据结构·算法·agent
小小小米粒5 小时前
Collection单列集合、Map(Key - Value)双列集合,多继承实现。
java·开发语言·windows
前端一小卒5 小时前
我用 Claude Code 的 Superpowers 技能链写了个服务,部署前差点把服务器搞炸
前端·javascript·后端
摇滚侠5 小时前
expdp 查看帮助
java·数据库·oracle
:1216 小时前
java基础
java·开发语言
曹牧7 小时前
Spring:@RequestMapping注解,匹配的顺序与上下文无关
java·后端·spring
daixin88487 小时前
cursor无法正常使用gpt5.5等模型解决方案
java·redis·cursor
韦禾水8 小时前
记录一次项目部署到tomcat的异常
java·tomcat