Java | 分布式——JWT加密

一、分布式Session共享

Session是服务器用来保存用户操作的一系列会话信息,由Web容器进行管理。单机情况下,不存在Session共享的情况,分布式情况下,如果不进行Session共享会出现请求落到不同机器要重复登录的情况:在一些需要用户登录的网站下,如果因为用提交的请求给到了不同的服务器进行处理,而需要让用户重复登录,这是一件非常扯蛋的操作。所以必须让所有服务器都能准确判断请求属于哪个用户。

一般来说解决Session共享有以下几种方案:

  1. Tomcat广播风暴(占用资源,不推荐)

  2. redis统一存储token(推荐)

  3. JWT加密

 

二、JWT简介

本文主要介绍JWT加密的方法,JWT(Json web token)是通过HMAC算法或者RSA的公钥密钥签名生成携带用户信息的token,将token存放在客户端中(可以存储在cookie,localstorage和sessionStorage中),客户端每次发送请求,都携带token,服务端通过解密来获取用户信息。

  • 优点:

    • token包含用户的基本信息,避免再次查库;

    • token存储在客户端,不占用服务器资源。

  • 缺点:

    • token经过base64编码,可以被解码,因此不能包含用户敏感信息(用户权限、密码等);

    • 服务器无存储,因此不能做登录失效处理,除非更改服务器密钥。

JWT格式包括3类,下面是一段JWT:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
  • 头部:主要是描述签名算法;

  • 负载:主要是描述加密对象的信息,主要描述是加密对象的信息,如用户的id等,也可以加些规范里面的东西,如iss 签发者,exp 过期时间,sub 面向的用户;

  • 签名:主要是把前面两部分进行加密,防⽌别⼈拿到token进行base解密后篡改token。

 

三、JWT使用

在SSM框架中,JWT的使用方法:

  • 添加依赖(POM.xml)

    <dependency>
       <groupId>io.jsonwebtoken</groupId>
       <artifactId>jjwt</artifactId>
       <version>0.7.0</version>
    </dependency>
  • 添加工具类,开发:生产token方法、校验token方法

    /**
    * JWT工具类
    * 1、生成的token,是可以通过base64解密出明文信息的
    * 2、base65解密出文明信息,修改后编码,则会解密失败
    * 3、无法作废已颁布的token,除非改密钥
    */
    public class JWTUtils {

       /***
        * 过期时间,一周
        */
       private static final long EXPIRE = 60000*60*24*7;

       /**
        * 加密密钥
        */
       private static final String SECRET = "dsaclass.net168";

       /**
        * 令牌前缀
        */
       private static final String TOKEN_PREFIX = "dsaclass";

       /**
        * subject
        */
       private static final String SUBJECT = "dsaclass";

       /**
        * 根据用户信息,生成令牌
        * @param user
        * @return
        */
       public static String geneJsonWebToken(User user){

           String token = Jwts.builder().setSubject(SUBJECT)
                  .claim("head_img", user.getHeadImg())
                  .claim("id", user.getId())
                  .claim("name", user.getName())
                  .setIssuedAt(new Date())
                  .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
                  .signWith(SignatureAlgorithm.HS256, SECRET).compact();

           token = TOKEN_PREFIX + token;

           return token;
      }

       /**
        * 校验token
        * @param token
        * @return
        */
       public static Claims checkJWT(String token){

           try{
               final Claims claims = Jwts.parser().setSigningKey(SECRET)
                      .parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
                      .getBody();
               return claims;
          }catch (Exception e){
               return null;
          }
      }
    }
  • 相关类中调用

    1、登录时生成token。

        public String findByPhoneAndPwd(String phone, String pwd) {

           User user = userMapper.findByPhoneAndPwd(phone,CommonUtils.MD5(pwd));

           if(user == null){
               return null;
          }
           return JWTUtils.geneJsonWebToken(user);
      }

    2、私有网页,拦截器拦截验证(不包括登录和注册页面请求)。

    public class LoginInterceptor implements HandlerInterceptor {

       /**
        * controller前登录拦截
        * @param request
        * @param response
        * @param handler
        * @return
        * @throws Exception
        */
       @Override
       public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

           try {
               String accesToken = request.getHeader("token");
               if (accesToken == null) {
                   accesToken = request.getParameter("token");
              }

               if (StringUtils.isNotBlank(accesToken)) {
                   Claims claims = JWTUtils.checkJWT(accesToken);
                   if (claims == null) {
                       // token过期,重新登录
                       sendJsonMsg(response,JsonData.buildError("登录过期,请重新登录"));
                       return false;
                  }
                   Integer id = (Integer)claims.get("id");
                   String name = (String)claims.get("name");

                   request.setAttribute("user_id",id);
                   request.setAttribute("name",name);
                   return true;
              }
          }catch (Exception e){
               // 登录失败
               sendJsonMsg(response,JsonData.buildError("未登录,请重新登录"));
          }
           return false;
      }

       /**
        * 相应json数据给前端
        * @param response
        * @param obj
        */
       private void sendJsonMsg(HttpServletResponse response,Object obj){
           ObjectMapper mapper = new ObjectMapper();
           response.setContentType("application/json;charset=utf-8");

           try (PrintWriter writer = response.getWriter()){
               writer.print(mapper.writeValueAsString(obj));
          }catch (Exception e){
               e.printStackTrace();
          }
      }


       @Override
       public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

      }

       @Override
       public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

      }
    }

     

  •  

posted @ 2021-09-29 01:11  ジョカ  阅读(1140)  评论(0)    收藏  举报