JWT 详解

1、JWT是什么?

JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。

2、JWT和传统Session的优劣

2.1、什么时候使用JWT?

Authorization (授权) : 这是使用JWT的最常见场景。一旦用户登录,后续每个请求都将包含JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是现在广泛使用的JWT的一个特性,因为它的开销很小,并且可以轻松地跨域使用。

Information Exchange (信息交换) : 对于安全的在各方之间传输信息而言,JSON Web Tokens无疑是一种很好的方式。因为JWTs可以被签名,例如,用公钥/私钥对,你可以确定发送人就是它们所说的那个人。另外,由于签名是使用头和有效负载计算的,您还可以验证内容没有被篡改。

2.2、JWT和Session的比较

1、无状态:
token 自身包含了身份验证所需要的所有信息,使得我们的服务器不需要存储 Session 信息,这显然增加了系统的可用性和伸缩性,大大减轻了服务端的压力。
也导致了它最大的缺点:当后端在token 有效期内废弃一个 token 或者更改它的权限的话,不会立即生效,一般需要等到有效期过后才可以。另外,当用户 Logout 的话,token 也还有效。除非,我们在后端增加额外的处理逻辑。

2、避免CSRF 攻击:
攻击者就可以通过让用户误点攻击链接,达到攻击效果。防止误触操作,避免请求直接获取本地的session值进行请求访问。

3、适合移动端应用
使用 Session 进行身份认证的话,需要保存一份信息在服务器端,而且这种方式会依赖到 Cookie(需要 Cookie 保存 SessionId),所以不适合移动端。
但是,使用 token 进行身份认证就不会存在这种问题,因为只要 token 可以被客户端存储就能够使用,而且 token 还可以跨语言使用。

4、单点登录友好
使用 Session 进行身份认证的话,实现单点登录,需要我们把用户的 Session 信息保存在一台电脑上,并且还会遇到常见的 Cookie 跨域的问题。但是,使用 token 进行认证的话, token 被保存在客户端,不会存在这些问题。

3、JWT的结构

JWT的结构由三部分组成,分别是标头、有效负载、签名算法,中间使用 点 进行隔开。

  1. header标头:记录了token的类型和加密算法的josn。
    像是这样:{ “typ”: “JWT”, “alg”: “HS256” }。然后要转成base64字符串。

  2. payload有效负载:记录了用户信息的json。
    同样要转成base64字符串。

  3. signatur:签名算法
    head和payload的json,转为base64字符串后,再加密,得到的。
    通过这个加密,就可以验证jwt是不是对的了。

4、JWT的工作流程

在身份验证中,当用户成功登录系统时,授权服务器将会把 JSON Web Token(JWT)返回给客户端,用户需要将此凭证信息存储在本地(cookie或浏览器缓存)。当用户发起新的请求时,需要在请求头中附带此凭证信息,当服务器接收到用户请求时,会先检查请求头中有无凭证,是否过期,是否有效。如果凭证有效,将放行请求;若凭证非法或者过期,服务器将回跳到认证中心,重新对用户身份进行验证,直至用户身份验证成功。

步骤如下:

  1. 发送登录请求,携带username、password
  2. 进行验证,验证通过返回JWT
  3. 请求头携带JWT发请求到应用服务
  4. 验证携带过来的JWT的合法性
  5. 验证通过返回,执行后续操作
    JWT 详解_第1张图片

5、获取Token和对token进行验证

在进行使用JWT的时候,我们首先还是引入依赖。

        <dependency>
            <groupId>com.auth0groupId>
            <artifactId>java-jwtartifactId>
            <version>3.4.0version>
        dependency>

之后我们先进行生成token的测试。如下代码所示:我们首先使用JWT对象的create方法进行创建对象,之后我们分别将Header标头、payload有效负载、signature签名算法进行指定,而这里的密钥也由自己指定。

	public static final String SIGN ="@lzq4585#$^"; // 密钥
	
    @Test
    void token() {
        Map map = new HashMap();

        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DATE,7);

        String token = JWT.create()
                .withHeader(map) // header
                .withClaim("id", 1) // payload
                .withClaim("name", "月月")
                .withExpiresAt(calendar.getTime()) // 有效时长
                .sign(Algorithm.HMAC256(SIGN)) // signature
        ;
        System.out.println(token);
    }

运行Test进行测试,我们可以看到token的结构,也就是x.y.z

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoi5pyI5pyIIiwiaWQiOjEsImV4cCI6MTYyNjcwMDAzOH0.PvUR8Qk9l_ct4xHfrl18cPs7NfNq9OIk4yyvTJmgmeY

