zoukankan      html  css  js  c++  java
  • SpringBoot

    前言

    说说JWT,先说下互联网服务常见的两种用户认证方式:


    session认证与Token认证

    session认证

    传统的Session认证的大体流程可以表示为用户提供用户名和密码登录后由服务器存储一份用户登录信息并传递给浏览器保存为Cookie,并在下次请求中根据Cookie来识别用户,但这种方式缺陷明显:

    • Session都是保存在内存中,随着认证用户的增多,服务端的开销明显增大
    • 保存在内存中的Session限制了分布式的应用
    • Cookie容易被截获伪造

    Token认证

    Token 泛指身份验证时使用的令牌,Token鉴权机制从某些角度而言与Cookie是一个作用,其目的是让后台知道请求是来自于受信的客户端,其通过实现了某种算法加密的Token字符串来完成鉴权工作,其优点在于:

    • 服务器不需要保存 Session 数据(无状态),容易实现扩展
    • 有效避免Cookie被截获引发的CSRF攻击
    • 可以存储一些业务逻辑所必要的非敏感信息
    • 便于传输,其构成非常简单,字节占用小

    JWT简介

    JWT定义

    • JWT全称为Json web token,也就是 Json 格式的 web token,可以这么理解:
    Token // 个人证件 
    JWT  // 个人身份证
    

    JWT数据结构

    • JWT由三段字符串组成,中间用.分隔,如下:
    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjEsInNjb3BlIjo4LCJleHAiOjE3MTU3NDAyMjIsImlhdCI6MTYyOTM0MDIyMn0.wuRsF5wvLHbDF_21Pocas8SeXQ315rgBl6wm1LRL2bQ
    
    • JWT 的三个部分依次如下:
    Header(头部)// Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。
    Payload(负载)// Payload 部分是一个 JSON 对象,用来存放实际需要传递的数据
    Signature(签名)// Signature 部分是对前两部分的签名,防止数据篡改
    
    • 第一段字符串Header:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9将其 Base64 解码后得到:
    {
    	"typ": "JWT", // TOKEN TYPE ,token类型
    	"alg": "HS256"  //ALGORITHM,算法 哈希256
    }
    
    • 第二段字符串Payload:eyJ1aWQiOjEsInNjb3BlIjo4LCJleHAiOjE3MTU3NDAyMjIsImlhdCI6MTYyOTM0MDIyMn0
    PAYLOAD是数据载体,可以有自定义数据
    {
      "uid": "1234567890" // 自定义数据
    }
    
    • 第三段字符串Signature:wuRsF5wvLHbDF_21Pocas8SeXQ315rgBl6wm1LRL2bQ
    Signature 部分是对前两部分的签名,防止数据篡改。
    

    JWT的类库

    • Java 中的 JWT 有很多类库,关于其优缺点可以在官网查看:https://jwt.io/,这里我们介绍Auth0的JWT的集成使用方式
    Auth0 实现的 com.auth0 / java-jwt / 3.3.0
    Brian Campbell 实现的 org.bitbucket.b_c / jose4j / 0.6.3
    connect2id 实现的 com.nimbusds / nimbus-jose-jwt / 5.7
    Les Hazlewood 实现的 io.jsonwebtoken / jjwt / 0.9.0
    FusionAuth 实现的 io.fusionauth / fusionauth-jwt / 3.1.0
    Vert.x 实现的 io.vertx / vertx-auth-jwt / 3.5.1
    

    具体实现

    JWT配置

    • pom.xml
    <!-- jwt -->
    <dependency>
        <groupId>com.auth0</groupId>
        <artifactId>java-jwt</artifactId>
        <version>3.8.1</version>
    </dependency>
    
    • application.yml
    coisini:
      security:
        jwt-key: coisini
        # 过期时间
        token-expired-in: 86400000
    

    JWT工具类

    • JwtUtil.java
    import com.auth0.jwt.JWT;
    import com.auth0.jwt.JWTVerifier;
    import com.auth0.jwt.algorithms.Algorithm;
    import com.auth0.jwt.exceptions.JWTVerificationException;
    import com.auth0.jwt.interfaces.Claim;
    import com.auth0.jwt.interfaces.DecodedJWT;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    import java.util.*;
    
    /**
     * @Description JWT工具类
     * @author coisini
     * @date Aug 18, 2021
     * @Version 1.0
     */
    @Component
    public class JwtUtil {
    
        /**
         * key
         */
        private static String jwtKey;
    
        /**
         * 过期时间
         */
        private static Integer expiredTimeIn;
    
        /**
         * JWT KEY
         * @param jwtKey
         */
        @Value("${coisini.security.jwt-key}")
        public void setJwtKey(String jwtKey) {
            JwtUtil.jwtKey = jwtKey;
        }
    
        /**
         * 过期时间
         * @param expiredTimeIn
         */
        @Value("${coisini.security.token-expired-in}")
        public void setExpiredTimeIn(Integer expiredTimeIn) {
            JwtUtil.expiredTimeIn = expiredTimeIn;
        }
    
        /**
         * 生成令牌
         * @param uid 用户id
         * @return
         */
        public static String makeToken(Long uid) {
            return JwtUtil.getToken(uid);
        }
    
        /**
         * 获取令牌
         * @param uid 用户id
         * @param scope 权限分级数字
         * @return
         */
        private static String getToken(Long uid) {
            // 指定算法
            Algorithm algorithm = Algorithm.HMAC256(JwtUtil.jwtKey);
    
            Map<String, Date> dateMap = JwtUtil.calculateExpiredIssues();
    
            /**
             * withClaim() 写入自定义数据
             * withExpiresAt() 设置过期时间
             * withIssuedAt() 设置当前时间
             * sign() 签名算法
             */
            return JWT.create()
                        .withClaim("uid", uid)
                        .withExpiresAt(dateMap.get("expiredTime"))
                        .withIssuedAt(dateMap.get("now"))
                        .sign(algorithm);
        }
    
        /**
         * 获取自定义数据
         * @param token
         * @return
         */
        public static Optional<Map<String, Claim>> getClaims(String token) {
            DecodedJWT decodedJWT;
    
            // 指定算法
            Algorithm algorithm = Algorithm.HMAC256(JwtUtil.jwtKey);
            JWTVerifier jwtVerifier = JWT.require(algorithm).build();
    
            try {
                decodedJWT = jwtVerifier.verify(token);
            } catch (JWTVerificationException e) {
                return Optional.empty();
            }
    
            return Optional.of(decodedJWT.getClaims());
        }
    
        /**
         * 验证Token
         * @param token
         * @return
         */
        public static boolean verifyToken(String token) {
            try {
                Algorithm algorithm = Algorithm.HMAC256(JwtUtil.jwtKey);
                JWTVerifier jwtVerifier = JWT.require(algorithm).build();
                jwtVerifier.verify(token);
            } catch (JWTVerificationException e) {
                return false;
            }
    
            return true;
        }
    
        /**
         * 计算过期时间
         * @return
         */
        private static Map<String, Date> calculateExpiredIssues() {
            Map<String, Date> map = new HashMap<>();
            Calendar calendar = Calendar.getInstance();
            Date now = calendar.getTime();
            calendar.add(Calendar.SECOND, JwtUtil.expiredTimeIn);
            // 当前时间
            map.put("now", now);
            // 过期时间
            map.put("expiredTime", calendar.getTime());
            return map;
        }
    
    }
    

    测试接口

    • JwtController.java
    @RestController
    @RequestMapping("/jwt")
    public class JwtController {
    
        /**
         * 获取Token
         * @param id
         * @return
         */
        @GetMapping(value = "/get")
        public String getToken(@RequestParam Long id) {
            return JwtUtil.makeToken(id);
        }
    
        /**
         * 验证Token
         * @param token
         * @return
         */
        @PostMapping("/verify")
        public Map<String, Boolean> verify(@RequestParam String token) {
            Map<String, Boolean> map = new HashMap<>();
            Boolean valid = JwtUtil.verifyToken(token);
            map.put("is_valid", valid);
            return map;
        }
    
    }
    
    • 测试结果

    在这里插入图片描述

    在这里插入图片描述

    • JWT生成的Token应该放在请求头内来传输,后端统一拦截验证,这里留在下篇文章吧。。。

    - End -
    梦想是咸鱼
    关注一下吧
    以上为本篇文章的主要内容,希望大家多提意见,如果喜欢记得点个推荐哦
    作者:Maggieq8324
    本文版权归作者和博客园共有,欢迎转载,转载时保留原作者和文章地址即可。
  • 相关阅读:
    Maximum Depth of Binary Tree
    Single Number
    Merge Two Sorted Lists
    Remove Nth Node From End of List
    Remove Element
    Remove Duplicates from Sorted List
    Add Two Numbers
    编译视频直播点播平台EasyDSS数据排序使用Go 语言 slice 类型排序的实现介绍
    RTMP协议视频直播点播平台EasyDSS在Linux系统中以服务启动报错can’t evaluate field RootPath in type*struct排查
    【解决方案】5G时代RTMP推流服务器/互联网直播点播平台EasyDSS实现360°全景摄像机VR直播
  • 原文地址:https://www.cnblogs.com/maggieq8324/p/15161692.html
Copyright © 2011-2022 走看看