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

Spring OAuth2自定义登录字段捕获失败及授权拒绝问题解决

问题诊断与解决方案

1. 解决AuthorizationDeniedException: Access Denied错误

出现该错误的核心原因是自定义的CustomDaoAuthenticationProvider未被注册到AuthenticationManager的认证链中,导致认证流程使用默认Provider,无法处理自定义验证逻辑,最终认证失败触发权限拒绝。

修复步骤:
在defaultSecurityFilterChain中通过authenticationProvider方法注册自定义Provider,同时优化CSRF配置避免表单提交失败:

@Bean
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http,
                                                      AuthenticationConfiguration authenticationConfiguration)
        throws Exception {

    http
            .authorizeHttpRequests((authorize) -> authorize
                    .requestMatchers("/css/**", "/imagenes/**", "/iconos/**").permitAll()
                    .requestMatchers("/login").permitAll()
                    .requestMatchers("/error").permitAll()
                    .anyRequest().authenticated()
            )
            .csrf(csrf -> csrf
                    .ignoringRequestMatchers("/login") // 登录接口关闭CSRF,适配表单提交
            )
            .formLogin(form -> form
                    .loginPage("/login")
                    .permitAll()
            )
            .authenticationProvider(customDaoAuthenticationProvider()) // 注册自定义认证Provider
            .addFilterBefore(new CustomAuthenticationFilter(authenticationManager(authenticationConfiguration)), UsernamePasswordAuthenticationFilter.class);

    return http.build();
}

Spring Security会自动收集所有AuthenticationProvider类型的Bean到AuthenticationManager中,注册后自定义Provider会参与认证流程。

2. 在CustomDaoAuthenticationProvider中获取tipoDocumento和recordar字段

不推荐依赖HttpSession传递字段,更可靠的方式是通过自定义AuthenticationToken嵌入额外字段,步骤如下:

步骤1:创建自定义AuthenticationToken

继承UsernamePasswordAuthenticationToken,添加额外字段:

public class CustomAuthenticationToken extends UsernamePasswordAuthenticationToken {
    private final String tipoDocumento;
    private final boolean recordar;

    public CustomAuthenticationToken(Object principal, Object credentials, String tipoDocumento, boolean recordar) {
        super(principal, credentials);
        this.tipoDocumento = tipoDocumento;
        this.recordar = recordar;
    }

    // getter方法
    public String getTipoDocumento() {
        return tipoDocumento;
    }

    public boolean isRecordar() {
        return recordar;
    }
}
步骤2:修改CustomAuthenticationFilter生成自定义Token

替换原有attemptAuthentication方法,生成自定义Token并传入认证流程:

public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    public CustomAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        String username = obtainUsername(request);
        String password = obtainPassword(request);
        String tipoDocumento = request.getParameter("tipoDocumento");
        tipoDocumento = tipoDocumento != null ? tipoDocumento.trim() : "";
        boolean recordar = "on".equalsIgnoreCase(request.getParameter("recordar")); // 适配复选框提交值

        // 创建自定义认证Token
        CustomAuthenticationToken authRequest = new CustomAuthenticationToken(username, password, tipoDocumento, recordar);
        setDetails(request, authRequest);
        
        return this.getAuthenticationManager().authenticate(authRequest);
    }
}
步骤3:修改CustomDaoAuthenticationProvider获取字段

在验证方法中强制转换为自定义Token,直接读取字段并添加逻辑:

protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
    if (authentication.getCredentials() == null) {
        this.logger.debug("Failed to authenticate since no credentials provided");
        throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
    } else {
        // 转换为自定义Token获取字段
        CustomAuthenticationToken customAuthToken = (CustomAuthenticationToken) authentication;
        String tipoDocumento = customAuthToken.getTipoDocumento();
        boolean recordar = customAuthToken.isRecordar();
        
        // 添加证件类型验证逻辑
        CustomUserDetails customUserDetails = (CustomUserDetails) userDetails;
        if (!customUserDetails.getTipoDocumento().equals(tipoDocumento)) {
            throw new BadCredentialsException("Invalid document type");
        }

        // 原有密码验证逻辑
        String presentedPassword = customUserDetails.getSalt() + authentication.getCredentials().toString();
        String secret = customUserDetails.getSecret();
        if (!this.passwordEncoder.matches(presentedPassword, secret)) {
            this.logger.debug("Failed to authenticate since password does not match stored value");
            throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        }

        // 处理"记住我"逻辑(如设置持久化Cookie)
        if (recordar) {
            // 自定义记住我逻辑实现
        }
    }
}

3. 额外注意事项

  • 确保登录表单的字段名称与request.getParameter中的名称完全一致(tipoDocumento、recordar)。
  • 若保留CSRF保护,需在登录表单中添加CSRF令牌:<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>。
  • 确认CustomUserDetails包含tipoDocumento字段及对应getter方法,保证验证逻辑可正常获取用户证件类型。

内容的提问来源于stack exchange,提问作者Favio Amarilla Miño

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 14:22:35