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、判断是否是否无状态的,如果想指定无状态可进行如下配置:

RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
http.requestMatcher(endpointsMatcher)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).

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、如果你配置的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、从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、 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、对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、在这里你会发现会判断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();

此时获取的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、在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);
	}
}

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

Spring SecuritySpring Session的结合可以提供更强大的安全性和会话管理功能。Spring Security提供了身份验证和授权的功能,而Spring Session提供了跨多个请求的会话管理。 在结合使用时,可以使用Spring Session的`SessionRepositoryFilter`将会话存储在Redis或数据库等外部存储中,并在Spring Security中使用`SessionManagementConfigurer`配置会话管理。以下是一个简单的示例: ``` @Configuration @EnableWebSecurity @EnableRedisHttpSession public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private RedisConnectionFactory redisConnectionFactory; @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/public/**").permitAll() .anyRequest().authenticated() .and() .formLogin().loginPage("/login").permitAll() .and() .logout().permitAll(); } @Bean public RedisOperationsSessionRepository sessionRepository() { return new RedisOperationsSessionRepository(redisConnectionFactory); } @Bean public SessionManagementConfigurer<HttpSecurity> sessionManagementConfigurer() { return new SessionManagementConfigurer<HttpSecurity>() { @Override public void configure(HttpSecurity http) throws Exception { http.sessionManagement() .sessionAuthenticationStrategy(sessionAuthenticationStrategy()) .maximumSessions(1) .maxSessionsPreventsLogin(true) .sessionRegistry(sessionRegistry()); } }; } @Bean public SessionAuthenticationStrategy sessionAuthenticationStrategy() { return new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry()); } @Bean public SessionRegistry sessionRegistry() { return new SpringSessionBackedSessionRegistry<>(new RedisOperationsSessionRepository(redisConnectionFactory)); } } ``` 在上面的配置中,`@EnableRedisHttpSession`注解启用了Spring Session,并使用`RedisOperationsSessionRepository`将会话存储在Redis中。`SessionManagementConfigurer`配置了会话管理,包括最大并发会话数和会话注册表。 需要注意的是,Spring Session默认使用一个名为`SESSION`的Cookie来跟踪会话。如果需要自定义Cookie名称和其他会话属性,可以使用`@EnableRedisHttpSession`的`cookieName`和`redisNamespace`属性进行配置。 在使用Spring SecuritySpring Session结合时,还需要确保在各个请求中正确地暴露会话信息。可以使用Spring Session的`SessionRepositoryFilter`来完成这个任务,例如: ``` @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private RedisConnectionFactory redisConnectionFactory; @Bean public FilterRegistrationBean<SessionRepositoryFilter> sessionRepositoryFilterRegistration() { FilterRegistrationBean<SessionRepositoryFilter> registration = new FilterRegistrationBean<>(); registration.setFilter(new SessionRepositoryFilter(sessionRepository())); registration.addUrlPatterns("/*"); registration.setOrder(Ordered.HIGHEST_PRECEDENCE); return registration; } @Bean public RedisOperationsSessionRepository sessionRepository() { return new RedisOperationsSessionRepository(redisConnectionFactory); } } ``` 在上面的配置中,`SessionRepositoryFilter`将会话信息暴露在所有请求中。需要注意的是,`SessionRepositoryFilter`应该注册为具有最高优先级的过滤器,以确保会话数据在其他过滤器之前暴露。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值