会话管理
用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保证在会话中。会话就是系统为了保持当前 用户的登录状态所提供的机制,常见的有基于session方式、基于token方式等。
基于session的认证方式由Servlet规范定制,服务端要存储session信息需要占用内存资源,客户端需要支持 cookie;基于token的方式则一般不需要服务端存储token,并且不限制客户端的存储方式。如今移动互联网时代 更多类型的客户端需要接入系统,系统多是采用前后端分离的架构进行实现,所以基于token的方式更适合。
session
用户认证成功后,在服务端生成用户相关的数据保存在session(当前会话)中,发给客户端的 sesssion_id 存放到 cookie 中,这样用户客户端请求时带上 session_id 就可以验证服务器端是否存在 session 数 据,以此完成用户的合法校验,当用户退出系统或session过期销毁时,客户端的session_id也就无效了。基于session的认证方式目前不常用了,而且不适用于现在分布式微服务场景。

token
,用户认证成功后,服务端生成一个token发给客户端,客户端可以放到 cookie 或 localStorage 等存储中,每次请求时带上 token,服务端收到token通过验证后即可确认用户身份。
Spring Security会话管理
用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保存在会话中。spring security提供会话管理,认证通过后将身份信息放入SecurityContextHolder上下文,SecurityContext与当前线程进行绑定,方便获取用户身份。
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
MyUser principal = (MyUser) authentication.getPrincipal();
可以查到到登录后,在浏览器中保存了session_id。
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,就出现了登录退出页面。
当点击了退出后,跳转到了登录页。
登录退出的配置类为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的实现类如下图:
- PersistentTokenBasedRememberMeServices 基于持久化token的RememberMe功能的相关清理
- TokenBasedRememberMeService 基于token的RememberMe功能的相关清理
- CookieClearingLogoutHandler 退出时Cookie的相关清理
- CsrfLogoutHandler 负责在退出时移除csrfToken
- SecurityContextLogoutHandler 退出时SecurityContext的相关清理