zoukankan      html  css  js  c++  java
  • JWT实战

    JWT实战

    JWT认证流程

    先来回顾下JWT的流程,jwt是存储在客户端的,服务器不需要存储jwt;客户端每次发送请求时携带token,然后到服务端验证token是否正确,是否过期,然后解码出携带的用户信息。

    存在的问题 
    1、Token失效问题: 
    比如在浏览器端通过用户名/密码验证获得签名的Token被木马窃取。即使用户登出了系统,黑客还是可以利用窃取的Token模拟正常请求可用它访问服务器,而服务器端对此完全不知道,(因为JWT机制是无状态的),直到过期,中间服务器无法控制它.

    2、 app类Token的有效时间

        token的有效时间:
    
            1. 如果 app 是新闻类/游戏类/聊天类等需要长时间用户粘性的. 一般可以设置1年的有效时间!
    
            2. 如果 app 是 支付类/银行类的. 一般token只得有效时间比较短: 15分钟左右!
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    我们的解决方法是: 
    服务端使用Redis缓存服务器签发给已登录用户的Token, 
    每次客户端发送请求时到redis中查该用户 请求的token 和 redis存的token是否一致,不一致不允许token登录, 
    如果一致,判断这个token是否可以用(主要防止修改密码和注销操作的token没失效问题) 
    最后返回用户信息 
    当用户修改密码和注销时直接将redis中该用户的Token设置失效。下次通过token登录,会提醒token失效,要重新登录,我们重新生成一个新的token给用户。通过redis存储token,实现主动控制 token过期失效的问题了。

    封装的JWT工具类 部分代码 
    生成Token码

      /**
         * 生成jwt token
         * @param userId 
         * @param expireTime 过期时间戳
         * @return
         */
        public static String makeToken(String userId,Date expireTime){
            long nowMillis = System.currentTimeMillis();
            Date now=new Date(nowMillis);//签发时间精度:毫秒
            try {
                Algorithm algorithm = Algorithm.HMAC256(MyConstance.JWT_SECRET);
                return JWT.create().withIssuer(userId).withIssuedAt(now).withExpiresAt(expireTime).sign(algorithm);
            } catch (UnsupportedEncodingException exception){
                exception.printStackTrace();
            } catch (JWTCreationException exception){
                exception.printStackTrace();
            }
            return null;
        }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    解码和验证Token码

    
        /**
         * 校验toekn是否有效
         * @param userId
         * @param token
         * @return
         */
        public static boolean verifyToken(String userId,String token){
            boolean active = true;
            try {
                Algorithm algorithm = Algorithm.HMAC256(MyConstance.JWT_SECRET);//声明签名所用的算法和秘钥
                JWTVerifier verifier = JWT.require(algorithm).withIssuer(userId).build();
                verifier.verify(token);
            } catch (TokenExpiredException exception){
                //System.out.println("--- token 过期");
                active = false;
            } catch (JWTDecodeException exception){
                //System.out.println("--- token 无效");
                active = false;
            } catch (UnsupportedEncodingException exception){
                //System.out.println("--- token 无效");
                active = false;
            } catch (JWTVerificationException exception){
                //System.out.println("--- token 错误");
                active = false;
            }
            return active;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    登录

    @ApiOperation(value = "登录")
        @RequestMapping(value = "/loginWEB", method = RequestMethod.POST)
        public ResponseEntity<BaseResult> loginWEB(HttpServletRequest request, HttpServletResponse response,
            @ApiParam("用户名") @RequestParam(value = "username", required = false) String username,
            @ApiParam("密码") @RequestParam(value = "password", required = false) String password) {
    
            XkbbUser user=userService.getBy("username", username);
            //账号密码校验业务
            if(user==null) {
                return buildFailedInfo(ApiConstance.USER_NOT_EXIST);
            }
            if(!password.equals(user.getPassword())) {
                return buildFailedInfo(ApiConstance.PASSWORD_ERROR);
            }
            //获取用户id
            String userId=user.getId();
            //设置token过期时间为30天之后
            Date expireTime = Tool.datePlu(new Date(), 30);
            //生成JWT(JSon Web Token)
            String token=TokenUtil.makeToken(userId, expireTime);
            //更新缓存里面该用户的token:
            //    如果已登录,则使其Token失效
            TokenUtil.updateTokenAPP(userId, token);
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("token", token);//服务器生成的token
            return buildSuccessInfo(map);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    MyConstance类部分全局常量定义 
    /** 
    * token的缓存map名 
    */ 
    public static final String KEY_TOKEN_MAP = “key_token_map”; 
    /** 
    * JWT生成token时加密用的secret 
    */ 
    public static final String JWT_SECRET = “3MZq0BYyGcXYoXjhS4QbAM+2YdlLCwKRr2gvVJOJ”;

    /**
         * 用户登录校验
         * @param userId
         * @param token
         * @return
         */
        //到redis中查该用户 请求的token 和 redis存的token是否一致,不一致不允许token登录, 
        //再次根据创建时间,判断这个token是否可以用(主要防止出现修改密码和注销操作的token没失效问题)
        //失效则Redis中删除该token
        public static boolean isLogin(String userId,String token){
            if(Tool.isNotBlank(userId)) {
                if(Tool.isNotBlank(token)) {
                    //到redis中查该用户 请求的token 和 redis存的token是否一致,不一致不允许token登录
                    String existValue = (String) CacheUtil.hget(MyConstance.KEY_TOKEN_MAP, userId);
                    //System.out.println("existValue:"+existValue);
                    if(Tool.isNotBlank(existValue) && existValue.equals(token)) {
                        //再次根据创建时间,判断这个token是否可有效(主要防止出现修改密码和注销操作的token没失效问题)
                        boolean isLogin = verifyToken(userId,token);
                        //失效则Redis中删除该token
                        if(!isLogin){
                            CacheUtil.hdel(MyConstance.KEY_TOKEN_MAP,userId);
                        }
                        return isLogin;
                    }
                }
            }
            return false;
        }
    
    
    /**
         * 清除用户缓存token
         * @param userId
         * @return
         */
        public static String clearToken(String userId){
    
            CacheUtil.hdel(MyConstance.KEY_TOKEN_MAP, userId);
    
        }
    
     /**
         * 更新用户token
         * @param userId
         * @param value
         */
        public static void updateToken(String userId,String value){
            clearToken(userId);//清除缓存中旧的Token
            CacheUtil.hset(MyConstance.KEY_TOKEN_MAP, userId, value);//缓存新的Token
    
        }
  • 相关阅读:
    2008新的一年到来了!
    WPF 回车转Tab实现跳转
    Remoting和WebService/Ref, Out, Params的区别/
    教你如何编写游戏外挂
    在表达式中使用内置报表函数和聚合函数 (Reporting Services)
    Facade模式
    十分经典的批处理教程
    Entity Framework(实体框架)之详解 Linq To Entities 之一 (经典收集自用)
    关于AppDomain 创建实例进行程序集之间的通讯问题
    OPENQUERY用法
  • 原文地址:https://www.cnblogs.com/yueguanguanyun/p/9055126.html
Copyright © 2011-2022 走看看