• SessionManagementConfigurer和SecurityContextConfigurer


    SessionManagementConfigurer

    介绍SessionManagementConfigurer的ini和configure方法执行流程。

    init

    init方法主要根据session创建策略来构建SecurityContextRepository以及创建SessionAuthenticationStrategy存放在HttpSecurity上下文配置中。

    public void init(H http) {
    	SecurityContextRepository securityContextRepository = http.getSharedObject(SecurityContextRepository.class);
    	boolean stateless = isStateless();// 1
    	if (securityContextRepository == null) {
    		if (stateless) {// 2
    			http.setSharedObject(SecurityContextRepository.class, new NullSecurityContextRepository());
    		}
    		else {
    			HttpSessionSecurityContextRepository httpSecurityRepository = new HttpSessionSecurityContextRepository();
    			httpSecurityRepository.setDisableUrlRewriting(!this.enableSessionUrlRewriting);
    			httpSecurityRepository.setAllowSessionCreation(isAllowSessionCreation());
    			AuthenticationTrustResolver trustResolver = http.getSharedObject(AuthenticationTrustResolver.class);
    			if (trustResolver != null) {
    				httpSecurityRepository.setTrustResolver(trustResolver);
    			}
    			http.setSharedObject(SecurityContextRepository.class, httpSecurityRepository);
    		}
    	}
    	RequestCache requestCache = http.getSharedObject(RequestCache.class);
    	if (requestCache == null) { // 3
    		if (stateless) {
    			http.setSharedObject(RequestCache.class, new NullRequestCache());
    		}
    	}
    	http.setSharedObject(SessionAuthenticationStrategy.class, getSessionAuthenticationStrategy(http));// 4
    	http.setSharedObject(InvalidSessionStrategy.class, getInvalidSessionStrategy());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    1、判断是否是否无状态的,如果想指定无状态可进行如下配置:

    RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
    http.requestMatcher(endpointsMatcher)
    .sessionManagement()
    .sessionCreationPolicy(SessionCreationPolicy.STATELESS).
    
    • 1
    • 2
    • 3
    • 4

    2、如果是无状态的则设置SecurityContextRepository为NullSecurityContextRepository,否则SecurityContextRepository设置为HttpSessionSecurityContextRepository
    3、如果requestCache 为空(初次执行为null)并且stateless为true(说明是无状态的)则设置RequestCache为NullRequestCache,
    4、getSessionAuthenticationStrategy(http)作用是通过AutowireBeanFactoryObjectPostProcessor把ChangeSessionIdAuthenticationStrategy实例(SessionAuthenticationStrategy的实现类)定义成Bean,作为CompositeSessionAuthenticationStrategy(List delegateStrategies)构造函数的delegateStrategies的一个元素对CompositeSessionAuthenticationStrategy进行实例化,然后把CompositeSessionAuthenticationStrategy实例通过AutowireBeanFactoryObjectPostProcessor定义成bean,最后把CompositeSessionAuthenticationStrategy实例返回。然后把key(SessionAuthenticationStrategy.class),value(CompositeSessionAuthenticationStrategy实例)存放在H(HttpSecurity)#sharedObject属性Map集合中。

    configure

    主要是为创建SessionManagementFilter实例,并把实例设置到过滤器链中。

    public void configure(H http) {
    	SecurityContextRepository securityContextRepository = http.getSharedObject(SecurityContextRepository.class);// 1
    	SessionManagementFilter sessionManagementFilter = new SessionManagementFilter(securityContextRepository,
    			getSessionAuthenticationStrategy(http));// 2
    	if (this.sessionAuthenticationErrorUrl != null) {// 3
    		sessionManagementFilter.setAuthenticationFailureHandler(
    				new SimpleUrlAuthenticationFailureHandler(this.sessionAuthenticationErrorUrl));
    	}
    	InvalidSessionStrategy strategy = getInvalidSessionStrategy();// 4
    	if (strategy != null) {
    		sessionManagementFilter.setInvalidSessionStrategy(strategy);
    	}
    	AuthenticationFailureHandler failureHandler = getSessionAuthenticationFailureHandler();// 5
    	if (failureHandler != null) {
    		sessionManagementFilter.setAuthenticationFailureHandler(failureHandler);
    	}
    	AuthenticationTrustResolver trustResolver = http.getSharedObject(AuthenticationTrustResolver.class);// 6
    	if (trustResolver != null) {
    		sessionManagementFilter.setTrustResolver(trustResolver);
    	}
    	sessionManagementFilter = postProcess(sessionManagementFilter);// 7
    	http.addFilter(sessionManagementFilter);// 8
    	if (isConcurrentSessionControlEnabled()) {
    		ConcurrentSessionFilter concurrentSessionFilter = createConcurrencyFilter(http);
    
    		concurrentSessionFilter = postProcess(concurrentSessionFilter);
    		http.addFilter(concurrentSessionFilter);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    1、如果你配置的sessionCreationPolicy是无状态的那么此时获取的SecurityContextRepository为NullSecurityContextRepository,否则为HttpSessionSecurityContextRepository对应init(H http) 方法的第2步。
    2、创建SessionManagementFilter对象,初始化其属性securityContextRepository、和sessionAuthenticationStrategy,这里需要注意是sessionAuthenticationStrategy不在是一个值,在init(H http)中设置了sessionAuthenticationStrategy为CompositeSessionAuthenticationStrategy,但CsrfConfigurer#configure方法执行早于SessionManagementConfigurer#configure的方法,在CsrfConfigurer#configure方法执行时对sessionAuthenticationStrategy进行了添加操作添加一个CsrfAuthenticationStrategy,如下:
    在这里插入图片描述
    在这里插入图片描述
    3、4、5 如果你对sessionManagement()相关属性进行自定义才会进行设置,否则不设置
    在这里插入图片描述
    6、和3、4、5一样都需要自定义才会存在,否则为null
    7、通过AutowireBeanFactoryObjectPostProcessor把sessionManagementFilter实例定义成bean
    8、把sessionManagementFilter过滤器添加到H(HttpSecurity)的过滤器列表(filters)中

    SecurityContextConfigurer

    SecurityContextConfigurer是相比SessionManagementConfigurer只对configure方法进行了重写。

    configure

    主要是为创建SecurityContextPersistenceFilter 实例,并把实例设置到过滤器链中。

    @Override
    @SuppressWarnings("unchecked")
    public void configure(H http) {
    	SecurityContextRepository securityContextRepository = http.getSharedObject(SecurityContextRepository.class);// 1
    	if (securityContextRepository == null) {
    		securityContextRepository = new HttpSessionSecurityContextRepository(); // 2
    	}
    	SecurityContextPersistenceFilter securityContextFilter = new SecurityContextPersistenceFilter(
    			securityContextRepository);// 3
    	SessionManagementConfigurer<?> sessionManagement = http.getConfigurer(SessionManagementConfigurer.class); // 4
    	SessionCreationPolicy sessionCreationPolicy = (sessionManagement != null)
    			? sessionManagement.getSessionCreationPolicy() : null;
    	if (SessionCreationPolicy.ALWAYS == sessionCreationPolicy) {
    		securityContextFilter.setForceEagerSessionCreation(true);// 5
    	}
    	securityContextFilter = postProcess(securityContextFilter);// 6
    	http.addFilter(securityContextFilter);// 7
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    1、从http(HttpSecurity)中获取SecurityContextRepository.class对象。在SessionManagementConfigurer的init方法执行时已经针对是否是无状态设置了SessionManagementConfigurer对应的实际对象。
    2、如果securityContextRepository为null就默认创建一个securityContextRepository为HttpSessionSecurityContextRepository
    3、创建SecurityContextPersistenceFilter对象
    4、从http(HttpSecurity)中获取SessionManagementConfigurer对象
    5、如果SessionManagementConfigurer的SessionCreationPolicy等于ALWAYS那么对SecurityContextPersistenceFilter的属性forceEagerSessionCreation设置值为true
    6、作用是通过AutowireBeanFactoryObjectPostProcessor把SecurityContextPersistenceFilter实例定义成Bean
    7、把SecurityContextPersistenceFilter过滤器添加到H(HttpSecurity)的过滤器列表(filters)中

    sessionManagementFilter和SecurityContextPersistenceFilter

    我们先看一下Spring Authorization Server 授权服务涉及到的相关过滤器列表:
    在这里插入图片描述

    然后我们在看上面提到了sessionManagementFilter和SecurityContextPersistenceFilter完成什么工作。

    SecurityContextPersistenceFilter

    SecurityContextPersistenceFilter过滤器执行早于sessionManagementFilter。

    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);
    	SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);// 1
    	try {
    		SecurityContextHolder.setContext(contextBeforeChainExecution);// 2
    		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());// 3
    	}
    	finally {
    		SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();// 4
    		// Crucial removal of SecurityContextHolder contents before anything else.
    		SecurityContextHolder.clearContext();// 5
    		this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());// 6
    		request.removeAttribute(FILTER_APPLIED);
    		this.logger.debug("Cleared SecurityContextHolder to complete request");
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    1、 this.repo.loadContext(holder);根据当时设置的SessionCreationPolicy返回不同的实例,如果SessionCreationPolicy为STATELESS则返回的是NullSecurityContextRepository,否则为HttpSessionSecurityContextRepository。加载context,此处返回的内容为null。
    2、把contextBeforeChainExecution存放在SecurityContextHolder的Context中
    3、执行过滤器链列表的下一个过滤器的doFilter方法。如果现在是一个登录操作那么简单的流程大致如下:
    在这里插入图片描述
    4、清空SecurityContextHolder的context
    5、执行repo对象的saveContext方法,如果repo为NullSecurityContextRepository则不作任何操作,如果repo为HttpSessionSecurityContextRepository则对应如下操作:

    @Override
    public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) {
    	SaveContextOnUpdateOrErrorResponseWrapper responseWrapper = WebUtils.getNativeResponse(response,
    			SaveContextOnUpdateOrErrorResponseWrapper.class);// 1
    	Assert.state(responseWrapper != null, () -> "Cannot invoke saveContext on response " + response
    			+ ". You must use the HttpRequestResponseHolder.response after invoking loadContext");
    	responseWrapper.saveContext(context);// 2
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    1、对SaveContextOnUpdateOrErrorResponseWrapper 进行实例化
    2、执行NullSecurityContextRepository或者HttpSessionSecurityContextRepository的saveContext方法。NullSecurityContextRepository#saveContext不作任何操作。
    HttpSessionSecurityContextRepository#saveContext执行如下

    @Override
    protected void saveContext(SecurityContext context) {
    	final Authentication authentication = context.getAuthentication();
    	HttpSession httpSession = this.request.getSession(false);
    	String springSecurityContextKey = HttpSessionSecurityContextRepository.this.springSecurityContextKey;
    	// See SEC-776
    	if (authentication == null
    			|| HttpSessionSecurityContextRepository.this.trustResolver.isAnonymous(authentication)) {
    		if (httpSession != null && this.authBeforeExecution != null) {
    			// SEC-1587 A non-anonymous context may still be in the session
    			// SEC-1735 remove if the contextBeforeExecution was not anonymous
    			httpSession.removeAttribute(springSecurityContextKey);
    			this.isSaveContextInvoked = true;
    		}
    		if (this.logger.isDebugEnabled()) {
    			if (authentication == null) {
    				this.logger.debug("Did not store empty SecurityContext");
    			}
    			else {
    				this.logger.debug("Did not store anonymous SecurityContext");
    			}
    		}
    		return;
    	}
    	httpSession = (httpSession != null) ? httpSession : createNewSessionIfAllowed(context, authentication);// 1
    	// If HttpSession exists, store current SecurityContext but only if it has
    	// actually changed in this thread (see SEC-37, SEC-1307, SEC-1528)
    	if (httpSession != null) {
    		// We may have a new session, so check also whether the context attribute
    		// is set SEC-1561
    		if (contextChanged(context) || httpSession.getAttribute(springSecurityContextKey) == null) {
    			httpSession.setAttribute(springSecurityContextKey, context);// 2
    			this.isSaveContextInvoked = true;
    			if (this.logger.isDebugEnabled()) {
    				this.logger.debug(LogMessage.format("Stored %s to HttpSession [%s]", context, httpSession));
    			}
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    1、在这里你会发现会判断httpSession是否为空,为空则创建一个session
    2、把context作为value,“SPRING_SECURITY_CONTEXT”作为key保存在session中,如果是一个登录操作,那么context中存放的Authentication就是UsernamePasswordAuthenticationToken。
    在这里插入图片描述
    在这里插入图片描述

    涉及问题

    这里存在一个问题,如果你是前后端的项目,你想着用session是无状态的,然后使用JWT作为验证的token,这是没问题的,你可以在Spring Authorization Server的登录成功后生成自定义的JWT,返回给前端,但如果你的Spring Authorization Server同时支持授权码功能,它需要资源所有者进行登录后在生成授权码,如果你使用session是无状态那么它不会把SecurityContext(对应到登录SecurityContext#Authentication会存放对象为UsernamePasswordAuthenticationToken)存放在session中,那么即使你登录成功后,去请求授权端点获取授权码,也是无法获取授权码的,因为在授权端点过滤器中,会执行如下逻辑:

    Authentication principal = SecurityContextHolder.getContext().getAuthentication();
    
    • 1

    此时获取的Authentication 为null,说明没有进行身份验证,最后给前端提示错误“Full authentication is required to access this resource”。所以说Spring Authorization Server如果是session无状态策略,涉及到一些需要登录然后在处理后边逻辑的都是无法使用的。这里感觉Spring Authorization Server多少是有点问题的对session无状态策略的支持。

    sessionManagementFilter

    此过滤器对请求参数中不包含属性FILTER_APPLIED (“__spring_security_session_mgmt_filter_applied”)才会进行执行。

    private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
    			throws IOException, ServletException {
    	if (request.getAttribute(FILTER_APPLIED) != null) {
    		chain.doFilter(request, response);
    		return;
    	}
    	request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
    	if (!this.securityContextRepository.containsContext(request)) {
    		Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    		if (authentication != null && !this.trustResolver.isAnonymous(authentication)) {
    			// The user has been authenticated during the current request, so call the
    			// session strategy
    			try {
    				this.sessionAuthenticationStrategy.onAuthentication(authentication, request, response);// 1
    			}
    			catch (SessionAuthenticationException ex) {
    				// The session strategy can reject the authentication
    				this.logger.debug("SessionAuthenticationStrategy rejected the authentication object", ex);
    				SecurityContextHolder.clearContext();
    				this.failureHandler.onAuthenticationFailure(request, response, ex);
    				return;
    			}
    			// Eagerly save the security context to make it available for any possible
    			// re-entrant requests which may occur before the current request
    			// completes. SEC-1396.
    			this.securityContextRepository.saveContext(SecurityContextHolder.getContext(), request, response);
    		}
    		else {
    			// No security context or authentication present. Check for a session
    			// timeout
    			if (request.getRequestedSessionId() != null && !request.isRequestedSessionIdValid()) {
    				if (this.logger.isDebugEnabled()) {
    					this.logger.debug(LogMessage.format("Request requested invalid session id %s",
    							request.getRequestedSessionId()));
    				}
    				if (this.invalidSessionStrategy != null) {
    					this.invalidSessionStrategy.onInvalidSessionDetected(request, response);
    					return;
    				}
    			}
    		}
    	}
    	chain.doFilter(request, response);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    1、在Authentication不为null并且非匿名的情况下,对CompositeSessionAuthenticationStrategy中的属性delegateStrategies进行遍历,我们知道delegateStrategies中存放着ChangeSessionIdAuthenticationStrategy,ChangeSessionIdAuthenticationStrategy会对session进行处理。

    @Override
    public void onAuthentication(Authentication authentication, HttpServletRequest request,
    		HttpServletResponse response) {
    	boolean hadSessionAlready = request.getSession(false) != null;
    	if (!hadSessionAlready && !this.alwaysCreateSession) {
    		// Session fixation isn't a problem if there's no session
    		return;
    	}
    	// Create new session if necessary
    	HttpSession session = request.getSession();
    	if (hadSessionAlready && request.isRequestedSessionIdValid()) {
    		String originalSessionId;
    		String newSessionId;
    		Object mutex = WebUtils.getSessionMutex(session);
    		synchronized (mutex) {
    			// We need to migrate to a new session
    			originalSessionId = session.getId();
    			session = applySessionFixation(request);
    			newSessionId = session.getId();
    		}
    		if (originalSessionId.equals(newSessionId)) {
    			this.logger.warn("Your servlet container did not change the session ID when a new session "
    					+ "was created. You will not be adequately protected against session-fixation attacks");
    		}
    		else {
    			if (this.logger.isDebugEnabled()) {
    				this.logger.debug(LogMessage.format("Changed session id from %s", originalSessionId));
    			}
    		}
    		onSessionChange(originalSessionId, session, authentication);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    alwaysCreateSession属性为true的种情况下,如果尚未创建会话,将创建会话,否则不会对session有任何操作。

  • 相关阅读:
    在VSCode中调试其他软件执行的python文件
    基于NodeJS实现企业微信机器人推送
    git学习入门8——git-revert
    go 语言爬虫库goQuery 的详细使用(知乎日报详情页解析示例)
    Spark SQL将Hive表中的数据写入到MySQL数据库中
    打印星堆(for循环嵌套实例)
    链表——移除链表元素
    python-itheima
    Windows 安装 Nginx, 本地访问vue打包项目
    考生都难哭了,用 Python 分析了一下,这里才是高考地狱级难度
  • 原文地址:https://blog.csdn.net/m13012606980/article/details/126427921