(一) 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();
List<Cookie> getCookies();
String getMethod();
List<String> getHeaderValues(String name);
Collection<String> getHeaderNames();
List<Locale> getLocales();
String[] getParameterValues(String name);
Map<String,String[]> 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);
}