zoukankan      html  css  js  c++  java
  • Spring Security -- session管理(转载)

    用户登录成功后,信息保存再服务器的session中,并返回给用户一个sessionid,sessionid是一个会话的key,浏览器第一次访问服务器会在服务器端生成一个session,当用户再次请求时,将携带该sessionId,如果在服务器中能够找到该sessionid,则表示用户登录成功。这节将会学习如何管理session。这节将会在Spring Security -- 添加图形验证码(转载)的基础上继续扩展。

    一、session超时设置

    1、配置session

    session超时时间也就是用户登录的有效时间。要设置session超时时间很简单,只需要在配置文件application.yml中添加:

    #session设置超时
    server:
      servlet:
        session:
          timeout: 3600

    单位为秒,通过上面的配置,session的有效期为一个小时。值得注意的是,session的最小有效期为60秒,也就是说即使你设置为小于60秒的值,其有效期还是为60秒。

    session失效后,刷新页面后将跳转到认证页面,我们可以再添加一些配置,自定义session失效后的一些行为。在Spring Security中配置session管理器,并配置session失效后要跳转的URL:

     @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class) // 添加图片验证码校验过滤器
                    .addFilterBefore(smsCodeFilter, UsernamePasswordAuthenticationFilter.class)  // 添加手机短信验证码校验过滤器
                    .authorizeRequests()    // 授权配置
                    .antMatchers("/code/image","/code/sms","/session/invalid")
                    .permitAll()       // 无需认证的请求路径
                    .anyRequest()       // 任何请求
                    .authenticated()    //都需要身份认证
                    .and()
                    .formLogin()         // 或者httpBasic()
                    .loginPage("/login")  // 指定登录页的路径
                    //我们的form表单action是将请求提交到/login/mobile页面,而在Spring Security中配置的 .loginProcessingUrl("/login") 值为/login,这两者为什么不一样呢?这样做的目的是通过指定Spring Security中的UsernamePasswordAuthenticationFilter的拦截目标为post请求/login,从而使得该过滤器不会拦截/login/mobile请求;那么针对/login/mobile请求我们会仿照UsernamePasswordAuthenticationFilter定义自己的过滤器,然后对其进行认证;
                    .loginProcessingUrl("/login")  // 指定自定义form表单提交请求的路径
                    .successHandler(authenticationSucessHandler)    // 处理登录成功
                    .failureHandler(authenticationFailureHandler) // 处理登录失败
                    // 必须允许所有用户访问我们的登录页(例如未验证的用户,否则验证流程就会进入死循环)
                    // 这个formLogin().permitAll()方法允许所有用户基于表单登录访问/login这个page。
                    .permitAll()
    //                .and()
    //                .rememberMe()
    //                .tokenRepository(persistentTokenRepository)  // 配置 token 持久化仓库
    //                .tokenValiditySeconds(3600)      // remember 过期时间,单为秒
    //                .userDetailsService(userDetailsService)   // 处理自动登录逻辑
    //                .and()
    //                .logout()
    //                .permitAll()
                    .and()
                    .sessionManagement()    //添加session管理器
                    .invalidSessionUrl("/session/invalid")   //Session失效后跳转到这个链接
                    .and()
                    //默认都会产生一个hiden标签 里面有安全相关的验证 防止请求伪造 这边我们暂时不需要 可禁用掉
                    .csrf().disable()
                    .apply(smsAuthenticationConfig); // 将短信验证码认证配置加到 Spring Security 中  添加一个安全配置其到http的configurers集合
        }

    2、SessionController

    上面配置了session失效后跳转到/session/invalid,并且将这个URL添加到了免认证路径中。

    在包com.goldwind.conmtroller下创建类SessionController,添加一个方法,映射该请求:

    package com.goldwind.controller;
    
    import org.springframework.http.HttpStatus;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.ResponseStatus;
    
    /**
     * @Author: zy
     * @Description: session路由
     * @Date: 2020/2/16
     */
    public class SessionController {
        @GetMapping("/session/invalid")
        @ResponseStatus(HttpStatus.UNAUTHORIZED)
        public String sessionInvalid(){
            return "session已失效,请重新认证";
        }
    }

    3、测试

    为了演示,我们将session的超时时间设置为最小值60秒,重启项目,访问http://127.0.0.1:8080/index,并成功认证后输出:

    等待60秒并刷新页面:

    可看到请求跳转到了我们自定义的/session/invalidURL上。

    4、session并发控制

    session并发控制可以控制一个账号同一时刻最多能登录多少个。我们在Spring Security配置中继续添加session相关配置:

     @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class) // 添加图片验证码校验过滤器
                    .addFilterBefore(smsCodeFilter, UsernamePasswordAuthenticationFilter.class)  // 添加手机短信验证码校验过滤器
                    .authorizeRequests()    // 授权配置
                    .antMatchers("/code/image","/code/sms","/session/invalid")
                    .permitAll()       // 无需认证的请求路径
                    .anyRequest()       // 任何请求
                    .authenticated()    //都需要身份认证
                    .and()
                    .formLogin()         // 或者httpBasic()
                    .loginPage("/login")  // 指定登录页的路径
                    //我们的form表单action是将请求提交到/login/mobile页面,而在Spring Security中配置的 .loginProcessingUrl("/login") 值为/login,这两者为什么不一样呢?这样做的目的是通过指定Spring Security中的UsernamePasswordAuthenticationFilter的拦截目标为post请求/login,从而使得该过滤器不会拦截/login/mobile请求;那么针对/login/mobile请求我们会仿照UsernamePasswordAuthenticationFilter定义自己的过滤器,然后对其进行认证;
                    .loginProcessingUrl("/login")  // 指定自定义form表单提交请求的路径
                    .successHandler(authenticationSucessHandler)    // 处理登录成功
                    .failureHandler(authenticationFailureHandler) // 处理登录失败
                    // 必须允许所有用户访问我们的登录页(例如未验证的用户,否则验证流程就会进入死循环)
                    // 这个formLogin().permitAll()方法允许所有用户基于表单登录访问/login这个page。
                    .permitAll()
    //                .and()
    //                .rememberMe()
    //                .tokenRepository(persistentTokenRepository)  // 配置 token 持久化仓库
    //                .tokenValiditySeconds(3600)      // remember 过期时间,单为秒
    //                .userDetailsService(userDetailsService)   // 处理自动登录逻辑
    //                .and()
    //                .logout()
    //                .permitAll()
                    .and()
                    //默认都会产生一个hiden标签 里面有安全相关的验证 防止请求伪造 这边我们暂时不需要 可禁用掉
                    .csrf().disable()
                    .apply(smsAuthenticationConfig) // 将短信验证码认证配置加到 Spring Security 中  添加一个安全配置其到http的configurers集合
                    .and()
                    .sessionManagement()    //添加session管理器
                    .invalidSessionUrl("/session/invalid")   //Session失效后跳转到这个链接
                    .maximumSessions(1)
                    .expiredSessionStrategy(sessionInformationExpiredStrategy);
        }

    maximumSessions配置了最大session并发数量为1个,如果admin这个账户登录后,在另一个客户端也使用admin账户登录,那么第一个使用admin登录的账户将会失效,类似于一个先入先出队列。sessionInformationExpiredStrategy配置了session在并发下失效后的处理策略,这里为我们在com.goldwind.config包下自定义策略CustomSessionInformationExpiredStrategy :

    package com.goldwind.config;
    
    import org.springframework.http.HttpStatus;
    import org.springframework.security.web.session.SessionInformationExpiredEvent;
    import org.springframework.security.web.session.SessionInformationExpiredStrategy;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * @Author: zy
     * @Description: 配置了Session在并发下失效后的处理策略
     * @Date: 2020/2/16
     */
    @Component
    public class CustomSessionInformationExpiredStrategy implements SessionInformationExpiredStrategy {
    
        @Override
        public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
            HttpServletResponse response = event.getResponse();
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().write("您的账号已经在别的地方登录,当前登录已失效。如果密码遭到泄露,请立即修改密码!");
        }
    }

    为了演示这个效果,我们先将session超时时间设置久一点,比如3600秒,然后重启项目,在Chrome里使用admin账户登录。

    登录成功后,在IE上也是用admin账户登录,登录成功后回到Chrome,刷新页面,效果如下所示:

    除了后者将前者踢出的策略,我们也可以控制当session达到最大有效数的时候,不再允许相同的账户登录。

    要实现这个功能只需要在上面的配置中添加:

    ......
    .and()
        .sessionManagement() // 添加 Session管理器
        .invalidSessionUrl("/session/invalid") // Session失效后跳转到这个链接
        .maximumSessions(1)
        .maxSessionsPreventsLogin(true)
        .expiredSessionStrategy(sessionInformationExpiredStrategy)
        .and()
    ......

    重启系统,在Chrome上登录admin账户后,在IE上尝试使用admin账户登录:

    可以看到登录受限。在实际开发中,发现session并发控制只对Spring Security默认的登录方式——账号密码登录有效,而像短信验证码登录,社交账号登录并不生效,解决方案可以开源项目https://github.com/wuyouzhuguli/FEBS-Security

    5、session集群处理

    session集群听着高大上,其实实现起来很简单。当我们登录成功后,用户认证的信息存储在session中,而这些session默认是存储在运行运用的服务器上的,比如Tomcat,netty等。当应用集群部署的时候,用户在A应用上登录认证了,后续通过负载均衡可能会把请求发送到B应用,而B应用服务器上并没有与该请求匹配的认证session信息,所以用户就需要重新进行认证。要解决这个问题,我们可以把session信息存储在第三方容器里(如Redis集群),而不是各自的服务器,这样应用集群就可以通过第三方容器来共享session了。

    我们引入Redis和Spring Session依赖:

    <dependency>
        <groupId>org.springframework.session</groupId>
        <artifactId>spring-session</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    然后在yml中配置Session存储方式为Redis:

    spring:
      session:
        store-type: redis

    为了方便,Redis配置采用默认配置即可。开启Redis,并且启动两个应用实例,一个端口为8080,另一个端口为9090。

    我们现在8080端口应用上登录:

    然后访问9090端口应用的主页:

    可以看到登录也是生效的。这就实现了集群化session管理。

    6、其它操作

    SessionRegistry包含了一些使用的操作Session的方法,比如:

    踢出用户(让Session失效):

    String currentSessionId = request.getRequestedSessionId();
    sessionRegistry.getSessionInformation(sessionId).expireNow();

    获取所有Session信息:

    List<Object> principals = sessionRegistry.getAllPrincipals();

    参考文章:

    [1] Spring Security Session管理(转载)

  • 相关阅读:
    C#中结构与类的区别
    LINQ中的聚合操作以及常用方法
    慎用const关键字
    .NET Framework想要实现的功能
    System.Object的一些方法
    你真的了解.NET中的String吗?
    C#学习要点一
    2012年 新的开始!
    java web服务器中的 request和response
    java Thread编程(二)sleep的使用
  • 原文地址:https://www.cnblogs.com/zyly/p/12316099.html
Copyright © 2011-2022 走看看