匿名用户经过 匿名访问过滤器 将会在contextHolder中存储一个匿名认证,经过异常处理拦截器,经过权限校验拦截器,由于是匿名认证所以权限管理器通过投票器校验失败,抛出权限校验异常,异常处理拦截器处理调用认证切入点重定向到/login 路径下,如果您已经理解了就可以在继续往下看了。如果没有还请您进行断点调试
这个过滤器的实现就比较简单,就是判断当前路径是不是/login 如果是,就调用 response 输出一个登陆页面
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
boolean loginError = isErrorPage(request);
boolean logoutSuccess = isLogoutSuccess(request);
//如果是/login /login?error 或是登出的话返回登录页面
if (isLoginUrlRequest(request) || loginError || logoutSuccess) {
String loginPageHtml = generateLoginPageHtml(request, loginError,
logoutSuccess);
response.setContentType("text/html;charset=UTF-8");
response.setContentLength(loginPageHtml.getBytes(StandardCharsets.UTF_8).length);
response.getWriter().write(loginPageHtml);
return;
}
chain.doFilter(request, response);
}
这样就返回了登录页面
当提交登录后就到这个过滤器进行处理,该过滤器捕获/login method 为 post ,大致流程为调用 AuthenticationManager的provider进行认证,provider调用userDetailsService进行用户数据的读取返回给provider ,provider 将密码使用编码器进行编码后校验如果成功就返回认证过得,失败抛异常,AuthenticationManager的默认实现类为ProviderManager,provider的默认实现类为DaoAuthenticationProvider,UserDetailsService可以简单看一下JdbcDaoImpl,实现过程较简单,读者可以自行阅读
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
String username = obtainUsername(request);
String password = obtainPassword(request);
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
setDetails(request, authRequest);
//调用 AuthenticationManager的provider进行认证,provider调用userDetailsService进行用户数据的读取返回给provider ,provider 将密码使用编码器进行编码后校验如果成功就返回认证过得,失败抛异常
return this.getAuthenticationManager().authenticate(authRequest);
}
此处是一个模板设计模式,父类完成核心逻辑的实现,将认证过程交给子类实现,父类核心代码分析:
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
implements ApplicationEventPublisherAware, MessageSourceAware {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
Authentication authResult;
try {
//模板设计模式,交给子类实现
authResult = attemptAuthentication(request, response);
if (authResult == null) {
return;
}
//session固话攻击和并发控制策略
sessionStrategy.onAuthentication(authResult, request, response);
}
catch (InternalAuthenticationServiceException failed) {
//认证失败
unsuccessfulAuthentication(request, response, failed);
return;
}
catch (AuthenticationException failed) {
unsuccessfulAuthentication(request, response, failed);
return;
}
// Authentication success
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
//认证成功
successfulAuthentication(request, response, chain, authResult);
}
}
此处有两点需要查看就是认证成功做了什么,认证失败又做了什么?
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
//将认证放入context中
SecurityContextHolder.getContext().setAuthentication(authResult);
//记住我
rememberMeServices.loginSuccess(request, response, authResult);
//成功处理器,默认为SavedRequestAwareAuthenticationSuccessHandler
successHandler.onAuthenticationSuccess(request, response, authResult);
}
//
//SavedRequestAwareAuthenticationSuccessHandler核心逻辑
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws ServletException, IOException {
//这在异常处理过滤器中将请求的路径存了进行,以实现源路径的跳转
SavedRequest savedRequest = requestCache.getRequest(request, response);
getRedirectStrategy().sendRedirect(request, response, targetUrl);
}
失败做了什么呢?
失败简单的调用了 rememberMeServices.loginfailes()
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
if (this.requiresLogout(request, response)) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
//核心代码,有一系列的handler 比如CsrfLogoutHandler
//SecurityContextLogoutHandler
//及记住我的service等,这也体现了架构的整体性与灵活性
this.handler.logout(request, response, auth);
this.logoutSuccessHandler.onLogoutSuccess(request, response, auth);
} else {
chain.doFilter(request, response);
}
}
好了,到此为止认证与授权的全部全部内容就完结了,剩下 记住我,session 固话攻击和并发讲完,spring security就彻底完结了
现在总结一下组件:
SecurityContextHolder
, SecurityContext
Authentication
GrantedAuthority
UserDetails
ProviderManager
AuthenticationProvider
UserDetailsService
ChannelProcessingFilter
SecurityContextPersistenceFilter
ConcurrentSessionFilter
UsernamePasswordAuthenticationFilter
, CasAuthenticationFilter
, BasicAuthenticationFilter
SecurityContextHolderAwareRequestFilter
RememberMeAuthenticationFilter
AnonymousAuthenticationFilter
ExceptionTranslationFilter
FilterSecurityInterceptor