Spring-security核心拦截器

(一) 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);
    }

 

 

猜你喜欢

转载自silentwu.iteye.com/blog/2214397