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

Spring Security自定义AuthenticationProvider未生效问题求助

问题分析与解决方案

首先,咱们来拆解下你遇到的两个核心问题:

  1. 登录接口/login/authenticateUser不会调用自定义认证提供者
  2. 其他接口返回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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 06:57:43