zoukankan      html  css  js  c++  java
  • 初识JWT

    1、JWT是什么

    官方网站

    JWT是JSON Web Token的简称。是一种开放标准(RFC 7519),定义了一种紧凑且自包含的方式,以JSON对象的形式在各方之间安全地传输信息,因为他被数字签名也就是加密过,因此可以被用来验证和信任。JWT使用RSA或者ECDSA的公钥/私钥进行签名认证。

    2、它能做什么

    • 认证:这是使用JWT最常见的场景。用户登录后,每个后续请求将包括JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是目前JWT广泛使用的一个特性,因为它的开销小,并且可以跨不同的域轻松使用。

    • 信息交换:JWT是一种很好的加密传输信息的方式,因为它可以被因为JWT可以使用公钥/私钥对进行签名,因此可以确保发送者是他们所说的人。另外,由于签名是使用报头和有效负载计算的,所以还可以验证内容没有被篡改。

    3、JWT的特点

    基于传统的session的认证

    因为HTTP协议时一种无状态协议,这意味着如果用户向服务器发送自己的用户名和密码进行用户认证,那么下一次请求时,用户还要进行一次用户认证才可以,因为根据HTTP协议,我们并不知道是哪个用户发出的请求,所以为了让我们的应用能是被是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时间内传递给浏览器,告诉浏览器保存cookie,一别下次请求时发送给我们的应用,这样我们的应用就能够识别请求来自哪个用户了,这是传统的session认证。

    问题

    每个用户经过我们的应用认证后,我们的应用都在服务端进行一次记录,以方便用户下次请求的鉴别,通常而言session是保存在服务端的内存中的,随着认证用户的增多,服务器的开销会加大。

    用户认证之后,服务端做认证记录,如果认证的记录都被保存在内存中的话,如果在分布式应用中,就相对的限制了负载均衡的能力。

    因为是基于cookie来进行用户识别的,cookie如果被截获,用户就会很容易的受到跨站请求伪造攻击(CSRF)。

    认证流程

    首先,前端通过表单提交将用户名密码等信息发送都后端的接口。这个过程一般是一个PSOT的请求,建议通过SSL加密传输(https协议),从而避免敏感信息被嗅探。

    后端核对用户名和密码成功后,将用户的id等其他信息作为JWT Payload,将其头部分别进行BASE64编码拼接后签名,形成一个JWT。形成的JWT的格式是header.payload.signature分割成三部分。

    head:存一些token加密的方式,类型等信息。

    "alg":"HS256",
    "type":"JWT"
    
    • alg:签名算法,HS256、HS384、HS512、RS256、RS384、RS512、ES256、ES384、ES512、PS256、PS384
    • type:认证格式,如JWT。

    payload:一般就是用户的信息。

    例如:

    {
        "uid":"1234567",
        "name":"Tom",
        "admin":"true"
    }
    

    对该json字符串进行BASE64压缩就得到了第二部分,这里可以看出payload完全是明文暴露的,因为BASE64是编码格式,有编码就有解码,他可不是加密算法。

    声明的主体信息格式有三中类型: registeredpublicprivate

    signature:该部分是签名,以token的前两部分也就是head和payload作为明文,用共同协商好的私钥进行签名。

    如果选择的算法是HMAC-SHA256,执行以下的方法得到签名值signature

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

    如果是非对称加密

    HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), publicKey, privateKey)
    

    再回过来看认证流程,后端将JWT字符串作为登陆成功的返回结果返回给前端。前端可以将返回的结果存在本地缓存中或者sessionStorage中,退出登录的时候,只需要删除token即可。

    前端每次请求都将JWT放在HTTP请求头的Authorization位(为了解决XSS和XSRF攻击)HEADER。

    后端检查是否存在,如果存在还要进一步验证JWT的有效性。例如检查签名是否正确;检查Token是否过期;检查Token的接收方是否是自己。

    验证通过后后端使用JWT中包含的用户信息进行其他逻辑操作,返回相应的结果。

    因此从上面的验证过程看出,不压不铭感信息放在JWT中,例如用户密码等,因为如果知道秘钥在通过BASE64解码,就能知道payload中的内容。

    4.JWT的优势

    • 简洁:可以通过URL,POST参数或者HTTP header发送,因为数据量小,传输速度快。
    • 自包含:payload中包含了所有用户所需要的信息,避免了多次查询数据库。
    • 因为token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持。
    • 不需要保存服务端,使用户分布式应用。

    5、常见异常信息

    • SignatureVerificationException:签名不一致
    • TokenExpireException:令牌过期异常
    • AlgorithmMismatchException:算法不匹配异常
    • InvalidClaimException:失效的payload异常。

    image-20200926213534141

    SpringBoot 中对JWT的处理

    首先编写JWT工具类,JWTUtil

    public class JwtUtil {
        // 签名可以用算法生成
        private static final String SIGN = "%^$#@!*nbcfer12794";
    
        /**
         * 生成token
         * @param map
         * @return
         */
        public static String getToken(Map<String, String> map){
            Calendar instance = Calendar.getInstance();
            instance.add(Calendar.DATE,7);
            JWTCreator.Builder builder = JWT.create();
            map.forEach((k,v)->{
                builder.withClaim(k,v);
            });
            builder.withExpiresAt(instance.getTime());
            return builder.sign(Algorithm.HMAC256(SIGN));
    
        }
    
        /**
         * 验证token
         * @param token
         * @return
         */
        public static DecodedJWT verify(String token) throws Exception{
            DecodedJWT verify = JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
            return verify;
        }
    }
    

    每次请求都要检查请求头中是否包含正确的token,只有token验证成功才能继续进行加下来的操作。
    因此可以自定义一个拦截器,每次请求都做检查token的工作。

    public class JWTInterceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            // errorMsg map
            Map<String, Object> errorMsg = new HashMap<>();
    
            // 从请求头中获取token
            String token = request.getHeader("token");
    
            // 验证token的有效性,如果校验成功返回true,放行,否则捕捉异常,返回errorMsg
            try {
                JwtUtil.verify(token);
                return true;
            }catch (SignatureVerificationException e){
                errorMsg.put("msg","无效签名");
                e.printStackTrace();
            }catch (TokenExpiredException e){
                e.printStackTrace();
                errorMsg.put("msg","签名失效");
            }catch (AlgorithmMismatchException e){
                e.printStackTrace();
                errorMsg.put("msg","签名算法不一致");
            }catch (Exception e){
                e.printStackTrace();
                errorMsg.put("msg","签名异常");
            }
            errorMsg.put("state",false);
    
            // 返回的是json串,因此需要用jackson将map转成json
            String jsonMsg = new ObjectMapper().writeValueAsString(errorMsg);
            response.setContentType("application/json; charset=UTF-8");
            response.getWriter().println(jsonMsg);
            return false;
        }
    }
    
    

    将拦截器加入到配置类中

    @Configuration
    public class JWTConfig implements WebMvcConfigurer {
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new JWTInterceptor())
                    .excludePathPatterns("/user/login") // 登陆的时候不进行拦截,因为第一次访问需要生成token
                    .addPathPatterns("/**");
        }
    }
    

    剩下的是controller,service,dao
    控制层需要调用service层的方法校验传过来的参数,如果无误,根据用户信息放入payload,生成token。最终返回提示信息。

    @GetMapping("/user/login")
        public Map<String, Object> login(String username,String password){
            //校验参数
            User loginUser = userService.login(username, password);
    
            // 通过后生成token,返回给客户端
            Map<String, Object> map = new HashMap<>();
            Map<String, String> payload = new HashMap<>();
            payload.put("userId",loginUser.getUserId());
            payload.put("username",loginUser.getUsername());
            try{
                String token = JwtUtil.getToken(payload);
                map.put("token",token);
                map.put("state",true);
                map.put("msg","认证成功");
            }catch (Exception e){
                map.put("state",false);
                map.put("msg","认证失败");
            }
    
            return map;
        }
    

    用户postman测试一下。
    在这里插入图片描述
    当不带token的时候,自然会抛出异常,返回错误提示。
    在这里插入图片描述

  • 相关阅读:
    02 .NET CORE 2.2 使用OCELOT -- 路由
    01 .NET CORE 2.2 使用OCELOT -- 简单使用
    Winows上简单配置使用kafka(.net使用)
    【C#】 获取计算机的硬件信息
    使用NPOI进行Excel操作
    Xamarin 开发Android应用简易教程(1)
    【日常笔记】字符串加密
    【日常笔记】生成验证码图片
    [Winfrom] 使用一个启动快捷方式,打开2个不同的窗体并且共用一个缓存空间
    windows获取物理mac地址
  • 原文地址:https://www.cnblogs.com/dataoblogs/p/14121855.html
Copyright © 2011-2022 走看看