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

