SpringSecurity SecurityContextPersistenceFilter源码 和企业级Redis使用思想

之前几篇我们基本上讲述了SpringSecurity 登录认证和授权认证的实现和源码分析。我们大致清楚了 :

  1. SpringSecurity  登录认证原理。
  2. 如果自定义登录认证流程
  3. 授权验证的流程原理

今天我们分析下整个认证过程和企业级redis使用的思想,是为了解决什么问题,以及怎么解决的?

我们简单回顾下 SpringSecurity  流程。

  1. 用户登录: 输入用户名 / 密码
  2. JwtLoginFilter (自定义的UsernamePasswordAuthenticationFilter): 拦截login接口,对用户登录信息进行认证。认证成功:信息存入上下文。
  3. 再次请求:进入到FilterSecurityInterceptor进行授权认证,通过获取上线文SecurityContext 中的用户权限信息和资源所需的权限对比认证。认证通过则访问资源,否则返回异常信息。

简单就可以发现上面流程从头到尾都少不了一个关键东西上下文SecurityContext。它是由SecurityContextPersistenceFilter 过滤器处理的,那我们就先看下它是怎么个实现逻辑:

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) {
			// ensure that filter is only applied once per request
			chain.doFilter(request, response);
			return;
		}


		final boolean debug = logger.isDebugEnabled();
		request.setAttribute(FILTER_APPLIED, Boolean.TRUE);

		if (forceEagerSessionCreation) {
			HttpSession session = request.getSession();

			if (debug && session.isNew()) {
				logger.debug("Eagerly created session: " + session.getId());
			}
		}

		HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
				response);
        // 中获取上下文
		SecurityContext contextBeforeChainExecution = repo.loadContext(holder);

		try {
            // 将上下文SecurityContextHolder
			SecurityContextHolder.setContext(contextBeforeChainExecution);

			chain.doFilter(holder.getRequest(), holder.getResponse());

		}
		finally {
       
			SecurityContext contextAfterChainExecution = SecurityContextHolder
					.getContext();
            // 结束认证之后,将 SecurityContextHolder清除
			SecurityContextHolder.clearContext();
            // SecurityContextRepository 存入session
			repo.saveContext(contextAfterChainExecution, holder.getRequest(),
					holder.getResponse());
			request.removeAttribute(FILTER_APPLIED);

			if (debug) {
				logger.debug("SecurityContextHolder now cleared, as request processing completed");
			}
		}
	}

讲解下什么意思:

请求API的时候进行以下步骤:

1. 通过该方法 SecurityContextRepository#loadContext(holder)去获取当前登录用户的上下文。它的实现类有2个:HttpSessionSecurityContextRepository 和 NullSecurityContextRepository

HttpSessionSecurityContextRepository :从session中获取上下文。

NullSecurityContextRepository:直接创建一个空的上下文。(当设置session为无状态时会使用他)

2. 将获取到的上下文set给 SecurityContextHolder#setContext。SecurityContextHolder 他是我们整个流程获取上下文数据的类,所以在一开始就通过session获取到上下文放到SecurityContextHolder 中,以便在后续认证流程中使用。

3. 直接 doFilter 下一个过滤器调用,也就是进入验证流程。

4. 请求认证结束后清除 SecurityContextHolder,并将上下存有上下文 SecurityContextRepository存到session中。

通过上面流程我们清楚了几个点:

SecurityContextHolder 是整个认证过程上下文信息的持有者,但是认证结束后会被清除。

上下文信息由SecurityContextRepository管理每次请求都会事先从session获取上下文或者直接为空上下文,给后续使用,如果确实登录过了上下文就有信息,否则后续验证不过关。

每次请求认证结束后,会清除SecurityContextHolder ,并存到session中。下次来还是从session获取。


但是对于企业级项目来说,这也欠缺的太多了:

1. 只要用户登录之后,访问任何api都是从session获取当前信息,从而api本身安全性低。我们所需要的是每个api都应该是无状态的,要求你每次api的请求都要进行安全认证(token)。

2. 用户信息存入session,那么对用过多的登录认证,session会越来越庞大,内存吃不消啊。

3. session是基于jvm内存的,服务被重启就没得了。

所以我们需要基于缓存存储token数据比如redis,大致的思路:

首先关闭springsecurity session管理,要求每一个请求要被验证, 实现一个过滤器对请求头中token进行验证。如果验证通过,则封装一个 authenticationToken给上下文,供后续认证使用。如果不通过,此时的上下文是没有数据的,后续的过滤器自然会拦截掉。

清晰思路:(每次请求都是这个流程)

从redis拿上下文  ---->  放入SecurityContextHolder ------> 用完清除 --->  从redis拿上下文

      用户认证后会把用户信息(上下文)存入redis,并返回一个JWT令牌(令牌内有redis的key),之后所有请求都需要验证的这个令牌访问,验证过滤器一开始就会提取JWT中的key,并从redis获取用户信息上下文:

获取不到-- >未登录,直接拦截;

获取到了--> 说明该用户确实登录了,但是最好还是需要校验一下token的有效性 ---> 一切都通过,则将redis中获取到的用户信息(上下文)存入SecurityContextHolder中。 这样后面的授权认证工作就可以顺利获取信息进行。 

当然了没带token的自然一开始就给直接拦截

注意:

1. 记得关闭session管理

2. 在登录认证之后我们就要把用户信息存入redis,作为缓存。

3. springsecurity  session关闭后springsecurity 会跳过所有的 filter chain:HttpSessionSecurityContextRepositorySessionManagementFilterRequestCacheFilter

你可能感兴趣的:(spring源码,互联网,计算机常识,redis)