springsecurity的认证流程
springsecurity的认证流程如下,登录请求被UsernamePasswordAuthenticationFilter拦截,过滤器调用认证管理器,认证管理器使用authenticationprovider来进行认证,userdetailservice用来提供用户信息(userdetails)给authenticationprovider以便认证
UsernamePasswordAuthenticationFilter
UsernamePasswordAuthenticationFilter继承了AbstractAuthenticationProcessingFilter,AbstractAuthenticationProcessingFilter里doFilter负责拦截请求,调用虚拟方法attemptAuthentication认证,UsernamePasswordAuthenticationFilter重写attemptAuthentication方法来实现认证。doFilter源码方法如下
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 判断是否拦截请求,默认的拦截路径是/login,post请求
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Request is to process authentication");
}
Authentication authResult;
try {
// 认证
authResult = attemptAuthentication(request, response);
if (authResult == null) {
// return immediately as subclass has indicated that it hasn't completed
// authentication
return;
}
sessionStrategy.onAuthentication(authResult, request, response);
}
catch (InternalAuthenticationServiceException failed) {
logger.error(
"An internal error occurred while trying to authenticate the user.",
failed);
unsuccessfulAuthentication(request, response, failed);
return;
}
catch (AuthenticationException failed) {
// 认证方法通过抛出异常AuthenticationException 来通知过滤器认证失败,调用认证失败后的处理方法
unsuccessfulAuthentication(request, response, failed);
return;
}
// Authentication success
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
// 认证成功后的处理方法
successfulAuthentication(request, response, chain, authResult);
}
UsernamePasswordAuthenticationFilter通过重写attemptAuthentication来实现认证,源码如下,其中获取用户名密码,默认的字段是username和password,认证管理器会调用AuthenticationProvider来进行认证,AuthenticationProvider有多个实现,默认的是ProviderManager,ProviderManager持有多个AuthenticationProvider的集合,通过遍历调用来实现认证。
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
// 获取用户名密码
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
// 调用认证管理器进行认证
return this.getAuthenticationManager().authenticate(authRequest);
}
认证成功
认证成功后,会将认证信息放入SecurityContext中,SecurityContext可以通过SecurityContextHolder来获取,SecurityContext存放在SecurityContextHolderStrategy中,SecurityContextHolderStrategy有多重实现,一般我们web项目里的实现用的是ThreadLocalSecurityContextHolderStrategy,ThreadLocalSecurityContextHolderStrategy将SecurityContext存放在ThreadLocal中,也就是存放在当前线程里,一般项目线程都是由线程池来管理,及时请求结束,也不会销毁线程,所以在请求结束前必须要清空ThreadLocal,认证中只负责在上下文中存放认证信息,上线文的管理由其他过滤器实现。源码如下
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
+ authResult);
}
// 将认证成功的信息存放在security的上下文里
SecurityContextHolder.getContext().setAuthentication(authResult);
rememberMeServices.loginSuccess(request, response, authResult);
// Fire event
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
authResult, this.getClass()));
}
successHandler.onAuthenticationSuccess(request, response, authResult);
}
参考博文: