zoukankan      html  css  js  c++  java
  • 单体应用的session & 分布式Session & JWT

    一、单体应用的session

    1、查询用户名和密码;
    2、使用 Security 的 PasswordEncoder 去校验用户名和密码;
    3、将 用户信息放入 session 中;
    (1)定义一个 BaseController,获取请求和响应;
    public class BaseController {
    
        public HttpServletRequest getRequest(){
            return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        }
    
        public HttpServletResponse getResponse(){
            return ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getResponse();
        }
    
        public HttpSession getHttpSession(){
            return getRequest().getSession();
        }
    }

    (2)放入用户信息到 session 中;

    getHttpSession().setAttribute("member", member);

    4、只有登录后,接口才可以去访问;

    自定义拦截器,拦截其他接口的登录;实现 HandlerInterceptor 接口,重写 preHandle 方法。
    @Slf4j
    public class AuthInterceptorHandler implements HandlerInterceptor {
        public final static String GLOBAL_JWT_USER_INFO="jwttoken:usermember:info";
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            log.info("进入前置拦截器");
            if(!ObjectUtils.isEmpty(request.getSession().getAttribute("member"))){
                return true;
            }
            print(response,"您没有权限访问!请先登录.");
            return false;
        }
    
        protected void print(HttpServletResponse response,String message) throws Exception{
            /**
             * 设置响应头
             */
            response.setHeader("Content-Type","application/json");
            response.setCharacterEncoding("UTF-8");
            String result = new ObjectMapper().writeValueAsString(CommonResult.forbidden(message));
            response.getWriter().print(result);
        }
    }

    6、将拦截器加入到配置中;

    (1)配置文件的配置;

    #登录拦截验证
    member:
      auth:
        shouldSkipUrls:
         - /sso/**
         - /home/**

    (2)加载配置文件中的配置到 Set 集合中;

    @Data
    @ConfigurationProperties(prefix = "member.auth")
    public class NoAuthUrlProperties {
        private LinkedHashSet<String> shouldSkipUrls;
    }

    (3)拦截器的配置;

    @EnableConfigurationProperties(NoAuthUrlProperties.class)
    @Configuration
    public class IntercepterConfiguration implements WebMvcConfigurer {
    
        @Autowired
        private NoAuthUrlProperties noAuthUrlProperties;
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            //注册拦截器
            registry.addInterceptor(authInterceptorHandler())
                    .addPathPatterns("/**")
                    .excludePathPatterns(new ArrayList<>(noAuthUrlProperties.getShouldSkipUrls()));
        }
    
        @Bean
        public AuthInterceptorHandler authInterceptorHandler(){
            return new AuthInterceptorHandler();
        }
    }

    单体应用部署多个,上面的代码就会出现问题,每次去访问的不同实例的时候,就需要再重新登录;、

    可以使用spring session 解决以上问题。

    二、使用 spring session 实现分布式Session

    1、引入 spring session 的 jar 包;

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

    2、配置文件中配置存储类型;可以使用 redis、mongodb、mysql;

    spring:
      session:
        store-type: redis

    3、开启spring session的配置;(以下session的超时时间设置为 3600s)

    @EnableRedisHttpSession(maxInactiveIntervalInSeconds = 3600)
    public class RedisHttpSessionConfiguration {
        /**
         * 引入分布式会话体系,会话内容存储在Redis当中,原理请阅读源码
         */
    }

     三、Spring Session的简介

    Spring Session 使用过滤器 Filter 实现了session 的共享;

    (1)Spring Session 内部不是每次都去从 Redis 中获取Session,它的本地缓存中也会保留一份 session;本地缓存中的 session 过期找不到的时候才会去连接 Redis 查询;

    (2)本地缓存 session 的过期时间是根据配置文件中的 spring.session.redis.cleanup-cron 的表达式配置来处理的;

    (3)不是每次去调用 getSession() 或 setAttribute() 方法的时候都会将 Redis 中的超时时间重置,是在过滤器调用链走完之后,再将 Redis 中的超时时间重置(finally 语句中的 warppedRequest.commitSession() ),保证每次请求只将 Redis 中的过期时间重置一次;

     

    @EnableRedisHttpSession 注解通过Import,引入了RedisHttpSessionConfiguration配置类。该配置类通过@Bean注解,向Spring容器中注册了一个SessionRepositoryFilterSessionRepositoryFilter的依赖关系:SessionRepositoryFilter --> SessionRepository --> RedisTemplate --> RedisConnectionFactory)。

    Spring Session源码参考

    (1)spring-session(一)揭秘: https://www.cnblogs.com/lxyit/p/9672097.html

    (2)利用spring session解决共享Session问题: https://blog.csdn.net/patrickyoung6625/article/details/45694157

    四、JWT

    1、JWT介绍

      全称JSON Web Token,用户会话信息存储在客户端浏览器,它定义了一种紧凑且自包含的方式,用于在各方之间以JSON对象进行安全传输信息。这些信息可以通过对称/非对称方式进行签名,防止信息被篡改。
     

    2、JWT数据格式(JWT = Header.Payload.Signature)

    (1)Header:头部
    {
        alg: "HS256",
        typ: "JWT"
    }
    • alg属性表示签名的算法,默认算法为HS256,可以自行别的算法。
    • typ属性表示这个令牌的类型,JWT令牌就为JWT。

    Header = Base64(上方json数据)

    (2)Payload:载荷

    存放用户的信息,如创建时间、过期时间;例如:

    {    
        "userid":"test",
        "created":1489079981393,
        "exp":1489684781
    }

     Payload = Base64(data)  //可以被反编码,所以不要放入敏感信息

    (3)Signature:签名

     使用头部中存储的签名算法去签名;

    Signature = HMACSHA256(base64UrlEncode(header)  +  "."  + base64UrlEncode(payload), secret)

    secret为加密的密钥,密钥存在服务端;

    3、JWT身份认证流程

    (1)用户提供用户名和密码登录;
    (2)服务器校验用户是否正确,如正确,就返回token给客户端,此token可以包含用户信息;
    (3)客户端存储token,可以保存在cookie或者local storage;
    (4)客户端以后请求时,都要带上这个token,一般放在请求头中;
    (5)服务器判断是否存在token,并且解码后就可以知道是哪个用户;
    (6)服务器这样就可以返回该用户的相关信息了;

    4、JWT的使用

    (1)引入JWT的工具包
    <!-- json web token 工具 -->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
    </dependency>
    (2)定义JWT的生成和解析工具类

     a. 配置文件

    #jwt config
    jwt:
      tokenHeader: Authorization #JWT存储的请求头
      secret: mall-member-secret #JWT加解密使用的密钥
      expiration: 604800 #JWT的超期限时间(60*60*24)
      tokenHead: Bearer #JWT负载中拿到开头

    b. 读取配置文件

    @Data
    @ConfigurationProperties(prefix = "jwt")
    @Component
    public class JwtProperties {
        private String tokenHeader;
        private String secret;
        private Long expiration;
        private String tokenHead;
    }

    c. JWT的生成 和 解析

    public class JwtKit {
        @Autowired
        private JwtProperties jwtProperties;
    
        /**
         * 创建jwtToken
         * @param member
         * @return
         */
        public String generateJwtToken(UmsMember member){
            Map<String,Object> claims = new HashMap<>();
    
            claims.put("sub",member.getUsername());
            claims.put("createdate",new Date());
            claims.put("id",member.getId());
            claims.put("memberLevelId",member.getMemberLevelId());
    
            return Jwts.builder()
                    .addClaims(claims) 
                    .setExpiration(new Date(System.currentTimeMillis() + jwtProperties.getExpiration()*1000))
                    .signWith(SignatureAlgorithm.HS256,jwtProperties.getSecret())
                    .compact();
        }
        
        /**
         * 解析jwt
         * @param jwtToken
         * @return
         * @throws BusinessException
         */
        public Claims parseJwtToken(String jwtToken) throws BusinessException {
            Claims claims = null;
            try {
                claims=Jwts.parser()
                        .setSigningKey(jwtProperties.getSecret())
                        .parseClaimsJws(jwtToken)
                        .getBody();
            } catch (ExpiredJwtException e) {
                throw new BusinessException("JWT验证失败:token已经过期");
            } catch (UnsupportedJwtException e) {
                throw new BusinessException("JWT验证失败:token格式不支持");
            } catch (MalformedJwtException e) {
                throw new BusinessException("JWT验证失败:无效的token");
            } catch (SignatureException e) {
                throw new BusinessException("JWT验证失败:无效的token");
            } catch (IllegalArgumentException e) {
                throw new BusinessException("JWT验证失败:无效的token");
            }
            return claims;
        }
    
    }

    d. 将工具类注入到容器中

    @Configuration
    public class SecurityConfiguration {
        @Bean
        public JwtKit jwtKit(){
            return new JwtKit();
        }
    }

    e. 自定义拦截器,解析对应的JWT token;

    @Slf4j
    public class AuthInterceptorHandler implements HandlerInterceptor {
        @Autowired
        private JwtKit jwtKit;
    
        @Autowired
        private JwtProperties jwtProperties;
    
        public final static String GLOBAL_JWT_USER_INFO="jwttoken:usermember:info";
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            log.info("进入前置拦截器");
            String message = null;
            String authorization = request.getHeader(jwtProperties.getTokenHeader());
            log.info("authorization:"+authorization);
            //校验token
    
            if(!StringUtils.isEmpty(authorization)
                    && authorization.startsWith(jwtProperties.getTokenHead())){
                String authToken = authorization.substring(jwtProperties.getTokenHead().length());
                //解析jwt-token
                Claims claims = null;
                try {
                    claims = jwtKit.parseJwtToken(authToken);
                    if(claims != null){
                        request.setAttribute(GLOBAL_JWT_USER_INFO,claims);
                        return true;
                    }
                } catch (BusinessException e) {
                    log.error(message = (e.getMessage()+":"+authToken));
                }
            }
            print(response,"您没有权限访问!请先登录.");
            return false;
        }
    
        protected void print(HttpServletResponse response,String message) throws Exception{
            //设置响应头
            response.setHeader("Content-Type","application/json");
            response.setCharacterEncoding("UTF-8");
            String result = new ObjectMapper().writeValueAsString(CommonResult.forbidden(message));
            response.getWriter().print(result);
        }
    }

    f.  将自定义的拦截器加入到配置中

    @Configuration
    public class IntercepterConfiguration implements WebMvcConfigurer {
        @Bean
        public AuthInterceptorHandler authInterceptorHandler(){
            return new AuthInterceptorHandler();
        }
    }

    五、session 和 JWT 的比较

    JWT

    (1)用户信息存储在客户端(storage,cookie);

    (2)JWT泄露之后,只要没有过期,都可以被使用;即使修改完密码后,泄露出去的JWT仍然可以使用;

    (3)JWT的安全性低于Session,JWT的使用的时候先要进行数据脱敏的处理;

    Session(有状态)

    (1)只在 cookie 中存储一个 jSessionId,用户信息存储在服务端,根据 jSessionId 去查询对应的用户信息;

    (2)安全性较高;

    Session 和 JWT 可以结合一起使用;

  • 相关阅读:
    Windows Server 2008 R2 Enterprise 安装.NET Framework 4.0
    layer弹层content写错导致div复制了一次,导致id失效 $().val() 获取不到dispaly:none div里表单的值
    IIS 注册.NET Framework 4.0 命令
    记一次神秘又刺激的装机
    HTTP Error 503. The service is unavailable.
    找到多个与名为“Home”的控制器匹配的类型。
    Discuz论坛广告横幅大图在百度app内无法显示,百度app默认开启了广告屏蔽
    解决Antimalware Service Executable CPU,内存占用高的问题
    Discuz 部署,500 – 内部服务器错误。 您查找的资源存在问题,因而无法显示。
    IIS部署网站只有首页能访问,其他链接失效/运行.net+Access网站-可能原因:IIS未启用32位应用程序模式
  • 原文地址:https://www.cnblogs.com/yufeng218/p/15652413.html
Copyright © 2011-2022 走看看