使用OAuth2AuthorizationRequest手动重新授权失败问题求助
问题描述
我正在构建一个客户端应用,需要在用户认证级别不足时进行认证提升,完整流程如下:
- 用户访问客户端,完成初始认证流程
- 用户通过客户端向资源服务器执行基础操作
- 用户触发高风险操作,资源服务器通过令牌 introspection 评估 access_token 后,返回401状态码及
WWW-Authenticate头 - 客户端通过
@ExceptionHandler捕获该401,需执行重新授权操作以获取更高认证级别的令牌 - 客户端应重定向至授权服务器重新认证(授权请求中需指定新的认证级别)
- 用户完成重新认证后,携带新的会话信息(access_token、userinfo、id_token)重定向回客户端
- 用户重新尝试最初的高风险操作
目前卡在创建有效的OAuth2AuthorizationRequest环节:调用自定义CustomAuthorizationRequestResolver的resolve(request)方法返回null,无法通过OAuth2AuthorizedClientManager完成授权。当前代码如下:
@PostMapping("/reauthenticate") public String postAuthenticate(OAuth2AuthenticationToken authentication, @RequestParam("acr") String acr, HttpServletRequest request, HttpServletResponse response) { CustomAuthorizationRequestResolver authorizationRequestResolver = new CustomAuthorizationRequestResolver(repo); // 创建新的OAuth2AuthorizationRequest OAuth2AuthorizationRequest authorizationRequest = authorizationRequestResolver.resolve(request); // 添加acr_values参数 authorizationRequest = authorizationRequestResolver.modifyRequest(authorizationRequest, "acr_values", acr); OAuth2AuthorizedClient authorizedClient = authorizedClientManager.authorize( (OAuth2AuthorizeRequest) authorizationRequest ); return "index"; }
解决方案
1. 解决resolve(request)返回null的问题
自定义AuthorizationRequestResolver返回null,通常是因为请求缺少OAuth2授权所需的上下文(比如无匹配的客户端注册ID、请求路径非授权端点路径)。不要在控制器中手动实例化Resolver,改为通过Spring容器注入,确保它能获取ClientRegistrationRepository和完整请求上下文:
@Bean public OAuth2AuthorizationRequestResolver authorizationRequestResolver(ClientRegistrationRepository clientRegistrationRepository) { return new CustomAuthorizationRequestResolver(clientRegistrationRepository); }
2. 正确构建认证提升的授权请求
认证提升需要基于已有会话发起新授权请求并携带acr_values参数,推荐两种实现方式:
方式一:用OAuth2AuthorizationRequestCustomizer(更贴合Spring Security规范)
在客户端配置中添加请求定制器,注入acr_values参数:
@Bean public OAuth2AuthorizationRequestResolver authorizationRequestResolver(ClientRegistrationRepository clientRegistrationRepository) { DefaultOAuth2AuthorizationRequestResolver resolver = new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository, "/oauth2/authorization"); resolver.setAuthorizationRequestCustomizer(customizer -> { // 从session获取提前存储的acr参数 String acr = (String) customizer.getAttribute("acr"); if (acr != null) { customizer.additionalParameters(Map.of("acr_values", acr)); } }); return resolver; }
控制器中存储acr并重定向到授权端点:
@PostMapping("/reauthenticate") public String postAuthenticate(@RequestParam("acr") String acr, HttpServletRequest request) { request.getSession().setAttribute("acr", acr); // 替换为你的客户端注册ID(比如"oidc") return "redirect:/oauth2/authorization/oidc"; }
方式二:手动构建OAuth2AuthorizationRequest
如果需要完全自定义,确保包含所有必填字段:
@PostMapping("/reauthenticate") public String postAuthenticate(@RequestParam("acr") String acr, HttpServletRequest request) { // 获取客户端注册信息 ClientRegistration client = clientRegistrationRepository.findByRegistrationId("oidc"); if (client == null) { throw new IllegalArgumentException("无效的客户端注册ID"); } // 构建授权请求 OAuth2AuthorizationRequest authRequest = OAuth2AuthorizationRequest.authorizationCode() .clientId(client.getClientId()) .authorizationUri(client.getProviderDetails().getAuthorizationUri()) .redirectUri(client.getRedirectUri()) .scopes(client.getScopes()) .state(UUID.randomUUID().toString()) .additionalParameters(Map.of("acr_values", acr)) .build(); // 存入session供Spring Security的重定向过滤器使用 request.getSession().setAttribute(OAuth2AuthorizationRequestRedirectFilter.AUTHORIZATION_REQUEST_ATTR_NAME, authRequest); return "redirect:" + authRequest.getAuthorizationUri(); }
3. 处理授权后的会话更新与原始操作恢复
- 确保Spring Security配置开启OAuth2登录,授权服务器返回授权码后,
OAuth2LoginAuthenticationFilter会自动完成令牌交换并更新会话:
@EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) .oauth2Login(oauth2 -> oauth2 .authorizationEndpoint(endpoint -> endpoint .authorizationRequestResolver(authorizationRequestResolver(clientRegistrationRepository())))); return http.build(); } }
- 在捕获401时保存原始请求URL,认证成功后重定向回去:
// 捕获401的异常处理器 @ExceptionHandler(HttpClientErrorException.Unauthorized.class) public String handleUnauthorized(HttpServletRequest request, RedirectAttributes redirectAttributes) { request.getSession().setAttribute("originalRequestUrl", request.getRequestURI()); // 从WWW-Authenticate头解析所需的acr值 String acr = extractAcrFromWwwAuthenticateHeader(request.getHeader("WWW-Authenticate")); redirectAttributes.addAttribute("acr", acr); return "redirect:/reauthenticate"; } // OAuth2登录回调控制器 @GetMapping("/login/oauth2/code/oidc") public String oauth2LoginCallback(HttpServletRequest request) { String originalUrl = (String) request.getSession().getAttribute("originalRequestUrl"); if (originalUrl != null) { request.getSession().removeAttribute("originalRequestUrl"); return "redirect:" + originalUrl; } return "redirect:/index"; }
内容的提问来源于stack exchange,提问作者Thibault
相关产品推荐
相关产品推荐

