Spring Security OAuth2登录:默认返回401而非跳转的配置问题
系统支持两种认证方式,要求大部分路径返回401状态码而非跳转。针对Keycloak使用HttpUnauthorizedEntryPoint可正常实现,但配置OAuth2登录时,该入口点阻止了特定路径(如/auth/challenge)自动跳转至/oauth2/authorization/azure的逻辑。正常流程日志如下:
org.springframework.security.web.util.matcher.NegatedRequestMatcher: matches = true
org.springframework.security.web.authentication.DelegatingAuthenticationEntryPoint: Match found! Executing org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint@112c824c
org.springframework.security.web.context.SecurityContextPersistenceFilter: SecurityContextHolder now cleared, as request processing completed org.springframework.security.web.DefaultRedirectStrategy: Redirecting to 'http://localhost:2222/oauth2/authorization/azure'
现有OAuth2配置代码
Kotlin 配置类片段
@Throws(Exception::class) override fun configure(http: HttpSecurity) { http.commonConfiguration() .exceptionHandling() .authenticationEntryPoint(HttpUnauthorizedEntryPoint()) .and() .oauth2Login() .userInfoEndpoint() .oidcUserService(userService) }
HttpUnauthorizedEntryPoint 实现
public class HttpUnauthorizedEntryPoint implements AuthenticationEntryPoint { private static final Logger LOG = LoggerFactory.getLogger(HttpUnauthorizedEntryPoint.class); public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException arg) throws IOException { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "This endpoint requires authorization."); } }
需求
如何配置才能默认返回401,同时让OAuth2的自动跳转逻辑正常生效?
已知HttpUnauthorizedEntryPoint导致Spring Security未自动填充DelegatingAuthenticationEntryPoint,曾尝试手动配置但希望由Spring自动处理流程:
val entryPoints = LinkedHashMap<RequestMatcher, AuthenticationEntryPoint>() entryPoints[loginPageMatcher] = LoginUrlAuthenticationEntryPoint("/oauth2/authorization/azure") val loginEntryPoint = DelegatingAuthenticationEntryPoint(entryPoints) loginEntryPoint.setDefaultEntryPoint(HttpUnauthorizedEntryPoint())
解决方案
不需要手动构建DelegatingAuthenticationEntryPoint,可以通过Spring Security的配置机制,让它自动维护委托入口点,同时保留自定义的默认401逻辑:
步骤1:修改配置代码
利用exceptionHandling()的defaultAuthenticationEntryPointFor方法,为需要跳转的路径绑定OAuth2入口点,其余路径使用自定义的401入口点:
@Throws(Exception::class) override fun configure(http: HttpSecurity) { val unauthorizedEntryPoint = HttpUnauthorizedEntryPoint() // 指定OAuth2跳转的入口点 val oauth2EntryPoint = LoginUrlAuthenticationEntryPoint("/oauth2/authorization/azure") http.commonConfiguration() .exceptionHandling() // 为需要触发跳转的路径绑定OAuth2入口点 .defaultAuthenticationEntryPointFor(oauth2EntryPoint, AntPathRequestMatcher("/auth/challenge")) // 其余路径默认返回401 .authenticationEntryPoint(unauthorizedEntryPoint) .and() .oauth2Login() .userInfoEndpoint() .oidcUserService(userService) }
多路径匹配扩展
如果需要为多个路径启用跳转逻辑,可以用OrRequestMatcher组合多个匹配规则:
val jumpPathsMatcher = OrRequestMatcher( AntPathRequestMatcher("/auth/challenge"), AntPathRequestMatcher("/login/oauth2/**"), AntPathRequestMatcher("/oauth2/authorization/**") ) http.exceptionHandling() .defaultAuthenticationEntryPointFor(oauth2EntryPoint, jumpPathsMatcher) .authenticationEntryPoint(unauthorizedEntryPoint)
原理说明
defaultAuthenticationEntryPointFor方法会自动帮你构建DelegatingAuthenticationEntryPoint:匹配指定路径的请求交给OAuth2入口点处理跳转,不匹配的请求则使用自定义的HttpUnauthorizedEntryPoint返回401。既保留了Spring对OAuth2流程的自动处理,又满足了大部分路径返回401的需求,无需手动维护映射关系。
内容的提问来源于stack exchange,提问作者marcin

