天天看点

Spring Security系列(8)-会话管理及登录退出详解

会话管理

用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保证在会话中。会话就是系统为了保持当前 用户的登录状态所提供的机制,常见的有基于session方式、基于token方式等。

基于session的认证方式由Servlet规范定制,服务端要存储session信息需要占用内存资源,客户端需要支持 cookie;基于token的方式则一般不需要服务端存储token,并且不限制客户端的存储方式。如今移动互联网时代 更多类型的客户端需要接入系统,系统多是采用前后端分离的架构进行实现,所以基于token的方式更适合。

session

用户认证成功后,在服务端生成用户相关的数据保存在session(当前会话)中,发给客户端的 sesssion_id 存放到 cookie 中,这样用户客户端请求时带上 session_id 就可以验证服务器端是否存在 session 数 据,以此完成用户的合法校验,当用户退出系统或session过期销毁时,客户端的session_id也就无效了。基于session的认证方式目前不常用了,而且不适用于现在分布式微服务场景。

Spring Security系列(8)-会话管理及登录退出详解

token

,用户认证成功后,服务端生成一个token发给客户端,客户端可以放到 cookie 或 localStorage 等存储中,每次请求时带上 token,服务端收到token通过验证后即可确认用户身份。

Spring Security系列(8)-会话管理及登录退出详解

Spring Security会话管理

用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保存在会话中。spring security提供会话管理,认证通过后将身份信息放入SecurityContextHolder上下文,SecurityContext与当前线程进行绑定,方便获取用户身份。

SecurityContext context = SecurityContextHolder.getContext();
        Authentication authentication = context.getAuthentication();
        MyUser principal = (MyUser) authentication.getPrincipal();
           

可以查到到登录后,在浏览器中保存了session_id。

Spring Security系列(8)-会话管理及登录退出详解

session创建策略

Spring Security下的枚举SessionCreationPolicy类,管理session的创建策略。

/**
 * Specifies the various session creation policies for Spring Security.
 * 指定 Spring Security 的各种会话创建策略
 */
public enum SessionCreationPolicy {
	ALWAYS,
	NEVER,
	IF_REQUIRED,
	STATELESS
}
           

可以通过以下选项准确控制会话何时创建以及Spring Security如何与之交互:

机制 描述
ALWAYS 如果没有session存在就创建一个
NEVER SpringSecurity 将不会创建Session,但是如果应用中其他地方创建了Session,那么Spring Security将会使用它。
IF_REQUIRED 如果需要就创建一个Session(默认)登录时
STATELESS SpringSecurity将绝对不会创建Session,也不使用Session

通过以下配置方式对该选项进行配置:

@Override protected void configure(HttpSecurity http) throws Exception { 
	http.sessionManagement() 
	.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) 
}
           

默认情况下,Spring Security会为每个登录成功的用户会新建一个Session,就是ifRequired 。

若选用never,则指示Spring Security对登录成功的用户不创建Session了,但若你的应用程序在某地方新建了 session,那么Spring Security会用它的。

若使用stateless,则说明Spring Security对登录成功的用户不会创建Session了,你的应用程序也不会允许新建 session。并且它会暗示不使用cookie,所以每个请求都需要重新进行身份验证。这种无状态架构适用于REST API 及其无状态认证机制。

配置项

SessionManagementConfigurer是会话管理配置类,它提供了很多配置项及方法处理session。

public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
		extends AbstractHttpConfigurer<SessionManagementConfigurer<H>, H> {

	private final SessionAuthenticationStrategy DEFAULT_SESSION_FIXATION_STRATEGY = createDefaultSessionFixationProtectionStrategy();

	private SessionAuthenticationStrategy sessionFixationAuthenticationStrategy = this.DEFAULT_SESSION_FIXATION_STRATEGY;

	private SessionAuthenticationStrategy sessionAuthenticationStrategy;

	private SessionAuthenticationStrategy providedSessionAuthenticationStrategy;

	// 设置session id无效时要应用的策略InvalidSessionStrategy。如果设置了该属性,浏览器端提供了无效的session id时,服务器端会调用该策略对象。
	// 通常情况下,这里也会是一个跳转策略对象SimpleRedirectInvalidSessionStrategy。
	private InvalidSessionStrategy invalidSessionStrategy;

	private SessionInformationExpiredStrategy expiredSessionStrategy;

	private List<SessionAuthenticationStrategy> sessionAuthenticationStrategies = new ArrayList<>();

	private SessionRegistry sessionRegistry;
	// 设置每个用户的最大并发会话数量。此方法返回一个ConcurrencyControlConfigurer,这也是一个安全配置器,设置每个用户会话数量超出单用户最大会话并发数时如何处理。
	private Integer maximumSessions;

	private String expiredUrl;

	private boolean maxSessionsPreventsLogin;
	// 设置会话创建策略SessionCreationPolicy。如果不设置,则会尝试使用公共对象中设置的SessionCreationPolicy。
	// 如果公共对象中也没有设置会话创建策略,则使用缺省的会话创建策略SessionCreationPolicy.IF_REQUIRED。
	private SessionCreationPolicy sessionPolicy;
	// 调用该方法设置属性enableSessionUrlRewriting.如果enableSessionUrlRewriting属性被设置为true,
	// 使用HttpServletResponse#encodeRedirectURL(String)/HttpServletResponse#encodeURL(String)时,
	// 允许将HTTP session信息重写到URL中。该方法对应的属性enableSessionUrlRewriting缺省为false,不允许Http session重写到URL。
	private boolean enableSessionUrlRewriting;

	// 设置session id无效时的跳转URL。如果设置了该属性,浏览器端提供了无效的session id时,服务器端会将其跳转到所设置的URL。
	private String invalidSessionUrl;
	// 定义SessionAuthenticationStrategy抛出异常时要跳转的URL。如果未设置该属性,SessionAuthenticationStrategy抛出异常时,会返回402给客户端。
	private String sessionAuthenticationErrorUrl;
	// 定义SessionAuthenticationStrategy抛出异常时要应用的认证失败处理器AuthenticationFailureHandler。
	// 如果未设置该属性,SessionAuthenticationStrategy抛出异常时,会返回402给客户端。
	private AuthenticationFailureHandler sessionAuthenticationFailureHandler;
	// -----省略大量源码
}
           

