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
相关产品推荐
相关产品推荐

