(一) ExceptionTranslationFilter
Spring-security的异常拦截器:这个拦截器只拦截AuthenticationException和AccessDeniedException异常,其他异常直接抛出
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; try { chain.doFilter(request, response); logger.debug("Chain processed normally"); } catch (IOException ex) { 若捕获IOException直接抛出 throw ex; } catch (Exception ex) { // Try to extract a SpringSecurityException from the stacktrace Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex); //获取认证异常 RuntimeException ase = (AuthenticationException) throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain); if (ase == null) { //若没有找到认证异常,就找授权异常 ase = (AccessDeniedException)throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain); } if (ase != null) { //处理spring-security抛出的异常 //1. 若是认证异常交给AuthenticationEntryPoint处理 //2. 若是授权异常交给AccessDeniedHandler处理 handleSpringSecurityException(request, response, chain, ase); } else { //省略… 抛出其他异常 } } }
AuthenticationEntryPoint
当用户请求了一个受保护的资源,但是用户没有通过认证,那么抛出异常,AuthenticationEntryPoint. Commence(..)就会调用
public interface AuthenticationEntryPoint { void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException; }
LoginUrlAuthenticationEntryPoint :跳转到登录页面
SavedRequest & RequestCache
在用户还为通过认证,访问了需要认证的资源,那么spring-security会保存当前的request,然后跳转到登录界面进行认证,在认证通过后,spring-security会跳转到保存的request;这个功能就是通过SavedRequest 和RequestCache 接口
1. SavedRequest:保存request的所有信息,默认的实现是DefaultSavedRequest
public interface SavedRequest extends java.io.Serializable { String getRedirectUrl(); ListgetCookies(); String getMethod(); List getHeaderValues(String name); Collection getHeaderNames(); List getLocales(); String[] getParameterValues(String name); Map getParameterMap(); }
2. RequestCache:默认的实现HttpSessionRequestCache,保存SavedRequest到session,
可以自己设置RequestMatcher,默认是AnyRequestMatcher
public interface RequestCache { void saveRequest(HttpServletRequest request, HttpServletResponse response); SavedRequest getRequest(HttpServletRequest request, HttpServletResponse response); HttpServletRequest getMatchingRequest(HttpServletRequest request, HttpServletResponse response); void removeRequest(HttpServletRequest request, HttpServletResponse response); }
AccessDeniedHandler
用户已经通过了认证,在访问一个受保护的资源,但是权限不够,那么抛出异常,AccessDeniedHandler. Handle(..)会调用
AccessDeniedHandlerImpl:若用户配置了权限不足的错误页面,那么就会跳转到错误页面;
若没有配置,那么就返回一个403
(二) SecurityContextPersistenceFilter
这个拦截器只完成两个任务:
1. 通过SecurityContextRepository获取到SecurityContext,默认是从session中获取,若没有找到,那么SecurityContextRepository就是创建一个空的,然后在通过SecurityContextHolder把获取到的SecurityContext保存到ThreadLocal
2. 在请求结束后通过SecurityContextHolder清除掉ThreadLocal中的SecurityContext
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; 确保这个拦截器一次请求只执行一次 if (request.getAttribute(FILTER_APPLIED) != null) { chain.doFilter(request, response); return; } //省略….. HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response); 通过SecurityContextRepository获取SecurityContext,SecurityContextRepository默认是HttpSessionSecurityContextRepository SecurityContext contextBeforeChainExecution = repo.loadContext(holder); try { 默认SecurityContextHolder把SecurityContext保存到ThreadLocal中 SecurityContextHolder.setContext(contextBeforeChainExecution); chain.doFilter(holder.getRequest(), holder.getResponse()); } finally { SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext(); // Crucial removal of SecurityContextHolder contents - do this before anything else. 清除SecurityContext,保存SecurityContext到session中 SecurityContextHolder.clearContext(); repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse()); request.removeAttribute(FILTER_APPLIED); } } }
(三) UsernamePasswordAuthenticationFilter
处理用户登录的拦截器,继承与AbstractAuthenticationProcessingFilter。
在AbstractAuthenticationProcessingFilter中的doFilter:
public void doFilter(ServletRequest req,ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; //匹配url,通过RequestMatcher判断是否认证请求的url是指定结尾的,默认的结尾是/j_spring_security_check,所有在form表单提交的地址要以/j_spring_security_check结尾 if (!requiresAuthentication(request, response)) { chain.doFilter(request, response); return; } Authentication authResult; try { //调用子类UsernamePasswordAuthenticationFilter的attemptAuthentication方法 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) { // 在认证的过程中抛出了异常,那么就认证失败。 //从SecurityContextHolder清除掉SecurityContext; //AuthenticationFailureHandler处理后续操作,跳转到认证失败URL可以在security.xml中配置 //RememberMeServices 删除保存的cookie unsuccessfulAuthentication(request, response, failed); return; } catch (AuthenticationException failed) { // Authentication failed unsuccessfulAuthentication(request, response, failed); return; } // Authentication success //保存SecurityContext //RememberMeServices保存cookie //AuthenticationSuccessHandler 若有缓存到session中的URL,那么跳转,若没有,那么跳转到默认的URL,可以在security.xml中配置 successfulAuthentication(request, response, chain, authResult); }
UsernamePasswordAuthenticationFilter中的attemptAuthentication方法:
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { //判断是否是POST请求,默认是要求必须是POST if (postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); } //从request中解析出表单提交的username password,默认的参数名称是:j_username j_password String username = obtainUsername(request); String password = obtainPassword(request); //省略…… UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); // Allow subclasses to set the "details" property setDetails(request, authRequest); //获取到AuthenticationManager调用authenticate方法进行认证 return this.getAuthenticationManager().authenticate(authRequest); }