zoukankan      html  css  js  c++  java
  • Spring Security学习总结及源码分析

    概要

    Spring Security是 Spring 家族中的成员,基于 Spring 提供了一套 Web 应用安全性的完整解决方案。Spring Security重要的核心功能包含认证和授权两部分:

    • 用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程
    • 用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。通俗点讲就是系统判断用户是否有权限去做某些事情

    同类产品比较

    Spring Security

    • 与 Spring 无缝整合
    • 全面的权限控制
    • 专门为Web开发而设计(旧版本不能脱离 Web 环境使用,新版本对整个框架进行了分层抽取,分成了核心模块和 Web 模块。单独引入核心模块就可以脱离 Web 环境)
    • 重量级

    Shiro:Apache 旗下的轻量级权限控制框架

    • 轻量级(Shiro主张的理念是把复杂的事情变简单,针对对性能有更高要求的互联网应用有更好表现)
    • 通用性(不局限于 Web 环境,可以脱离 Web 环境使用,在 Web 环境下一些特定的需求需要手动编写代码定制)

    相对于 Shiro,在 SSM 中整合 Spring Security 是比较麻烦的操作,所以Spring Security 虽然功能比 Shiro 强大,但是使用反而没有 Shiro 多。自从有了 Spring Boot 之后,Spring Boot 对于 Spring Security 提供了自动化配置方案,可以使用更少的配置来使用 Spring Security。因此,一般来说,常见的安全管理技术栈的组合是这样的:

    • SSM + Shiro
    • Spring Boot/Spring Cloud + Spring Security

    Spring Security基本原理

    /**
     * SpringSecurity 采用的是责任链的设计模式,它有一条很长的过滤器链
     * 从启动是可以获取到过滤器链如下
     */
    // 将 Security 上下文与 Spring Web 中用于 处理异步请求映射的 WebAsyncManager 进行集成
    org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter
    // 在每次请求处理之前将该请求相关的安全上 下文信息加载到 SecurityContextHolder 中,然后在该次请求处理完成之后,将 SecurityContextHolder 中关于这次请求的信息存储到一个“仓储”中,然后将 SecurityContextHolder 中的信息清除,例如在 Session 中维护一个用户的安全信息就是这个过滤器处理的
    org.springframework.security.web.context.SecurityContextPersistenceFilter
    // 用于将头信息加入响应中
    org.springframework.security.web.header.HeaderWriterFilter
    // 用于处理跨站请求伪造
    org.springframework.security.web.csrf.CsrfFilter 
    // 用于处理退出登录
    org.springframework.security.web.authentication.logout.LogoutFilter 
    // 用于处理基于表单的登录请求,从表单中 获取用户名和密码。默认情况下处理来自 /login 的请求。从表单中获取用户名和密码 时,默认使用的表单 name 值为 username 和 password,这两个值可以通过设置这个 过滤器的 usernameParameter 和 passwordParameter 两个参数的值进行修改
    org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter 
    // 如果没有配置登录页面,那系统初始化时就会 配置这个过滤器,并且用于在需要进行登录时生成一个登录表单页面
    org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter 
    // 检测和处理 http basic 认证
    org.springframework.security.web.authentication.www.BasicAuthenticationFilter
    // 用来处理请求的缓存
    org.springframework.security.web.savedrequest.RequestCacheAwareFilter 
    // 主要是包装请求对象 request
    org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter 
    // 检测 SecurityContextHolder 中是否存在 Authentication 对象,如果不存在为其提供一个匿名 Authentication
    org.springframework.security.web.authentication.AnonymousAuthenticationFilter 
    // 管理 session 的过滤器
    org.springframework.security.web.session.SessionManagementFilter 
    // 处理 AccessDeniedException 和 AuthenticationException 异常
    org.springframework.security.web.access.ExceptionTranslationFilter 
    // 可以看做过滤器链的出口
    org.springframework.security.web.access.intercept.FilterSecurityInterceptor
    // 当用户没有登录而直接访问资源时, 从 cookie 里找出用户的信息, 如果 Spring Security 能够识别出用户提供的 remember me cookie,用户将不必填写用户名和密码, 而是直接登录进入系统,该过滤器默认不开启
    org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter
    
    
          /**
           * 其中需要重点查看的有以下三个
           */
          // FilterSecurityInterceptor:方法级的权限过滤器, 基本位于过滤链的最底部
          public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
    		if (isApplied(filterInvocation) && this.observeOncePerRequest) {
    			// filter already applied to this request and user wants us to observe
    			// once-per-request handling, so don't re-do security checking
    			filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
    			return;
    		}
    		// first time this request being called, so perform security checking
    		if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
    			filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
    		}
                    // 查看之前的 filter 是否通过
    		InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
    		try {
                            // 真正的调用后台的服务
    			filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
    		}
    		finally {
    			super.finallyInvocation(token);
    		}
    		super.afterInvocation(token, null);
    	}
    
            // ExceptionTranslationFilter:异常过滤器,用来处理在认证授权过程中抛出的异常
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    			throws IOException, ServletException {
    		doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
    	}
    	private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
    			throws IOException, ServletException {
    		try {
    			chain.doFilter(request, response);
    		}
    		catch (IOException ex) {
    			throw ex;
    		}
    		catch (Exception ex) {
    			// Try to extract a SpringSecurityException from the stacktrace
    			Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
    			RuntimeException securityException = (AuthenticationException) this.throwableAnalyzer
    					.getFirstThrowableOfType(AuthenticationException.class, causeChain);
    			if (securityException == null) {
    				securityException = (AccessDeniedException) this.throwableAnalyzer
    						.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
    			}
    			if (securityException == null) {
    				rethrow(ex);
    			}
    			if (response.isCommitted()) {
    				throw new ServletException("Unable to handle the Spring Security Exception "
    						+ "because the response is already committed.", ex);
    			}
    			handleSpringSecurityException(request, response, chain, securityException);
    		}
    	}
    
          // UsernamePasswordAuthenticationFilter:对/login 的 POST请求做拦截,校验表单中用户名,密码
          public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
    			throws AuthenticationException {
    		if (this.postOnly && !request.getMethod().equals("POST")) {
    			throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
    		}
    		String username = obtainUsername(request);
    		username = (username != null) ? username : "";
    		username = username.trim();
    		String password = obtainPassword(request);
    		password = (password != null) ? password : "";
    		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
    		// Allow subclasses to set the "details" property
    		setDetails(request, authRequest);
    		return this.getAuthenticationManager().authenticate(authRequest);
    	}
    

    SpringSecurity 基本流程

    Spring Security 采取过滤链实现认证与授权,只有当前过滤器通过,才能进入下一个过滤器

    绿色部分是认证过滤器,需要我们自己配置,可以配置多个认证过滤器。认证过滤器可以 使用 Spring Security 提供的认证过滤器,也可以自定义过滤器(例如:短信验证)。认证过滤器要在 configure(HttpSecurity http)方法中配置,没有配置不生效。下面会重点介绍以下三个过滤器

    UsernamePasswordAuthenticationFilter 过滤器:该过滤器会拦截前端提交的 POST 方式的登录表单请求,并进行身份认证
    ExceptionTranslationFilter 过滤器:该过滤器不需要我们配置,对于前端提交的请求会直接放行,捕获后续抛出的异常并进行处理(例如:权限访问限制)
    FilterSecurityInterceptor 过滤器:该过滤器是过滤器链的最后一个过滤器,根据资源 权限配置来判断当前请求是否有权限访问对应的资源。如果访问受限会抛出相关异常,并由ExceptionTranslationFilter 过滤器进行捕获和处理

    SpringSecurity 认证流程

    /**
     * UsernamePasswordAuthenticationFilter 源码部分
     */
            // 当前端提交的是一个 POST 方式的登录表单请求,就会被该过滤器拦截,并进行身份认证。该过滤器的 doFilter() 方法实现在其抽象父类AbstractAuthenticationProcessingFilter中,相关源码如下
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    			throws IOException, ServletException {
    		doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
    	}
    
    	private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
    			throws IOException, ServletException {
    		if (!requiresAuthentication(request, response)) {
                            // 判断该请求是否是POST方式的表单提交,不是则放行进入下一个过滤器
    			chain.doFilter(request, response);
    			return;
    		}
    		try {
                            // Authentication用来存储用户认证信息的类,attemptAuthentication调用子类重写的方法进行身份认证
    			Authentication authenticationResult = attemptAuthentication(request, response);
    			if (authenticationResult == null) {
    				// return immediately as subclass has indicated that it hasn't completed
    				return;
    			}
                            // Session处理策略,如果配置了用户Session最大并发数,则在此进行判断处理
    			this.sessionStrategy.onAuthentication(authenticationResult, request, response);
    			// Authentication success
                            // 默认continueChainBeforeSuccessfulAuthentication为false,所以认证成功之后不进入下一个过滤器
    			if (this.continueChainBeforeSuccessfulAuthentication) {
    				chain.doFilter(request, response);
    			}
                            // 调用认证成功的处理器
    			successfulAuthentication(request, response, chain, authenticationResult);
    		}
    		catch (InternalAuthenticationServiceException failed) {
    			this.logger.error("An internal error occurred while trying to authenticate the user.", failed);
                            // 调用认证失败的处理器
    			unsuccessfulAuthentication(request, response, failed);
    		}
    		catch (AuthenticationException ex) {
    			// Authentication failed
    			unsuccessfulAuthentication(request, response, ex);
    		}
    	}
    
    /**
     * 上面的过程调用了UsernamePasswordAuthenticationFilter 的 attemptAuthentication() 方法,源码如下
     */
    public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    
            // 默认表单用户名参数名
    	public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
            
            // 默认表单密码参数名
    	public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
    
            // 默认路径/login,请求方式POST
    	private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login",
    			"POST");
            // 默认只能是POST
    	private boolean postOnly = true;
    
    	// 身份认证方法
    	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
    			throws AuthenticationException {
                    // 默认如果不是POST会抛出异常
    		if (this.postOnly && !request.getMethod().equals("POST")) {
    			throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
    		}
                    // 获取用户名和密码
    		String username = obtainUsername(request);
    		username = (username != null) ? username : "";
    		username = username.trim();
    		String password = obtainPassword(request);
    		password = (password != null) ? password : "";
                    // 构建Authentication对象,标记为未认证
    		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
    		// 将请求中的一些属性信息设置到Authentication中,如sessionId,remoteAddress等
    		setDetails(request, authRequest);
                    // 调用authenticate方法进行认证
    		return this.getAuthenticationManager().authenticate(authRequest);
    	}
    }
    
    /**
     * 上述过程创建的 UsernamePasswordAuthenticationToken 是 Authentication 接口的实现类,该类有两个构造器,一个用于封装前端请求传入的未认证的用户信息,一个用于封装认证成功后的用户信息
     */
    public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
    
    	private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
    
    	private final Object principal;
    
    	private Object credentials;
    
    	/**
    	 * 用于封装未认证的用户信息
    	 */
    	public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
    		super(null); // 权限为null
    		this.principal = principal; // 用户名
    		this.credentials = credentials; // 密码
    		setAuthenticated(false); //标记未认证
    	}
    
    	/**
    	 * 
    	 */
    	public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
    			Collection<? extends GrantedAuthority> authorities) {
    		super(authorities); // 权限列表
    		this.principal = principal; // 封装的UserDetails对象,不再是用户名
    		this.credentials = credentials; // 密码
    		super.setAuthenticated(true); // 标记认证成功
    	}
    }
    
    /**
     * ProviderManager 源码部分
     * 上述过程中,UsernamePasswordAuthenticationFilter 过滤器的 attemptAuthentication() 方法的将未认证的 Authentication 对象传入 ProviderManager 类的 authenticate() 方法进行身份认证
     * ProviderManager 是 AuthenticationManager 接口的实现类,该接口是认证相关的核心接口,也是认证的入口。
     * 在实际开发中,我们可能有多种不同的认证方式,例如:用户名+ 密码、邮箱+密码、手机号+验证码等,而这些认证方式的入口始终只有一个,那就是 AuthenticationManager。
     * 在该接口的常用实现类 ProviderManager 内部会维护一个 List<AuthenticationProvider>列表,存放多种认证方式,实际上这是委托者模式 (Delegate)的应用。
     * 每种认证方式对应着一个 AuthenticationProvider, AuthenticationManager 根据认证方式的不同(根据传入的 Authentication 类型判断)委托对应的 AuthenticationProvider 进行用户认证
     */
    public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
            public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                    // 获取传入的Authentication类型,即UsernamePasswordAuthenticationToken.class
    		Class<? extends Authentication> toTest = authentication.getClass();
    		AuthenticationException lastException = null;
    		AuthenticationException parentException = null;
    		Authentication result = null;
    		Authentication parentResult = null;
    		int currentPosition = 0;
    		int size = this.providers.size();
    		for (AuthenticationProvider provider : getProviders()) {
                            // 判断当前AuthenticationProvider是否适用UsernamePasswordAuthenticationToken.class类型的Authentication
    			if (!provider.supports(toTest)) {
    				continue;
    			}
    			if (logger.isTraceEnabled()) {
    				logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
    						provider.getClass().getSimpleName(), ++currentPosition, size));
    			}
    			try {
                                    // 调用DaoAuthenticationProvider的authenticate方法进行认证
    				result = provider.authenticate(authentication);
    				if (result != null) {
                                            // 认证成功之后将传入的Authentication中的details拷贝到已认证中的Authentication
    					copyDetails(authentication, result);
    					break;
    				}
    			}
    			catch (AccountStatusException | InternalAuthenticationServiceException ex) {
    				prepareException(ex, authentication);
    				// SEC-546: Avoid polling additional providers if auth failure is due to
    				// invalid account status
    				throw ex;
    			}
    			catch (AuthenticationException ex) {
    				lastException = ex;
    			}
    		}
    		if (result == null && this.parent != null) {
    			// Allow the parent to try.
    			try {
                                    // 认证失败,使用父类AuthenticateManager进行认证
    				parentResult = this.parent.authenticate(authentication);
    				result = parentResult;
    			}
    			catch (ProviderNotFoundException ex) {
    				// ignore as we will throw below if no other exception occurred prior to
    				// calling parent and the parent
    				// may throw ProviderNotFound even though a provider in the child already
    				// handled the request
    			}
    			catch (AuthenticationException ex) {
    				parentException = ex;
    				lastException = ex;
    			}
    		}
    		if (result != null) {
                            // 认证成功之后,去除result的敏感信息,要求相关类实现CredentialsContainer接口
    			if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
    				// Authentication is complete. Remove credentials and other secret data
    				// from authentication
    				((CredentialsContainer) result).eraseCredentials();
    			}
    			// If the parent AuthenticationManager was attempted and successful then it
    			// will publish an AuthenticationSuccessEvent
    			// This check prevents a duplicate AuthenticationSuccessEvent if the parent
    			// AuthenticationManager already published it
    			if (parentResult == null) {
                                    // 发布认证成功的事件
    				this.eventPublisher.publishAuthenticationSuccess(result);
    			}
    
    			return result;
    		}
    
    		// Parent was null, or didn't authenticate (or throw an exception).
    		if (lastException == null) {
                            // 认证失败,抛出失败的异常信息
    			lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound",
    					new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}"));
    		}
    		// If the parent AuthenticationManager was attempted and failed then it will
    		// publish an AbstractAuthenticationFailureEvent
    		// This check prevents a duplicate AbstractAuthenticationFailureEvent if the
    		// parent AuthenticationManager already published it
    		if (parentException == null) {
    			prepareException(lastException, authentication);
    		}
    		throw lastException;
    	}
    }
    
    /**
     * 上述认证成功之后,调用 CredentialsContainer 接口定义的 eraseCredentials() 方法去除敏感信息。查看 UsernamePasswordAuthenticationToken 实现的 eraseCredentials() 方法,该方法实现在其父类中
     */
    public abstract class AbstractAuthenticationToken implements Authentication, CredentialsContainer {
            public void eraseCredentials() {
    		eraseSecret(getCredentials());
    		eraseSecret(getPrincipal());
    		eraseSecret(this.details);
    	}
            // 如果想要去除敏感信息,需要实现CredentialsContainer的eraseCredentials方法
    	private void eraseSecret(Object secret) {
    		if (secret instanceof CredentialsContainer) {
    			((CredentialsContainer) secret).eraseCredentials();
    		}
    	}
    }
    
    /**
     * AbstractAuthenticationProcessingFilter中关于认证成功和认证失败的处理
     */
    public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
    		implements ApplicationEventPublisherAware, MessageSourceAware {
    
            // 认证成功
            protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
    			Authentication authResult) throws IOException, ServletException {
                    // 将认证成功的用户信息封装到SecurityContext中
                    // SecurityContextHolder是对ThreadLocal的一个封装
    		SecurityContextHolder.getContext().setAuthentication(authResult);
    		if (this.logger.isDebugEnabled()) {
    			this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));
    		}
                    // rememberMe的处理
    		this.rememberMeServices.loginSuccess(request, response, authResult);
    		if (this.eventPublisher != null) {
                            // 发布认证成功的事件
    			this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
    		}
                    // 调用认证成功的处理器
    		this.successHandler.onAuthenticationSuccess(request, response, authResult);
    	}
            // 认证失败后的处理
            protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
    			AuthenticationException failed) throws IOException, ServletException {
                    // 清除该线程在SecurityContextHolder中对应的SecurityContext对象
    		SecurityContextHolder.clearContext();
    		this.logger.trace("Failed to process authentication request", failed);
    		this.logger.trace("Cleared SecurityContextHolder");
    		this.logger.trace("Handling authentication failure");
                    // rememberMe的处理
    		this.rememberMeServices.loginFail(request, response);
                    // 调用认证失败的处理器
    		this.failureHandler.onAuthenticationFailure(request, response, failed);
    	}
    }
    

    SpringSecurity 权限访问流程

    SpringSecurity权限访问流程,主要是对 ExceptionTranslationFilter 过滤器和 FilterSecurityInterceptor 过滤器进行介绍

    /**
     * 该过滤器是用于处理异常的,不需要我们配置,对于前端提交的请求会直接放行,捕获后续抛出的异常并进行处理(例如:权限访问限制)
     */
    public class ExceptionTranslationFilter extends GenericFilterBean {
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    			throws IOException, ServletException {
    		doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
    	}
    
    	private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
    			throws IOException, ServletException {
    		try {
                            // 对于请求直接放行
    			chain.doFilter(request, response);
    		}
    		catch (IOException ex) {
    			throw ex;
    		}
    		catch (Exception ex) {
    			// 对捕获的异常进行处理
    			Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
                            // 访问需要认证的资源,但当前请求未认证抛出的异常
    			RuntimeException securityException = (AuthenticationException) this.throwableAnalyzer
    					.getFirstThrowableOfType(AuthenticationException.class, causeChain);
    			if (securityException == null) {
                                    访问权限受限抛出的异常
    				securityException = (AccessDeniedException) this.throwableAnalyzer
    						.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
    			}
    			if (securityException == null) {
    				rethrow(ex);
    			}
    			if (response.isCommitted()) {
    				throw new ServletException("Unable to handle the Spring Security Exception "
    						+ "because the response is already committed.", ex);
    			}
    			handleSpringSecurityException(request, response, chain, securityException);
    		}
    	}
    }
    
    /**
     * FilterSecurityInterceptor 是过滤器链的最后一个过滤器,该过滤器是过滤器链的最后一个过滤器,根据资源权限配置来判断当前请求是否有权限访问对应的资源。如果访问受限会抛出相关异常,最终所抛出的异常会由前一个过滤器 ExceptionTranslationFilter 进行捕获和处理
     *  需要注意,Spring Security 的过滤器链是配置在 SpringMVC 的核心组件 DispatcherServlet 运行之前。也就是说,请求通过 Spring Security 的所有过滤器, 不意味着能够正常访问资源,该请求还需要通过 SpringMVC 的拦截器链
     */
    public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    			throws IOException, ServletException {
    		invoke(new FilterInvocation(request, response, chain));
    	}
    
            public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
    		if (isApplied(filterInvocation) && this.observeOncePerRequest) {
    			// filter already applied to this request and user wants us to observe
    			// once-per-request handling, so don't re-do security checking
    			filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
    			return;
    		}
    		// first time this request being called, so perform security checking
    		if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
    			filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
    		}
                    // 根据资源配置权限来判断当前请求是否有权限来访问对应的资源,如果不能访问则抛出异常
    		InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
    		try {
                            // 访问相关资源需要通过SpringMVC的核心组件DispatcherServlet进行访问
    			filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
    		}
    		finally {
    			super.finallyInvocation(token);
    		}
    		super.afterInvocation(token, null);
    	}
    }
    

    SpringSecurity 请求间共享认证信息

    一般认证成功后的用户信息是通过 Session 在多个请求之间共享,那么 Spring Security 中是如何实现将已认证的用户信息对象 Authentication 与 Session 绑定的?

           // 认证成功的处理
           protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
    			Authentication authResult) throws IOException, ServletException {
    		SecurityContextHolder.getContext().setAuthentication(authResult);
    		if (this.logger.isDebugEnabled()) {
    			this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));
    		}
    		this.rememberMeServices.loginSuccess(request, response, authResult);
    		if (this.eventPublisher != null) {
    			this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
    		}
    		this.successHandler.onAuthenticationSuccess(request, response, authResult);
    	}
    
    /**
     * 该类其实是对 ThreadLocal 的封装,存储 SecurityContext 对象
     */
    public class SecurityContextHolder {
    
            public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
    
            private static void initialize() {
    		if (!StringUtils.hasText(strategyName)) {
    			// 默认使用MODE_THREADLOCAL模式
    			strategyName = MODE_THREADLOCAL;
    		}
    		if (strategyName.equals(MODE_THREADLOCAL)) {
                            // 默认使用ThreadLocalSecurityContextHolderStrategy创建strategy,其内部使用ThreadLocal对SecurityContext进行存储
    			strategy = new ThreadLocalSecurityContextHolderStrategy();
    		}
    		else if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) {
    			strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
    		}
    		else if (strategyName.equals(MODE_GLOBAL)) {
    			strategy = new GlobalSecurityContextHolderStrategy();
    		}
    		else {
    			// Try to load a custom strategy
    			try {
    				Class<?> clazz = Class.forName(strategyName);
    				Constructor<?> customStrategy = clazz.getConstructor();
    				strategy = (SecurityContextHolderStrategy) customStrategy.newInstance();
    			}
    			catch (Exception ex) {
    				ReflectionUtils.handleReflectionException(ex);
    			}
    		}
    		initializeCount++;
    	}
    
    	public static void clearContext() {
                    // 清除当前线程对应的ThreadLocal<SecurityContext>
    		strategy.clearContext();
    	}
    
            public static SecurityContext getContext() {
                    // 需要注意,如果当前线程对应的ThreadLocal<SecurityContext>没有存储任何对象,会返回一个空的SecuriyContext对象
    		return strategy.getContext();
    	}
    
    	public static void setContext(SecurityContext context) {
                    // 设置当前线程对应的ThreadLocal<SecurityContext>
    		strategy.setContext(context);
    	}
    }
    
    final class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
    
    	private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>();
    
    	@Override
    	public SecurityContext getContext() {
                    // 当前线程对应的ThreadLocal<SecurityContext>没有存储任何对象,会返回一个空的SecuriyContext对象
    		SecurityContext ctx = contextHolder.get();
    		if (ctx == null) {
    			ctx = createEmptyContext();
    			contextHolder.set(ctx);
    		}
    		return ctx;
    	}
    }
    
    /**
     * 查看 SecurityContext 接口及其实现类 SecurityContextImpl,该类其实就是对 Authentication 的封装
     */
    public class SecurityContextImpl implements SecurityContext {
    
            private Authentication authentication;
    
    	public SecurityContextImpl() {
    	}
    
    	public SecurityContextImpl(Authentication authentication) {
    		this.authentication = authentication;
    	}
    
    }
    
    /**
     *前面提到过,在 UsernamePasswordAuthenticationFilter 过滤器认证成功之 后,会在认证成功的处理方法中将已认证的用户信息对象 Authentication 封装进 SecurityContext,并存入 SecurityContextHolder
     *之后,响应会通过 SecurityContextPersistenceFilter 过滤器,该过滤器的位置 在所有过滤器的最前面,请求到来先进它,响应返回最后一个通过它,所以在该过滤器中 处理已认证的用户信息对象 Authentication 与 Session 绑定
     *认证成功的响应通过 SecurityContextPersistenceFilter 过滤器时,会从 SecurityContextHolder 中取出封装了已认证用户信息对象 Authentication 的 SecurityContext,放进 Session 中。当请求再次到来时,请求首先经过该过滤器,该过滤 器会判断当前请求的 Session 是否存有 SecurityContext 对象,如果有则将该对象取出再次 放入 SecurityContextHolder 中,之后该请求所在的线程获得认证用户信息,后续的资源访 问不需要进行身份认证;当响应再次返回时,该过滤器同样从 SecurityContextHolder 取出 SecurityContext 对象,放入 Session 中
     */
    public class SecurityContextPersistenceFilter extends GenericFilterBean {
    
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    			throws IOException, ServletException {
    		doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
    	}
    
    	private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
    			throws IOException, ServletException {
    		// ensure that filter is only applied once per request
    		if (request.getAttribute(FILTER_APPLIED) != null) {
    			chain.doFilter(request, response);
    			return;
    		}
    		request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
    		if (this.forceEagerSessionCreation) {
    			HttpSession session = request.getSession();
    			if (this.logger.isDebugEnabled() && session.isNew()) {
    				this.logger.debug(LogMessage.format("Created session %s eagerly", session.getId()));
    			}
    		}
    		HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
                    // 请求到来时,检查当前Session中是否有SecurityContext对象,如果有从Session中取出,如果没有创建一个空的SecurityContext对象
    		SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
    		try {
                            // 将上述获得的SecurityContext对象放入SecurityContextHolder中
    			SecurityContextHolder.setContext(contextBeforeChainExecution);
    			if (contextBeforeChainExecution.getAuthentication() == null) {
    				logger.debug("Set SecurityContextHolder to empty SecurityContext");
    			}
    			else {
    				if (this.logger.isDebugEnabled()) {
    					this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", contextBeforeChainExecution));
    				}
    			}
                            // 进入下一个过滤器
    			chain.doFilter(holder.getRequest(), holder.getResponse());
    		}
    		finally {
                            // 响应返回时,从SecurityContextHolder中取出SecurityContext
    			SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
    			// 移除SecurityContextHolder中的SecurityContext
    			SecurityContextHolder.clearContext();
                            // 将取出的SecurityContext放到Session
    			this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
    			request.removeAttribute(FILTER_APPLIED);
    			this.logger.debug("Cleared SecurityContextHolder to complete request");
    		}
    	}
    }
    
  • 相关阅读:
    WCF Restful调用跨域解决方案
    [Asp.net]常见word,excel,ppt,pdf在线预览方案,有图有真相,总有一款适合你!
    人体呼吸信号的数据挖掘
    Spark编译及spark开发环境搭建
    诗两首------重庆项目出差有感
    eclipse安装和中文汉化,以及配置
    Querying CRM data with LINQ
    oracle pl/sql之在java中怎么调用oracle函数
    oracle pl/sql之oracle函数
    oracle pl/sql之java中调用oracle有参存储过程
  • 原文地址:https://www.cnblogs.com/ding-dang/p/14470334.html
Copyright © 2011-2022 走看看