既然生成了token,那么同样的我们也可以对这个token进行解析出来,如下代码所示:进行解析token值的时候我们需要给定对应的加密算法和密钥以及token值,进行解密后获取的token值分别getHeader、getClaim就可以获取到标头和有效负载了。

    @Test
    void verify(){
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("@lzq4585#$^")).build();

        DecodedJWT verify =  jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoi5pyI5pyIIiwiaWQiOjEsImV4cCI6MTYyNjAwMTkxOH0.4Q9RBX-YT2MQe2-RnlxquSdHj6WHNWgu3QJ91muk6qY");

        System.out.println(verify.getHeader());
        System.out.println(verify.getClaim("id").asString());
        System.out.println(verify.getClaim("id").asInt());
        System.out.println(verify.getPayload());

        System.out.println("过期时间:"+verify.getExpiresAt());
    }

注意:在这里的有效负载的值对于相对应的数据类型应使用对应的asString或者asInt等等。比如上面的id就是int类,而进行asString就得不到值,也就是null。
JWT 详解_第2张图片
6、方法的封装

在前面的测试JWT的生成以及验证我们写了两个对应的test测试,而在项目当中我们进行使用这两个方法也是必不可少的,所以我们可以对上面两个方法进行封装一下,用来直接处理JWT的生成和验证。

第一个就是生成JWT的方法,首先我们生成JWT先获取对于参数,这里使用map进行传参,这里的参数我们要写入payload有效负载当中,返回回去,之后我们添加一个时间,用来作为token过期时间,而后我们创建一个JWT对象,将对应的有效负载、过期时间、加密算法进行写进去给到token。这里入参用的map,而进行写入直接forEach进行循环写入,这是Java8的语法,可以了解一下。

    public static String getToken(Map<String, String> map) {
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DATE, 7);
        JWTCreator.Builder jwt = JWT.create();
        map.forEach((k, v) -> {
            jwt.withClaim(k, v);
        });

        jwt.withExpiresAt(calendar.getTime());
        String token = jwt.sign(Algorithm.HMAC256(SIGN));

        return token;
    }

第二个方法就是验证token的方法。这个就比较简单了,直接将token值传进来,通过密钥进行判断,最后直接返回。

    public static DecodedJWT verify(String token) {
        return JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
    }

方法封装之后,我们使用一个Utils类进行保存和统一管理,新建一个JwtUtil类,后面当我们需要使用到token的生成和验证的时候,我们就可以直接进行调用这里面的静态方法即可。

7、登录验证案例

最后离不开的还是案例,这里用一个登录进行验证。首先还是整合mybatis,这里的整合mybatis也就不做详细的说明了,SpringBoot详情可以参考:SpringBoot 详解

并且我们添加一个用来获取所有信息的接口:其中mapper查询语句如下:

<select id="login" parameterType="User" resultType="User">
    select * from user where  name = #{name} and password = #{password}
select>

之后添加一个请求,用来模拟登录,这里入参给到name和password。之后查询数据库,返回值用userDB进行保存,将id和name写进有效负载当中,然后调用前面封装的方法,也就是jwtUtil.getToken方法,返回一个token值,最后将数据写进map当中进行返回。

    @GetMapping("/user/login1")
    public Map<String,Object> login1(User user){
        Map map = new HashMap();
        try {
            User userDB =userService.login(user);
            Map payload = new HashMap();
            payload.put("id",userDB.getId());
            payload.put("name",userDB.getName());
            String token = jwtUtil.getToken(payload);
            map.put("state",true);
            map.put("msg","认证成功");
            map.put("token",token);
        }catch (Exception e){
            map.put("state",false);
            map.put("msg",e.getMessage());
        }

        return map;
    }

接口有了,之后我们就直接发送请求看一下效果,首先是账号密码都正确的,可以看到是会有token值进行返回的。
JWT 详解_第3张图片
随后发送一个账号密码不对的,也就验证通不过,不会生成token值,查看返回结果。
JWT 详解_第4张图片
同样的,在生成了token之后,我们可以调用验证token的方法,对生成的token进行验证

    @PostMapping("/user/test")
    public Map<String,Object> token(String token){
        Map map = new HashMap();
        try {
            DecodedJWT verify =jwtUtil.verify(token);
            map.put("token",verify);
        }catch (Exception e){
            map.put("e",e.getMessage());
        }
        return map;
    }

而这里我们同样的发送一条post请求进去查看结果,首先是有效的token,我们可以获取到这个token当中的信息。
JWT 详解_第5张图片
而后,当token被修改了,也就是这个token是通不过的,捕获异常。
JWT 详解_第6张图片
当然了,我们这里的token验证是很多请求接口都需要用到,这里的话我们可以使用一个拦截器进行统一管理,同样的可以参考:SpringBoot 详解

JWT 详解_第7张图片

你可能感兴趣的:(#,Java,jwt,java,json,session,验证)