JWT简单记录以及整合Springboot

JWT简介

什么是JWT

JWT(Json Web Token)是一个开放标准,它定义了一种紧凑的、自包含的方式,用于在各方面之间以JSON对象安全地传输信息,此信息可以验证和信任,因为它是数字签名的,jwt可以使用密钥(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名

简单理解: JWT简称Json Web Token, 也就是通过JSON形式作为Web应用中的令牌, 用于在各方之间安全地将信息作为对象传输, 在数据传输过程中还可以完成数据加密、签名等相关处理

JWT能做什么

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

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

JWT的认证流程

image-20210308200110590

  • 首先,前端通过Web表单将自己的用户名和密码发送到后端的接口。这一过程- -般是一 个HTTP POST请求。建议的方式是通过SSL加密的传输(https协议) ,从而避免敏感信息被嗅探。

  • 后端核对用户名和密码成功后,将用户的id等其他信息作为JWT Payload (负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT(Token)。形成的JWT就是一个形同11. zzz. xxx的字符串。token head . payload . singurater

  • 后端将JWT字符串作为登录成功的返回结果返回给前端。 前端可以将返回的结果保存在localStorage或sessionStorage上, 退出登录时前端删除保存的JWT即可。

  • 前端在每次请求时将JWT放入HTTP Header中的Authorization位。 (解决XSS和XSRF问题)

  • 后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确;检查Token是否过期;检查Token的接收方是否是自己(可选)

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

JWT的优势

  • 简洁(Compact): 可以通过URL,POST参 数或者在HTTP header发送,因为数据量小,传输速度也很快

  • **自包含(Self-contained): **因为Token是 以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持。

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

  • 不需要在服务端保存会话信息,特别适用于分布式微服务

JWT的结构是什么

⚔️ 令牌组成

1. **标头(Header)**
2. **有效载荷(Payload)**
3. **签名(Signature)**

因此,JWT通常如下所示: xxxx.yyyy.zzzz Header.payload.signature

⚔️ Header

标头通常由两部分组成:令牌的类型(即JWT) 和所使用的签名算法,例如HMAC SHA256或RSA。 它会使用Base64 编码组成JWT 结构的第一部分。
注意:Base64是一 种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。

{
	"alg" : "HS256" //使用的加密算法
	"typ" : "JWT"  //类型
}

⚔️ Payload

令牌的第二部分是有效负载,其中包含声明。声明是有关实体(通常是用户)和其他数据的声明。同样的,它会使用Base64 编码组成JWT结构的第二部分(不要在payload里放敏感信息 如:用户密码等)

{
	"sub" : "HS256"
	"name" : "wang"
	"admin" : "true"
}

⚔️ signature

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

    HMACSHA256 (base64Ur1Encode(header) + "." + base64Ur1Encode(payload) , secret); 
    

⚔️ 签名的目的

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

JWT适合用于向Web应用传递一些非敏感信息, JWT还经常用于设计用户认证和授权系统,甚至实现Web应用的单点登陆

image-20210308204029969

原始数据未加密

输出: 是由三个点分隔的Base64-URL字符串, 可以在HTML和HTTP环境中轻松传递这些字符串,与基于XML的标准(例如SAML)相比,它更紧凑

  • 简洁(Compact): 可以通过URL,POST参 数或者在HTTP header发送,因为数据量小,传输速度也很快
  • 自包含(Self-contained): 负载中包含了多有用户所需要的信息, 避免了多次查询数据库

image-20210308204359521

JWT实战

  • 导入依赖

     <!--引入jwt-->
    <dependency>
        <groupId>com.auth0</groupId>
        <artifactId>java-jwt</artifactId>
        <version>3.10.3</version>
    </dependency>
    
  • 编写测试类进行测试

    @Test
        public void test(){
    
            //HashMap<String, Object> map = new HashMap<>();
    
            Calendar instance = Calendar.getInstance();//日历类
            instance.add(Calendar.SECOND,100);
    
            String token = JWT.create()
                    //.withHeader(map) //header(可以不写默认就是这个)
                    .withClaim("userId",314) //payload
                    .withClaim("username","ahui")
    
                    .withExpiresAt(instance.getTime()) //指定令牌的过期时间
                    .sign(Algorithm.HMAC256("onlylmf")); //签名  ("密钥")
    
            System.out.println(token);
        }
    
        @Test
        public void test1(){
            //创建验证对象
            JWTVerifier onlylmf = JWT.require(Algorithm.HMAC256("onlylmf")).build();
            DecodedJWT verify = onlylmf.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTUyNTI5ODYsInVzZXJJZCI6MzE0LCJ1c2VybmFtZSI6ImFodWkifQ.nIib7kjGvqOEZ6fqWOypkKEd_Hy02ReQfgaLbtnaKEw");
            System.out.println(verify.getClaims().get("userId").asInt());
            System.out.println(verify.getClaims().get("username").asString());
            System.out.println(verify.getExpiresAt()); //过期时间
        }
    

    **注: ** 存入Int类型数据时不要以0开头的数据,解密出来不一样

常见的异常信息

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

封装工具类

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;

public class JWTUtils {

    private static final String SIGNATURE = "onlylmf"; //密钥

    /**
     * 生成token  header.plyload.signature
     */
    public static String getToken(Map<String,String> map){

        Calendar instance = Calendar.getInstance();//日历类
        instance.add(Calendar.DATE,7);  //默认7天过期

        //创建jwt builder
        JWTCreator.Builder builder = JWT.create();

        //payload
        map.forEach((k,v)->{
            builder.withClaim(k,v);
        });

        //signature
        String token = builder.withExpiresAt(instance.getTime())//指定令牌的过期时间
                .sign(Algorithm.HMAC256(SIGNATURE)); //签名  ("密钥")

        return token;
    }


    /**
     * 验证token  验证合法性
     */
    public static void verify(String token){
        //创建验证对象
        JWT.require(Algorithm.HMAC256(SIGNATURE)).build().verify(token);
    }


    /**
     * 获取token信息方法
     */
    public static DecodedJWT getTokenInfo(String token){
        DecodedJWT verify = JWT.require(Algorithm.HMAC256(SIGNATURE)).build().verify(token);
        return verify;
    }

}

整合Springboot

引入依赖

<!--Springboot-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis-->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.4</version>
</dependency>
<!--lombok-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

<!--Druid数据库连接池-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.3</version>
</dependency>

创建数据库表

用于用的是之前项目所以自行创建数据库 (用的之前ems_vue项目的emp中的用户表)

image-20210309134003456

  • 新建实体类

    image-20210309133953477

  • 开发DAO和Mapper.xml

    User findByUserName(String username);
    
    -------------------------------------------
    
    <!--findByUserName-->
    <select id="findByUserName" parameterType="String" resultType="User">
    select id,username,realname,password,sex,status,regsterTime
    from t_user where username=#{username}
    </select>
    
  • 开发Service和ServiceImpl

    //用户登录
    User login(User user);
    
    --------------------------
        
        @Override
        public User login(User user) {
        //1. 根据用户输入的用户名查询
        User byUserName = userDao.findByUserName(user.getUsername());
        //可以用工具类判断
        if (!ObjectUtils.isEmpty(byUserName)){
            //2. 比较密码
            if (user.getPassword().equals(byUserName.getPassword())){
                return byUserName;
            }else {
                throw new RuntimeException("密码输入错误");
            }
        }else {
            throw  new RuntimeException("账号输入错误");
        }
    }
    
  • 创建Controller

    /**
         * 用来处理用户登录
         */
        @PostMapping("login")
        public Map<String,Object> login(@RequestBody User user){
            log.info("当前用户信息为: [{}]",user.toString());
    
            Map<String,Object> map = new HashMap<>();
    
            try {
                User userDB = userService.login(user);
    
                //生成payload 将想要加入的数据put进map集合
                HashMap<String, String> payload = new HashMap<>();
                payload.put("id",userDB.getId());
                payload.put("username",userDB.getUsername());
                //生成JWT令牌
                String token = JWTUtils.getToken(payload);
                map.put("state",true);
                map.put("msg","验证成功,登录完成");
                map.put("user",userDB);
                map.put("token",token);
    
            } catch (Exception e) {
                e.printStackTrace();
                map.put("state",false);
                map.put("msg",e.getMessage());
            }
            return map;
        }
    
        @PostMapping("/user/test")
        public Map<String,Object> test(String token){
            Map<String,Object> map = new HashMap<>();
            log.info("当前token为:[{}]",token);
            try{
                DecodedJWT verify = JWTUtils.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","token算法不一致");
            }catch (Exception e){
                e.printStackTrace();
                map.put("msg","token无效");
            }
            map.put("state",false);
            return map;
        }
    

    image-20210309151318538

**问题: ** 使用上述方式每次都要传递token数据,每个方法都需要验证token冗余,不够灵活,应该如何优化

Web项目应该使用拦截器进行优化

  • 创建interceptors包下的JWTInterceptor类

    public class JWTInterceptor implements HandlerInterceptor {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
            HashMap<String, Object> map = new HashMap<>();
            //获取请求头中的令牌
            String token = request.getHeader("token");
    
            try{
                JWTUtils.verify(token);
                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","token算法不一致");
            }catch (Exception e){
                e.printStackTrace();
                map.put("msg","token无效");
            }
            map.put("state",false); //设置状态
            //将map 转为JSON jackson
            String json = new ObjectMapper().writeValueAsString(map);
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().println(json);
            return false;
        }
    }
    
  • 在config包下创建拦截器InterceptorConfig

    //拦截器
    @Configuration
    public class InterceptorConfig implements WebMvcConfigurer {
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new JWTInterceptor())
                    .addPathPatterns("/**") //拦截所有路径
                    .excludePathPatterns("/user/**"); //排除用户相关路径
        }
    }
    
  • 然后之前写在Controller中的方法就可以不用了

    /**
         * 用来处理用户登录
         */
        @PostMapping("login")
        public Map<String,Object> login(@RequestBody User user){
            log.info("当前用户信息为: [{}]",user.toString());
    
            Map<String,Object> map = new HashMap<>();
    
            try {
                User userDB = userService.login(user);
    
                //生成payload 将想要加入的数据put进map集合
                Map<String, String> payload = new HashMap<>();
                payload.put("id",userDB.getId());
                payload.put("username",userDB.getUsername());
                //生成JWT令牌
                String token = JWTUtils.getToken(payload);
                map.put("state",true);
                map.put("msg","验证成功,登录完成");
                map.put("user",userDB);
                map.put("token",token);
    
            } catch (Exception e) {
                e.printStackTrace();
                map.put("state",false);
                map.put("msg",e.getMessage());
            }
            return map;
        }
    
        @PostMapping("/user/test")
        public Map<String,Object> test(HttpServletRequest request){
            Map<String,Object> map = new HashMap<>();
            //处理自己的业务逻辑
            String token = request.getHeader("token");
            DecodedJWT verify = JWTUtils.verify(token);
            log.info("用户id: [{}]",verify.getClaim("id").asString());
            log.info("用户username: [{}]",verify.getClaim("username").asString());
            map.put("state", true);
            map.put("msg", "请求成功!");
            return map;
        }
    ``
    
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值