You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 19:42:38