SrpingSecurity的Session管理与退出登录
in JavaDevelop with 0 comment

SrpingSecurity的Session管理与退出登录

in JavaDevelop with 0 comment

单机session管理

到目前为止三个功能:

前两种使用表单提交方式完成,后一种使用oath授权完成;

虽然表现方式和处理流程不同,但是有一个共同点,认证后的用户信息是存放在session中的;

session超时

注意:server.session.timeout 已经过时了

server:
  port: 80
  servlet:
    session:
      timeout: 10s

重启测试,发现并没有超时;在以下源码中限制了最小为1分钟;

org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#configureSession

实现session超时提醒

session超时之后,再次访问进行一个提醒要怎么做呢?

cn.mrcode.imooc.springsecurity.securitybrowser.BrowserSecurityConfig#configure

配置下session失效跳转的url地址,这个地址需要我们实现。你可以做任何的业务逻辑;同时记得放行该地址,否则又被拦截授权了
.and()
.sessionManagement()
.invalidSessionUrl("/session/invalid")
cn.mrcode.imooc.springsecurity.securitybrowser.BrowserSecurityController#sessionInvalid
这里就打印下消息

@GetMapping("/session/invalid")
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public SimpleResponse sessionInvalid() {
    String message = "session失效";
    return new SimpleResponse(message);
}

实现session并发登录控制

// 配置地址或则策略类
.maximumSessions(1) //限制同一个用户只能有一个session登录
//.expiredUrl("/session/expired") // 也可以跳转到一个服务
.expiredSessionStrategy(new MySessionInformationExpiredStrategy())  // 失效后的策略。定制型更高,失效前的请求还能拿到

编写策略类

/**
 * session并发登录失效策略
 * @author : zhuqiang
 * @version : V1.0
 * @date : 2018/8/6 21:28
 */
public class MySessionInformationExpiredStrategy implements SessionInformationExpiredStrategy {
    @Override
    public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
        // 该对象能获取到访问失效前的url地址
        event.getResponse().setContentType("application/json;charset=UTF-8");
        event.getResponse().getWriter().write("session并发登录");
    }
}

测试:在不同浏览器登录,然后在最开始登录的浏览器中访问一个服务查看下;

实现session并发登录策略2

.maximumSessions(1) //限制同一个用户只能有一个session登录
.maxSessionsPreventsLogin(true)  // 当session达到最大后,阻止后登录的行为

测试会提示:“Maximum sessions of 1 for this principal exceeded”

代码重构,消除重复代码,可提供可配置功能

这里尝试自己看一遍视频,然后全程去思考如何提出来。不行的时候再看源码

重构的时候需要注意一个坑:

/** 当session达到最大值后,是阻止用户登录还是剔除掉已登录用户
 * fasle : 会走{@link cn.mrcode.imooc.springsecurity.securitybrowser.session.MySessionInformationExpiredStrategy}
 * true:会阻止登录,这个阻止登录的个性化消息没有设置,看源码的时候好像可以覆盖那个过滤器;设置为true会看到报错信息,然后就可以查看覆盖说明了
 * */
private boolean maxSessionsPreventsLogin;

集群session管理

spring Security 是基于session的安全框架。所以就会有这个问题

// 该项目用来做浏览器端的所以需要有session
    // 提供集群环境下的session管理,也没有被管理到,需要自己添加
    //如果在启动的时候报错,可以通过配置 yml文件中 spring: session: store-type: none
    compile('org.springframework.session:spring-session:1.3.3.RELEASE')

org.springframework.boot.autoconfigure.session.StoreType 标识支持的类型,
上面设置成none其实就是使用原生j2ee单机服务器session这种模式

为上面使用redis?

  1. 所有请求都要使用session
  2. 有过期时间,Redis天生支持

依赖:特别注意:spring-session:1.3.3.RELEASE在高版本的spring boot autoconfig中已经不支持了;
需要分开引用下面的包

// 该项目用来做浏览器端的所以需要有session
// 提供集群环境下的session管理,也没有被管理到,需要自己添加
//如果在启动的时候报错,可以通过配置 yml文件中 spring: session: store-type: none
//    compile('org.springframework.session:spring-session:1.3.3.RELEASE')
compile('org.springframework.session:spring-session-core')
compile('org.springframework.session:spring-session-data-redis')

开始改造

依赖添加之后,更改配置文件

spring:
  session:
    store-type: redis

访问登录页面:/imocc-signIn.html; 发现验证码图片无法显示,报错了。没有序列化

