JWT

JWT(JSON Web Token)是一种轻量级的身份验证机制,常用于授权和信息交换。其优势在于简洁、自包含,适合跨语言和分布式微服务。JWT由Header、Payload和Signature三部分组成,签名确保内容未被篡改。本文提供了一个JWT的Java实现案例,展示了如何创建、验证JWT,并封装成工具类。同时,介绍了如何通过拦截器在分布式系统中实现JWT的自动验证。
摘要由CSDN通过智能技术生成


通过JSON的格式作为web应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输。在数据传输过程可以完成数据加密,签名等相关处理。

JWT能做什么

1、授权

一旦用户开始登陆,每个后续请求将包括JWT,从而允许用户访问该令牌允许的路由,服务和资源。单点登陆是当今广泛使用JWT的一项功能,因为它的开销很小并且可以在不同的域中使用。

2、信息交换

JWT是在各方之间安全地传输信息的好方法。因为可以对JWT进行签名,所以可以保证发件人是所希望的。此外,由于签名是使用标头和有效负载计算的,还可以验证内容是否遭到篡改!

JWT的优势是什么?

1、简洁:可以通过URL,POST参数或者在Http header发送,数据量小,传输速度快

2、自包含:负载中包含了所有用户所需要的信息,避免了多次查询数据库

3、因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持

4、不需要在服务端保存会话信息,特别适合于分布式微服务!

JWT的结构是什么?

令牌组成:1、标头(Header)2、有效载荷(PayLoad)3、签名(Signature)

形式是 header.payload.singnature

Header

标头通常由两部分组成:令牌的类型(即JWT)和所使用的的签名算法。例如HMAC、SHA256或者RSA。它会使用Base64编码组成JWT结构的第一部分。

注意:Base64是一种编码,也就是说,它可以被翻译为原来的样子,不是一种加密过程。

{
    "alg":"HS256",
    "typ":"JWT"    
}

Base64 xxxxx.

Payload

有效负载,其中包含声明,声明是有关实体(通常是用户)和其他数据的声明。同样的,它会使用Base64编码组成JWT结构的第二部分。

{
    "sub":"123456789",
    "name":"John Doe",
    "admin":ture
}
Base64    .yyyyyy.  

Signature

前面两部分都是使用Base64进行编码的,即前端可以解开里面的信息。Signature需要使用编码后的header 和 payload以及我们提供的一个密钥,然后使用header指定的签名算法(HS256)进行签名。签名的作用是保证JWT没有被篡改过!

签名目的:最后一步签名的过程,实际上是头部以及负载内容进行签名,防止内容被篡改。如果有人对头部以及负载内容解码之后再进行修改,在进行编码,最后加上之前的签名组合成新的JWT的话,那么服务器端会判断出新的头部和负载形成的签名和JWT附带的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。

信息安全问题:不应该在负载里面加入敏感信息。

第一个JWT案例代码

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.junit.jupiter.api.Test;

import java.util.Calendar;
import java.util.Date;

public class TestToken {

    //令牌获取
    @Test
    void getToken(){
        Calendar instance=Calendar.getInstance();
        instance.add(Calendar.SECOND,200);//20秒
        String token=JWT.create()
                .withClaim("userId","123")
                .withClaim("username","zidu")
                .withExpiresAt(instance.getTime())//指定令牌过期时间
                .sign(Algorithm.HMAC256("!@#@#R#DWE!@")); //签名
        System.out.println(token);
    }

    //令牌校验
    @Test
    public void checkToken(){
        //根据上面的私钥生成一个验签对象
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("!@#@#R#DWE!@")).build();
        DecodedJWT verify = jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MjEwNjg4NzAsInVzZXJJZCI6IjEyMyIsInVzZXJuYW1lIjoiemlkdSJ9.VxoFYsUKCCNpq7_Hqz0mTagXaOMV74ttRoxh5W21tQM");
        Date expiresAt = verify.getExpiresAt();//获取令牌失效时间
        System.out.println(expiresAt);
        System.out.println(verify.getClaim("userId").asString());
        System.out.println(verify.getClaim("username").asString());
    }
}
===================================================================================
//常见异常
签名异常
令牌过期异常
算法不匹配异常
失效的PayLoad异常

封装JWT工具类

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.util.Calendar;
import java.util.Map;

/*
* @ author Selfcrossing
* */

public class JWTUtil {
    //服务器上的私钥
    private static  final String SIGN="!@#@#R#DWE!@";

