介绍SessionManagementConfigurer的ini和configure方法执行流程。
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集合中。
主要是为创建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是相比SessionManagementConfigurer只对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)中
我们先看一下Spring Authorization Server 授权服务涉及到的相关过滤器列表:
然后我们在看上面提到了sessionManagementFilter和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无状态策略的支持。
此过滤器对请求参数中不包含属性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有任何操作。