在执行认证过程之前,Spring Security将运行SecurityContextPersistenceFilter过滤器负责存储安请求之间的全上下文,
上下文根据策略进行存储,默认为HttpSessionSecurityContextRepository ,其使用http session作为存储器。
对于session管理,有三种:
- session超时处理:session有效的时间,超时后删除
- session并发控制:同个用户登录,是强制退出前一个登录,还是禁止后一个登录。
- 集群session管理:默认session是放在单个服务器的单个应用里,在集群中,会出现在一个节点应用登录后,session只能在该节点使用。另一个节点不能使用其他节点的session,还会需要登录,所以需要集群共用一个session
2.1、session超时
设置Session的超时,很简单,只需要在配置文件application.yml配置即可,如下为设置50秒:
Springboot2.0前的版本:
spring:
session:
timeout: 50
Springboot2.0后的版本:
server: servlet: session: timeout: 50
上面设置Session失效时间为50s,
实际源码TomcatEmbeddedServletContainerFactory类内部会取1分钟。源码内部转成分钟,然后设置给tomcat原生的StandardContext,所以一般设置为60秒的整数倍。
2.2、session超时处理
可以直接配置跳转,但是如果是前后端分离的话就不适合配置跳转的方式
2.2.1、超时跳转URL
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
@Override
protected void configure(HttpSecurity http) throws Exception {
// session无效时跳转的url
http.sessionManagement().invalidSessionUrl("/session/invalid");
http.authorizeRequests()
// 需要放行条跳转的url
.antMatchers("/session/invalid").permitAll()
.anyRequest().authenticated()
}
}
}
2.2.2、超时处理器
session无效时的处理策略,优先级比上面的高
抽象类
多个session处理类,定义抽象类
public abstract class AbstractSessionStrategy {
private final Logger logger = LoggerFactory.getLogger(getClass());
/**
* 跳转的url
*/
private String redirectUrl;
/**
* 重定向策略
*/
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
/**
* 跳转前是否创建新的session
*/
private boolean createNewSession = true;
public AbstractSessionStrategy(String url) {
Assert.isTrue(UrlUtils.isValidRedirectUrl(url), "url must start with '/' or with 'http(s)'");
this.redirectUrl = url;
}
/**
* session 无效处理
*
* @param request
* @param response
*
* @throws IOException
*/
protected void onSessionInvalid(HttpServletRequest request, HttpServletResponse response) throws IOException {
if(createNewSession) {
request.getSession();
}
String sourceUrl = request.getRequestURI();
String targetUrl;
if(StringUtils.endsWithIgnoreCase(sourceUrl, ".html")) {
targetUrl = destinationUrl + ".html";
logger.info("session失效,跳转到" + targetUrl);
redirectStrategy.sendRedirect(request, response, targetUrl);
} else {
String message = ResponseData.failure(CommResponseEnum.USER1023);
if(isConcurrency()) {
message = ResponseData.failure(CommResponseEnum.USER1024);
}
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(message);
}
}
/**
* session失效是否是并发导致的
*
* @return
*/
protected boolean isConcurrency() {
return false;
}
public void setCreateNewSession(boolean createNewSession) {
this.createNewSession = createNewSession;
}
}
实现类
定义超时处理类,因为使用了构造类,所以无法使用@Component注解直接注入spring,
需要在配置类中进行注入
public class MyInvalidSessionStrategy extends AbstractSessionStrategy implements InvalidSessionStrategy {
public MyInvalidSessionStrategy(String invalidSessionUrl) {
super(invalidSessionUrl);
}
@Override
public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response) throws IOException {
onSessionInvalid(request, response);
}
}
配置类
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@ConditionalOnMissingBean(InvalidSessionStrategy.class)
public InvalidSessionStrategy invalidSessionStrategy() {
return new MyInvalidSessionStrategy("/index");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 设置session无效处理策略
http.sessionManagement().invalidSessionStrategy(invalidSessionStrategy());
http.authorizeRequests()
.antMatchers("/session/invalid").permitAll()
.anyRequest().authenticated()
}
}
2.3、session并发控制
默认下,我们可以在不同浏览器同时登录同一个用户,这样就会保存了多个Session
而有时,我们需要只能在一处地方登录,其他地方的登录就让前一个失效或不能登录。
2.3.1、后登录致前登录失效(踢下线)
在一个浏览器登录后,再到另一个浏览器登录,再回到前一个登录刷新页面,登录失效。
只需要设置最大session数为1即可
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
// 设置session无效处理策略
.invalidSessionStrategy(invalidSessionStrategy)
// 设置同一个用户只能有一个登陆session
.maximumSessions(1);
http.authorizeRequests()
.anyRequest().authenticated();
}
}
上面设置maximumSessions设置为1后,只能有一个登录Session,
多个登录,后一个会把前一个登录的Sesson失效。
我们也可以自定义失效返回信息,有两种
1、失效跳转URL
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.invalidSessionStrategy(invalidSessionStrategy)
.maximumSessions(1)
// 其他地方登录session失效处理URL
.expiredUrl("/login");
http.authorizeRequests()
.anyRequest().authenticated();
}
}
2、失效处理器
配置过期策略
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@ConditionalOnMissingBean(SessionInformationExpiredStrategy.class)
public SessionInformationExpiredStrategy sessionInformationExpiredStrategy() {
return new MyExpiredSessionStrategy(securityProperties.getBrowser().getSession().getSessionInvalidUrl());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.invalidSessionStrategy(invalidSessionStrategy)
.maximumSessions(1)
// 其他地方登录session失效处理策略
.expiredSessionStrategy(sessionInformationExpiredStrategy());
http.authorizeRequests()
.anyRequest().authenticated()
}
}
抽象类
实现类
定义超时处理类,因为使用了构造类,所以无法使用@Component注解直接注入spring,
需要在配置类中进行注入
public class MyExpiredSessionStrategy extends AbstractSessionStrategy implements SessionInformationExpiredStrategy {
public MyExpiredSessionStrategy(String invalidSessionUrl) {
super(invalidSessionUrl);
}
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException {
onSessionInvalid(event.getRequest(), event.getResponse());
}
@Override
protected boolean isConcurrency() {
return true;
}
}
2.3.2、前登录禁后登录(已登录禁止再次登录)
有时,我们在一个地方登录正在操作,不能被打断,这时就要禁止在其他地方登录导致当前的登录Session失效
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@ConditionalOnMissingBean(SessionInformationExpiredStrategy.class)
public SessionInformationExpiredStrategy sessionInformationExpiredStrategy() {
return new MyExpiredSessionStrategy(securityProperties.getBrowser().getSession().getSessionInvalidUrl());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.invalidSessionStrategy(invalidSessionStrategy)
.maximumSessions(1)
// 设置为true,即禁止后面其它人的登录
.maxSessionsPreventsLogin(true)
.expiredSessionStrategy(sessionInformationExpiredStrategy());
http.authorizeRequests()
.anyRequest().authenticated()
}
}
禁止后登录后,可以通过如下方式判断异常并进行用户通知
在自定义的认证异常处理器中进行处理
@Slf4j
@Component("myAuthenticationFailureHandler")
public class MyAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
@Autowired
private SecurityProperties securityProperties;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
log.info("登录失败");
if(LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())) {
if (exception instanceof SessionAuthenticationException){
response.getWriter().write("用户已在其它地方登录,禁止当前登录...");
}
response.setContentType("application/json;charset=UTF-8");
if(exception.getMessage().equals("坏的凭证")) {
response.getWriter().write("账号或密码有误!");
} else {
response.getWriter().write(exception.getMessage());
}
} else {
super.onAuthenticationFailure(request, response, exception);
}
}
}
2.4、session共享
2.5、Logout注销
2.5.1、配置注销接口
配置注销接口和注销成功跳转接口
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.logout()
// 退出登录的url, 默认为/logout
.logoutUrl("/logout2")
// 退出成功跳转URL,注意该URL不需要权限验证
.logoutSuccessUrl("/logout/success").permitAll()
}
}
2.5.2、配置注销处理器
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@ConditionalOnMissingBean(LogoutSuccessHandler.class)
public LogoutSuccessHandler logoutSuccessHandler() {
return new MyLogoutSuccessHandler(securityProperties.getBrowser().getSignOutUrl());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.logout()
// 退出登录的url, 默认为/logout
.logoutUrl("/logout2")
// 退出成功跳转URL,注意该URL不需要权限验证,所有加.permitAll
//.logoutSuccessUrl("/logout/success").permitAll()
//退出登录成功处理器
.logoutSuccessHandler(logoutSuccessHandler())
}
}
实现
@Slf4j
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
private String signOutUrl;
public MyLogoutSuccessHandler(String signOutUrl) {
this.signOutUrl = signOutUrl;
}
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
log.info("退出成功");
if(StringUtils.isBlank(signOutUrl)) {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(JSON.toJSONString("退出成功"));
} else {
response.sendRedirect(request.getContextPath() + signOutUrl);
}
}
}
2.5.3、退出成功删除Cookie
默认退出后不会删除Cookie。可配置退出后删除:
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.logout()
// 退出登录的url, 默认为/logout
.logoutUrl("/logout2")
// 退出成功跳转URL,注意该URL不需要权限验证,所有加.permitAll
//.logoutSuccessUrl("/logout/success").permitAll()
//退出登录成功处理器
.logoutSuccessHandler(logoutSuccessHandler)
// 退出登录删除指定的cookie
.deleteCookies("JSESSIONID")
}
}