    /*
    * 生成token heade.payload.signature
    */
    public static String getToken(Map<String,String>map){
        Calendar instance=Calendar.getInstance();
        instance.add(Calendar.DATE,7);//默认七天过期
        JWTCreator.Builder builder=JWT.create();
        // 设置要在payLoad中存放的键值对
        map.forEach((k,v)->{
            builder.withClaim(k,v);
        });
        //设置过期时间 & 加密算法
        String token=builder.withExpiresAt(instance.getTime())
        .sign(Algorithm.HMAC256(SIGN));
        return  token;
    }
    /*
    * 验证token,没有异常即验证通过
    *
    * */
    public static DecodedJWT verify(String token){
        return JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
    }
}

测试

/*
    *
    *  用户登录
    * */
    @GetMapping("login")
    @ResponseBody
    public Map<String,Object> login(String username,String password){
        System.out.println(username);
        System.out.println(password);
        Map<String,Object> map=new HashMap<>();
        User user=new User(username,password);

            User userDB=userService.login(user);
            if (userDB!=null)
            {
                HashMap<String,String> payload=new HashMap<>();
                payload.put("id",userDB.getId());
                payload.put("username",userDB.getUsername());

                //生成JWT令牌
                String token = JWTUtil.getToken(payload);
                map.put("state",true);
                map.put("msg","登陆成功");
                map.put("token",token); //响应token
            }
            else
                {
                map.put("state", false);
                map.put("msg", "登录失败");
            }
        return map;
    }
======================================================================================
************************************无情分割线*****************************************
======================================================================================    

    @PostMapping("test")
    @ResponseBody
    public Map<String, Object> test01(String token){
        System.out.println(token);
        Map<String,Object> map=new HashMap<>();

        try
        {
            //验证令牌
            DecodedJWT verify = JWTUtil.verify(token);
            map.put("state",true);
            map.put("msg","登陆成功");
            return map;
        }
        //签名不一致异常
        catch (SignatureVerificationException e)
        {
            e.printStackTrace();
            map.put("msg","无效签名!");
        }
        // 令牌过期异常
        catch (TokenExpiredException e)
        {
            e.printStackTrace();
            map.put("msg","token过期!");
        }
        // 算法不匹配异常
        catch (AlgorithmMismatchException e)
        {
            e.printStackTrace();
            map.put("msg","算法不一致!");
        }
        //
        catch (Exception e)
        {
            e.printStackTrace();
            map.put("msg","无效签名!!");
        }
        map.put("state", false);
        return map;
    }

上面这个例子,我们可以拿着所有需要登录以后才能操作的接口,利用token进行验证保护。但是如果每次都要传入一个token,并且在每个接口里面都要去验证到底准确不准确,那是非常麻烦的。于是我们可以利用拦截器!
向后端服务器发起请求获得token
带着token去测试后端接口

解决方法

在单体下面,用拦截器去做,在分布式系统里面,利用网关去解决!

package com.zidu.travels.interceptors;

import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zidu.travels.utils.JWTUtil;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

/*
* 令牌拦截器
* */
public class JWTInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        //获取请求头中的令牌
        String token = request.getHeader("token");
        Map<String,Object> map=new HashMap<>();
        try
        {
            //验证令牌
            DecodedJWT verify = JWTUtil.verify(token);
            map.put("state",true);
            map.put("msg","登陆成功");
            return true;//放行请求
        }
        //签名不一致异常
        catch (SignatureVerificationException e)
        {
            e.printStackTrace();
            map.put("msg","无效签名!");
        }
        // 令牌过期异常
        catch (TokenExpiredException e)
        {
            e.printStackTrace();
            map.put("msg","token过期!");
        }
        // 算法不匹配异常
        catch (AlgorithmMismatchException e)
        {
            e.printStackTrace();
            map.put("msg","算法不一致!");
        }
        //
        catch (Exception e)
        {
            e.printStackTrace();
            map.put("msg","无效签名!!");
        }
        map.put("state", false);//设置状态

        //将map转为json
        String json=new ObjectMapper().writeValueAsString(map);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().println(json);
        return false;
    }

}

配置拦截器!

package com.zidu.travels.config;


import com.zidu.travels.interceptors.JWTInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    /*
    *
    * 添加自定义的拦截器
    *
    * */
    @Override
    public void addInterceptors(InterceptorRegistry registry)
    {
        //正常应该除了注册和登录外,其他所有请求都要拦截
        registry.addInterceptor(new JWTInterceptor())
                .addPathPatterns("/user/test")
                .excludePathPatterns("/user/login")
                .excludePathPatterns("/user/register");
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值