// 相关报错序列化问题的地方都记得修改
public class ImageCode extends ValidateCode implements Serializable{
    private static final long serialVersionUID = -703011095085705839L;
    private BufferedImage image;  // 看这里,是一个复杂的图片对象

怎么处理这个图片对象呢?考虑在存和取的地方下功夫

cn.mrcode.imooc.springsecurity.securitycore.validate.code.impl.AbstractValidateCodeProcessor#save

private void save(ServletWebRequest request, C validateCode) {
    // 不保存图片对象到redis session中,无法序列化
    // 因为在验证的时候不需要图片对象
    ValidateCode code = new ValidateCode(validateCode.getCode(), validateCode.getExpireTime());
    sessionStrategy.setAttribute(request, getSessionKey(), code);
}

测试

  1. 启动两个不同端口的项目
  2. 在同一个浏览器中
  3. 其中一个端口先登录 :localhost:80
  4. 然后新开一个页面直接打开另外一个端口访问 localhost:80/user/me

之前配置的所有功能正常,只是在session失效只允许一个登录这个功能,可能会有一点点的延迟

退出登录

默认退出处理逻辑

还记得以前登录的时候有一个默认的登录地址:/login,同样默认了一个退出/logout;
直接访问该地址:如果看到下面的报错,请检查 之前开发记住我的功能的配置

Mon Aug 06 23:55:25 CST 2018
There was an unexpected error (type=Internal Server Error, status=500).
PreparedStatementCallback; bad SQL grammar [delete from persistent_logins where username = ?]; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'imooc-demo.persistent_logins' doesn't exist
cn.mrcode.imooc.springsecurity.securitybrowser.BrowserSecurityConfig#persistentTokenRepository

记住我功能的配置,自动生成表结构;像我公司和家来回学习的人。有时候会忘记

解决之后,访问 /logout 发现跳转到了认证页面

http://localhost:8080/authentication/require?logout

上面的步骤,退出之后会重定向到登录页,我们的登录页是自定义的,处理授权前的连接,所以就跳转到这里了

cn.mrcode.imooc.springsecurity.securitycore.properties.SecurityConstants#DEFAULT_UNAUTHENTICATION_URL

退出常用基本配置

.logout()
                .logoutUrl("/singout")  // 退出请求路径
                .logoutSuccessUrl("/imocc-signOut.html") // 退出成功跳转到的地址
                .logoutSuccessHandler()  // 与logoutSuccessUrl互斥,有handler则logoutSuccessUrl失效
                .deleteCookies("JSESSIONID")

退出实现

.logout()
//                .logoutUrl("/singout")  // 退出请求路径
// 与logoutSuccessUrl互斥,有handler则logoutSuccessUrl失效
// 通过处理器增加配置了页面则跳转到页面,没有则输出json
.logoutSuccessHandler(logoutSuccessHandler)
.deleteCookies("JSESSIONID")

使用handler来处理退出逻辑

package cn.mrcode.imooc.springsecurity.securitybrowser.logout;

import cn.mrcode.imooc.springsecurity.securitybrowser.support.SimpleResponse;
import cn.mrcode.imooc.springsecurity.securitycore.properties.SecurityProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author : zhuqiang
 * @version : V1.0
 * @date : 2018/8/7 0:18
 */
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
    private org.slf4j.Logger logger = LoggerFactory.getLogger(getClass());

    private ObjectMapper objectMapper = new ObjectMapper();

    private SecurityProperties securityProperties;

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        // 当退出成功的时候,如果配置了一个页面,则跳转到页面,
        // 没有配置页面则打印session
        // 这里增加了一个属性,默认为空
        String signOutUrl = securityProperties.getBrowser().getSignOutUrl();
        if (StringUtils.isBlank(signOutUrl)) {
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(objectMapper.writeValueAsString(new SimpleResponse("退出成功")));
        } else {
            response.sendRedirect(signOutUrl);
        }
    }

    public SecurityProperties getSecurityProperties() {
        return securityProperties;
    }

    public void setSecurityProperties(SecurityProperties securityProperties) {
        this.securityProperties = securityProperties;
    }
}

配置处理器的初始化bean

cn.mrcode.imooc.springsecurity.securitybrowser.BrowserSecurityBeanConfig#logoutSuccessHandler

@Bean
@ConditionalOnMissingBean(LogoutSuccessHandler.class)
public LogoutSuccessHandler logoutSuccessHandler() {
    MyLogoutSuccessHandler myLogoutSuccessHandler = new MyLogoutSuccessHandler();
    myLogoutSuccessHandler.setSecurityProperties(securityProperties);
    return myLogoutSuccessHandler;
}