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

Spring Security自定义认证:在AuthenticationManager中获取请求认证头

解决方案

针对你的需求,这里提供两种符合Spring Security框架约束的实现方案,优先推荐第一种自定义认证令牌的方式,更贴合框架设计规范:

方案一:自定义AuthenticationToken(推荐)

通过创建专属的认证令牌类,携带token和HTTP请求信息,避免将token伪装为principal传递,同时能在AuthenticationManager中直接获取请求上下文。

1. 自定义AuthenticationToken类

import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import javax.servlet.http.HttpServletRequest;
import java.util.Collection;

public class CustomAuthenticationToken extends AbstractAuthenticationToken {

    private final String token;
    private final HttpServletRequest request;

    // 未认证状态的构造器
    public CustomAuthenticationToken(String token, HttpServletRequest request) {
        super(null);
        this.token = token;
        this.request = request;
        setAuthenticated(false);
    }

    // 认证成功后的构造器
    public CustomAuthenticationToken(String token, HttpServletRequest request, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.token = token;
        this.request = request;
        super.setAuthenticated(true); // 必须调用父类方法设置认证状态
    }

    @Override
    public Object getCredentials() {
        return null; // 无需credentials,返回null
    }

    @Override
    public Object getPrincipal() {
        return null; // 按需求留空,不将token作为principal
    }

    public String getToken() {
        return token;
    }

    public HttpServletRequest getRequest() {
        return request;
    }

    @Override
    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        if (isAuthenticated) {
            throw new IllegalArgumentException("仅能通过带权限列表的构造器设置已认证状态");
        }
        super.setAuthenticated(false);
    }

    @Override
    public void eraseCredentials() {
        super.eraseCredentials();
    }
}

2. 重写自定义过滤器(基于OncePerRequestFilter)

替代AbstractPreAuthenticatedProcessingFilter,直接从请求中提取token,创建自定义认证令牌并触发认证:

import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class CustomTokenFilter extends OncePerRequestFilter {

    private final AuthenticationManager authenticationManager;

    public CustomTokenFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String authorizationHeader = request.getHeader("authorization");
        if (authorizationHeader != null && authorizationHeader.startsWith("Custom ")) {
            String token = authorizationHeader.split(" ")[1];
            // 创建自定义认证令牌,携带token和请求对象
            CustomAuthenticationToken authRequest = new CustomAuthenticationToken(token, request);
            try {
                // 调用认证管理器执行认证逻辑
                Authentication authResult = authenticationManager.authenticate(authRequest);
                // 将认证结果存入SecurityContext
                SecurityContextHolder.getContext().setAuthentication(authResult);
            } catch (AuthenticationException e) {
                // 认证失败时清空上下文并返回401
                SecurityContextHolder.clearContext();
                response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "无效的自定义令牌");
                return;
            }
        }
        // 继续执行后续过滤器链
        filterChain.doFilter(request, response);
    }
}

3. 修改自定义认证管理器

适配自定义令牌,直接获取token和请求对象:

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import java.util.Collections;

public class CustomAuthenticationMgr implements AuthenticationManager {

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // 仅处理自定义认证令牌
        if (!(authentication instanceof CustomAuthenticationToken)) {
            throw new AuthenticationServiceException("不支持的认证类型");
        }

        CustomAuthenticationToken customToken = (CustomAuthenticationToken) authentication;
        String token = customToken.getToken();
        HttpServletRequest request = customToken.getRequest(); // 按需获取请求上下文

        // 调用外部库执行token验证逻辑(示例仅做长度校验)
        if (token == null || token.length() <= 0) {
            throw new AuthenticationServiceException("令牌无效");
        }

        // 验证通过后,构造已认证的令牌(权限可从外部库获取或根据业务设置)
        return new CustomAuthenticationToken(token, request, Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")));
    }
}

4. 更新Security配置

调整过滤器的添加方式,指定过滤器位置:

@Configuration
@EnableWebSecurity(debug = true)
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception{
        CustomAuthenticationMgr authMgr = new CustomAuthenticationMgr();
        CustomTokenFilter customTokenFilter = new CustomTokenFilter(authMgr);

        httpSecurity
                .csrf().disable() // 根据业务需求配置csrf
                .addFilterBefore(customTokenFilter, UsernamePasswordAuthenticationFilter.class) // 放在用户名密码过滤器之前
                .authorizeRequests()
                .mvcMatchers("/users/**").authenticated()
                .mvcMatchers("/other-api/**").permitAll()
                .and()
                .httpBasic();
    }
}

方案二:利用PreAuthenticatedAuthenticationToken的details字段

如果不想自定义令牌,可以将token存入PreAuthenticatedAuthenticationToken的details字段,避免占用principal:

修改自定义过滤器

public class CustomTokenFilter extends AbstractPreAuthenticatedProcessingFilter {

    @Override
    protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
        return null; // principal留空
    }

    @Override
    protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
        return null; // credentials留空
    }

    @Override
    protected Authentication createAuthenticationRequest(HttpServletRequest request) {
        String authorization = request.getHeader("authorization");
        String token = null;
        if(authorization != null && authorization.startsWith("Custom ")){
            token = authorization.split(" ")[1];
        }
        PreAuthenticatedAuthenticationToken authRequest = new PreAuthenticatedAuthenticationToken(null, null);
        authRequest.setDetails(token); // 将token存入details字段
        return authRequest;
    }
}

修改认证管理器

public class CustomAuthenticationMgr implements AuthenticationManager {
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String token = (String) authentication.getDetails();
        if(token == null || token.length() <= 0){
            throw new AuthenticationServiceException("令牌无效");
        }
        // 验证通过后设置权限并标记为已认证
        authentication.setAuthenticated(true);
        authentication.setAuthorities(Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")));
        return authentication;
    }
}

方案对比

  • 方案一:完全贴合Spring Security的设计理念,令牌职责清晰,扩展性强,适合长期维护。
  • 方案二:改动量小,适合快速适配,但details字段原本用于存储请求细节,用它存token属于变通用法,可读性稍差。

内容的提问来源于stack exchange,提问作者univ27all

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 03:27:39