Spring Security自定义AuthenticationProvider未生效问题求助
问题分析与解决方案
首先,咱们来拆解下你遇到的两个核心问题:
- 登录接口
/login/authenticateUser不会调用自定义认证提供者 - 其他接口返回403,也没触发认证提供者
为什么会这样?
- 登录接口的问题:你把
/login/authenticateUser配置成了permitAll(),这意味着Spring Security的过滤器链会直接放行这个请求,不会触发任何认证流程——自然也就不会调用你的CustomAuthenticationProvider。登录接口需要你手动触发认证,而不是靠Security自动拦截处理。 - 其他接口的403问题:你虽然配置了
anyRequest().authenticated(),但没有告诉Spring Security如何获取请求中的认证凭证(比如用户名密码、Token)。没有凭证来源,Security不知道该怎么调用你的认证提供者,直接返回403禁止访问。
一步步解决问题
1. 修正SecurityConfig,暴露AuthenticationManager并配置认证方式
首先,我们需要把AuthenticationManager暴露出来(方便在Controller里手动调用),同时给其他接口配置一种认证方式(这里先以HTTP Basic为例,你后续可以换成Token认证):
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CustomAuthenticationProvider authProvider; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(authProvider); } // 暴露AuthenticationManager,让Controller可以注入调用 @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/login/authenticateUser").permitAll() // 登录接口依然放开 .anyRequest().authenticated() // 其他接口必须认证 .and() .httpBasic(); // 配置HTTP Basic认证,让Security能从请求中提取凭证 } }
2. 修复CustomAuthenticationProvider的supports方法
你当前的supports方法用了equals,这会导致如果Spring Security使用UsernamePasswordAuthenticationToken的子类时,无法匹配。换成isAssignableFrom更灵活:
@Component public class CustomAuthenticationProvider implements AuthenticationProvider { static Map<String, UserDetails> userSessionList = new HashMap<>(); @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String userName = authentication.getName(); // 修正原代码的语法错误:split(";")[0] String password = authentication.getCredentials().toString().split(";")[0]; if (checkUserNameAndPassword(userName, password)) { List<GrantedAuthority> grantedAuths = new ArrayList<>(); grantedAuths.add(() -> "AUTH_USER"); Authentication auth = new UsernamePasswordAuthenticationToken(userName, password, grantedAuths); // 可以把认证后的用户信息存入session列表 userSessionList.put(userName, new User(userName, password, grantedAuths)); return auth; } else { throw new AuthenticationCredentialsNotFoundException("Invalid Credentials!"); } } @Override public boolean supports(Class<?> authentication) { // 用isAssignableFrom替代equals,兼容子类情况 return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication); } // 这里是你的用户名密码验证逻辑,自行替换成实际业务逻辑 private boolean checkUserNameAndPassword(String userName, String password) { // 示例:固定用户名密码验证,实际应该从数据库/缓存查询 return "admin".equals(userName) && "admin123".equals(password); } }
3. 在登录Controller中手动触发认证
因为登录接口被放行,所以需要我们手动调用AuthenticationManager来触发CustomAuthenticationProvider:
@RestController @RequestMapping("/login") public class LoginController { @Autowired private AuthenticationManager authenticationManager; @PostMapping("/authenticateUser") public ResponseEntity<?> authenticateUser(@RequestBody LoginRequest loginRequest) { try { // 创建认证Token,交给AuthenticationManager处理 Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( loginRequest.getUsername(), loginRequest.getPassword() ) ); // 将认证信息存入SecurityContext,方便后续接口获取用户信息 SecurityContextHolder.getContext().setAuthentication(authentication); return ResponseEntity.ok("登录成功!"); } catch (AuthenticationException e) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage()); } } // 登录请求的DTO类 public static class LoginRequest { private String username; private String password; // Getter和Setter public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } }
验证效果
- 调用
POST /login/authenticateUser:会触发CustomAuthenticationProvider的authenticate方法,认证成功返回200,失败返回401。 - 调用其他接口(比如
GET /api/user):需要在请求头中添加Authorization: Basic base64编码的用户名:密码,此时Security会提取凭证,调用你的自定义认证提供者,认证通过则返回接口数据,失败返回401。
如果后续你想换成Token认证(比如JWT),只需要替换httpBasic()为自定义的Token过滤器,从请求头提取Token并解析出用户信息,再创建Authentication对象交给AuthenticationManager即可。
内容的提问来源于stack exchange,提问作者user2941236
相关产品推荐
相关产品推荐