会话超时时间

可以再sevlet容器中设置Session的超时时间,如下设置Session有效期为3600s

server:
  servlet:
    session:
      timeout: 3600s
           

跳转路径

session超时之后,可以通过Spring Security 设置跳转的路径,expired指session过期,invalidSession指传入的sessionid无效。

http.sessionManagement() 
.expiredUrl("/login‐view?error=EXPIRED_SESSION") 
.invalidSessionUrl("/login‐view?error=INVALID_SESSION");
           

安全会话

我们可以使用httpOnly和secure标签来保护我们的会话cookie: httpOnly:如果为true,那么浏览器脚本将无法访问cookie secure:如果为true,则cookie将仅通过HTTPS连接发送 spring boot 配置文件:

server:
  servlet:
    session:
      timeout: 3600s
      cookie:
        http-only: true
        secure: true
           

登录退出

之前我们了解到,spring security提供了登录页面,那么是否也提供了登录退出页面呢?答案是肯定的,我们只要输入http://localhost:9090/logout,就出现了登录退出页面。

Spring Security系列(8)-会话管理及登录退出详解

当点击了退出后,跳转到了登录页。

Spring Security系列(8)-会话管理及登录退出详解

登录退出的配置类为LogoutConfigurer,在HttpSecurity 配置加载时,会创建该配置项。

// HttpSecurity 代码片段
	public LogoutConfigurer<HttpSecurity> logout() throws Exception {
		return getOrApply(new LogoutConfigurer<>());
	}

           

LogoutConfigurer核心配置如下:

public final class LogoutConfigurer<H extends HttpSecurityBuilder<H>>
		extends AbstractHttpConfigurer<LogoutConfigurer<H>, H> {
	// 退出时要执行的处理器,可以有多个, contextLogoutHandler 总是最后一个
	private List<LogoutHandler> logoutHandlers = new ArrayList<>();

	private SecurityContextLogoutHandler contextLogoutHandler = new SecurityContextLogoutHandler();

	// 成功退出登录跳转路径
	private String logoutSuccessUrl = "/login?logout";
	// 退出登录成处理器
	private LogoutSuccessHandler logoutSuccessHandler;
	// 退出登录页面路径
	private String logoutUrl = "/logout";

	private RequestMatcher logoutRequestMatcher;
	// 是否允许所有
	private boolean permitAll;
	// 是否自定义成功登出
	private boolean customLogoutSuccess;
}
           

可以自定义退出功能:

@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() //... .
	and() .logout() 
 	.logoutUrl("/logout") 
  	.logoutSuccessUrl("/login‐view?logout") 
   	.logoutSuccessHandler(logoutSuccessHandler) 
    .addLogoutHandler(logoutHandler)
    .invalidateHttpSession(true); 
}
           

当退出操作出发时,将发生:

  • 使HTTP Session 无效
  • 清除 SecurityContextHolder
  • 跳转到 /login-view?logout

LogoutHandler接口定义了退出的相关操作。

public interface LogoutHandler {

	/**
	 * Causes a logout to be completed. The method must complete successfully.
	 *
	 * @param request        the HTTP request
	 * @param response       the HTTP response
	 * @param authentication the current principal details
	 *                       导致注销完成。该方法必须成功完成
	 */
	void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication);
}
           

LogoutHandler的实现类如下图:

Spring Security系列(8)-会话管理及登录退出详解
  • PersistentTokenBasedRememberMeServices 基于持久化token的RememberMe功能的相关清理
  • TokenBasedRememberMeService 基于token的RememberMe功能的相关清理
  • CookieClearingLogoutHandler 退出时Cookie的相关清理
  • CsrfLogoutHandler 负责在退出时移除csrfToken
  • SecurityContextLogoutHandler 退出时SecurityContext的相关清理