如何为OAuth2授权服务器配置预认证以跳过登录流程
问题
我已经基于默认配置实现了Authorization Server,其他服务配置为Resource Server。正常流程下调用/authorize会跳转至默认登录页,登录后获取access_token即可访问Resource Server。
现在的需求是:我们的应用通过第三方完成用户认证,请求到达Authorization Server时用户已处于认证状态,希望跳过授权服务器的认证步骤,不需要在授权服务器中维护用户凭证。类似常规Spring Boot Security应用中使用AbstractPreAuthenticatedProcessingFilter->RequestAttributeAuthenticationFilter,通过指定请求头完成认证的方式。
我尝试在第二个SecurityFilterChain中添加RequestAttributeAuthenticationFilter,但/authorize请求始终被拒绝,看起来所有OAuth端点都受自身的filter->manager->provider流程(包括Session)保护。
当前的两个SecurityFilterChain配置如下:
@Bean @Order(1) public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); http.getConfigurer(OAuth2AuthorizationServerConfigurer.class) .oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0 http // Redirect to the login page when not authenticated from the // authorization endpoint .exceptionHandling((exceptions) -> exceptions .defaultAuthenticationEntryPointFor( new LoginUrlAuthenticationEntryPoint("/login"), new MediaTypeRequestMatcher(MediaType.TEXT_HTML) ) ) // Accept access tokens for User Info and/or Client Registration .oauth2ResourceServer((resourceServer) -> resourceServer .jwt(Customizer.withDefaults())); return http.build(); } @Bean @Order(2) public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests((authorize) -> authorize .anyRequest().authenticated() ) // Form login handles the redirect to the login page from the // authorization server filter chain .formLogin(Customizer.withDefaults()); return http.build(); }
配置建议
1. 能否在OAuth2过滤链中使用RequestAttributeAuthenticationFilter?
可以。因为/authorize等OAuth2授权端点属于@Order(1)的authorizationServerSecurityFilterChain处理,所以必须在这个过滤链中配置预认证相关组件,而非@Order(2)的默认过滤链。
2. 具体配置步骤
步骤1:在授权服务器过滤链中添加预认证Filter
修改authorizationServerSecurityFilterChain,将RequestAttributeAuthenticationFilter添加到OAuth2授权端点Filter之前,并配置对应的认证管理器:
@Bean @Order(1) public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); http.getConfigurer(OAuth2AuthorizationServerConfigurer.class) .oidc(Customizer.withDefaults()); // 初始化预认证Filter RequestAttributeAuthenticationFilter preAuthFilter = new RequestAttributeAuthenticationFilter(); // 指定存放已认证用户ID的请求属性名(需和第三方传入的属性名一致) preAuthFilter.setPrincipalRequestAttributeName("authenticatedUserId"); preAuthFilter.setAuthenticationManager(preAuthenticatedAuthenticationManager(http.getSharedObject(AuthenticationManagerBuilder.class))); // 将Filter插入到OAuth2授权端点Filter之前 http.addFilterBefore(preAuthFilter, OAuth2AuthorizationEndpointFilter.class); http .exceptionHandling((exceptions) -> exceptions .defaultAuthenticationEntryPointFor( new LoginUrlAuthenticationEntryPoint("/login"), new MediaTypeRequestMatcher(MediaType.TEXT_HTML) ) ) .oauth2ResourceServer((resourceServer) -> resourceServer .jwt(Customizer.withDefaults())); return http.build(); }
步骤2:配置预认证AuthenticationManager
创建支持预认证的AuthenticationManager,包含PreAuthenticatedAuthenticationProvider和配套的UserDetailsService:
private AuthenticationManager preAuthenticatedAuthenticationManager(AuthenticationManagerBuilder auth) throws Exception { PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider(); provider.setPreAuthenticatedUserDetailsService(preAuthUserDetailsService()); auth.authenticationProvider(provider); return auth.build(); } private UserDetailsService preAuthUserDetailsService() { return username -> { // 根据第三方传入的用户ID构造UserDetails,权限可按需设置 return User.withUsername(username) .password("") // 预认证场景无需密码 .authorities("ROLE_USER") .build(); }; }
步骤3:适配请求头传递的场景(可选)
如果第三方通过请求头而非请求属性传递用户信息,可直接使用RequestHeaderAuthenticationFilter(继承自AbstractPreAuthenticatedProcessingFilter),更贴合需求:
RequestHeaderAuthenticationFilter headerAuthFilter = new RequestHeaderAuthenticationFilter(); // 指定第三方传入的用户ID请求头名称 headerAuthFilter.setPrincipalRequestHeader("X-Authenticated-User"); headerAuthFilter.setAuthenticationManager(preAuthenticatedAuthenticationManager(http.getSharedObject(AuthenticationManagerBuilder.class))); http.addFilterBefore(headerAuthFilter, OAuth2AuthorizationEndpointFilter.class);
步骤4:移除登录跳转(可选)
如果所有请求都通过第三方预认证,无需跳转登录页,可修改异常处理逻辑:
http.exceptionHandling(exceptions -> exceptions .authenticationEntryPoint((request, response, authException) -> { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Pre-authentication failed"); }) );
关键注意点
- 预认证组件必须配置在**
@Order(1)的授权服务器过滤链**中,否则无法覆盖OAuth2端点的默认认证逻辑。 - 确保第三方服务在转发请求时,已将用户标识(ID/用户名)放入指定的请求属性或请求头中。
- 如果不需要用户权限,UserDetailsService可返回仅包含用户名的空权限对象。
内容的提问来源于stack exchange,提问作者user27304515

