最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

一文了解什么是JWT

 更新時間:2023年05月09日 14:20:23   作者:java技術愛好者  
JSON?WEB?Token是一種基于JSON的、用于在網絡上聲明某種主張的令牌,由三部分組成:?頭信息,?消息體和簽名,下面就一起來了解一下什么是JWT

起源

需要了解一門技術,首先從為什么產生開始說起是最好的。JWT 主要用于用戶登錄鑒權,所以我們從最傳統(tǒng)的 session 認證開始說起。

session認證

眾所周知,http 協(xié)議本身是無狀態(tài)的協(xié)議,那就意味著當有用戶向系統(tǒng)使用賬戶名稱和密碼進行用戶認證之后,下一次請求還要再一次用戶認證才行。因為我們不能通過 http 協(xié)議知道是哪個用戶發(fā)出的請求,所以如果要知道是哪個用戶發(fā)出的請求,那就需要在服務器保存一份用戶信息(保存至 session ),然后在認證成功后返回 cookie 值傳遞給瀏覽器,那么用戶在下一次請求時就可以帶上 cookie 值,服務器就可以識別是哪個用戶發(fā)送的請求,是否已認證,是否登錄過期等等。這就是傳統(tǒng)的 session 認證方式。

session 認證的缺點其實很明顯,由于 session 是保存在服務器里,所以如果分布式部署應用的話,會出現session不能共享的問題,很難擴展。于是乎為了解決 session 共享的問題,又引入了 redis,接著往下看。

token認證

這種方式跟 session 的方式流程差不多,不同的地方在于保存的是一個 token 值到 redis,token 一般是一串隨機的字符(比如UUID),value 一般是用戶ID,并且設置一個過期時間。每次請求服務的時候帶上 token 在請求頭,后端接收到token 則根據 token 查一下 redis 是否存在,如果存在則表示用戶已認證,如果 token 不存在則跳到登錄界面讓用戶重新登錄,登錄成功后返回一個 token 值給客戶端。

優(yōu)點是多臺服務器都是使用 redis 來存取 token,不存在不共享的問題,所以容易擴展。缺點是每次請求都需要查一下redis,會造成 redis 的壓力,還有增加了請求的耗時,每個已登錄的用戶都要保存一個 token 在 redis,也會消耗 redis 的存儲空間。

有沒有更好的方式呢?接著往下看。

什么是JWT

JWT (全稱:Json Web Token)是一個開放標準(RFC 7519),它定義了一種緊湊的、自包含的方式,用于作為 JSON 對象在各方之間安全地傳輸信息。該信息可以被驗證和信任,因為它是數字簽名的。

上面說法比較文縐縐,簡單點說就是一種認證機制,讓后臺知道該請求是來自于受信的客戶端。

首先我們先看一個流程圖:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-7UDQ9SEm-1653810812356)(什么是JWT.assets/v2-bd0aeaf5ba1bad5ab1edff601e9b7a78_720w.jpg)]

流程描述一下:

  • 用戶使用賬號、密碼登錄應用,登錄的請求發(fā)送到 Authentication Server。
  • Authentication Server 進行用戶驗證,然后創(chuàng)建 JWT 字符串返回給客戶端。
  • 客戶端請求接口時,在請求頭帶上 JWT。
  • Application Server 驗證 JWT 合法性,如果合法則繼續(xù)調用應用接口返回結果。

可以看出與token方式有一些不同的地方,就是不需要依賴 redis,用戶信息存儲在客戶端。所以關鍵在于生成 JWT 和解析 JWT 這兩個地方。

JWT的數據結構

JWT 一般是這樣一個字符串,分為三個部分,以 “.” 隔開:

xxxxx.yyyyy.zzzzz

Header

JWT 第一部分是頭部分,它是一個描述 JWT 元數據的 Json 對象,通常如下所示。

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

alg 屬性表示簽名使用的算法,默認為 HMAC SHA256(寫為HS256),typ 屬性表示令牌的類型,JWT 令牌統(tǒng)一寫為JWT。

最后,使用 Base64 URL 算法將上述 JSON 對象轉換為字符串保存。

Payload

JWT 第二部分是 Payload,也是一個 Json 對象,除了包含需要傳遞的數據,還有七個默認的字段供選擇。

  • iss (issuer):簽發(fā)人/發(fā)行人
  • sub (subject):主題
  • aud (audience):用戶
  • exp (expiration time):過期時間
  • nbf (Not Before):生效時間,在此之前是無效的
  • iat (Issued At):簽發(fā)時間
  • jti (JWT ID):用于標識該 JWT

如果自定義字段,可以這樣定義:

{
    //默認字段
    "sub":"主題123",
    //自定義字段
    "name":"java技術愛好者",
    "isAdmin":"true",
    "loginTime":"2021-12-05 12:00:03"
}

需要注意的是,默認情況下 JWT 是未加密的,任何人都可以解讀其內容,因此一些敏感信息不要存放于此,以防信息泄露。

JSON 對象也使用 Base64 URL 算法轉換為字符串后保存,是可以反向反編碼回原樣的,這也是為什么不要在 JWT 中放敏感數據的原因。

Signature

header (base64URL 加密后的)
payload (base64URL 加密后的)
secret

JWT 第三部分是簽名。是這樣生成的,首先需要指定一個 secret,該 secret 僅僅保存在服務器中,保證不能讓其他用戶知道。這個部分需要 base64URL 加密后的 header 和 base64URL 加密后的 payload 使用 . 連接組成的字符串,然后通過header 中聲明的加密算法 進行加鹽secret組合加密,然后就得出一個簽名哈希,也就是Signature,且無法反向解密。

那么 Application Server 如何進行驗證呢?可以利用 JWT 前兩段,用同一套哈希算法和同一個 secret 計算一個簽名值,然后把計算出來的簽名值和收到的 JWT 第三段比較,如果相同則認證通過。

JWT的優(yōu)點

  • json格式的通用性,所以JWT可以跨語言支持,比如Java、JavaScript、PHP、Node等等。
  • 可以利用Payload存儲一些非敏感的信息。
  • 便于傳輸,JWT結構簡單,字節(jié)占用小。
  • 不需要在服務端保存會話信息,易于應用的擴展。

怎么使用JWT

首先引入Maven依賴。

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

創(chuàng)建工具類,用于創(chuàng)建(生成) jwt 字符串和解析 jwt。

@Component
public class JwtUtil {
    @Value("${jwt.secretKey}")
    private String secretKey;
    public String createJWT(String id, String subject, long ttlMillis, Map<String, Object> map) throws Exception {
        JwtBuilder builder = Jwts.builder()
                .setId(id)
                .setSubject(subject) // 發(fā)行者
                .setIssuedAt(new Date()) // 發(fā)行時間
                .signWith(SignatureAlgorithm.HS256, secretKey) // 簽名類型 與 密鑰
                .compressWith(CompressionCodecs.DEFLATE);// 對載荷進行壓縮
        if (!CollectionUtils.isEmpty(map)) {
            builder.setClaims(map);
        }
        if (ttlMillis > 0) {
            builder.setExpiration(new Date(System.currentTimeMillis() + ttlMillis));
        }
        return builder.compact();
    }
    public Claims parseJWT(String jwtString) {
        return Jwts.parser().setSigningKey(secretKey)
                .parseClaimsJws(jwtString)
                .getBody();
    }
}

接著在application.yml配置文件配置jwt.secretKey。

## 用戶生成jwt字符串的secretKey
jwt:
  secretKey: ak47

接著創(chuàng)建一個響應體。

public class BaseResponse {
    private String code;
    private String msg;
    public static BaseResponse success() {
        return new BaseResponse("0", "成功");
    }
    public static BaseResponse fail() {
        return new BaseResponse("1", "失敗");
    }
    //構造器、getter、setter方法
}
public class JwtResponse extends BaseResponse {
    private String jwtData;
    public static JwtResponse success(String jwtData) {
        BaseResponse success = BaseResponse.success();
        return new JwtResponse(success.getCode(), success.getMsg(), jwtData);
    }
    public static JwtResponse fail(String jwtData) {
        BaseResponse fail = BaseResponse.fail();
        return new JwtResponse(fail.getCode(), fail.getMsg(), jwtData);
    }
    //構造器、getter、setter方法
}

接著創(chuàng)建一個UserController:

@RestController
@RequestMapping("/user")
public class UserController {

    @Resource
    private UserService userService;

    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public JwtResponse login(@RequestParam(name = "userName") String userName,
                             @RequestParam(name = "passWord") String passWord){
        String jwt = "";
        try {
            jwt = userService.login(userName, passWord);
            return JwtResponse.success(jwt);
        } catch (Exception e) {
            e.printStackTrace();
            return JwtResponse.fail(jwt);
        }
    }
}

還有UserService:

@Service
public class UserServiceImpl implements UserService {

    @Resource
    private JwtUtil jwtUtil;

    @Resource
    private UserMapper userMapper;

    @Override
    public String login(String userName, String passWord) throws Exception {
        //登錄驗證
        User user = userMapper.findByUserNameAndPassword(userName, passWord);
        if (user == null) {
            return null;
        }
        //如果能查出,則表示賬號密碼正確,生成jwt返回
        String uuid = UUID.randomUUID().toString().replace("-", "");
        HashMap<String, Object> map = new HashMap<>();
        map.put("name", user.getName());
        map.put("age", user.getAge());
        return jwtUtil.createJWT(uuid, "login subject", 0L, map);
    }
}

還有UserMapper.xml:

@Mapper
public interface UserMapper {
    User findByUserNameAndPassword(@Param("userName") String userName, @Param("passWord") String passWord);

}

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="io.github.yehongzhi.jwtdemo.mapper.UserMapper">
    <select id="findByUserNameAndPassword" resultType="io.github.yehongzhi.jwtdemo.model.User">
        select * from user where user_name = #{userName} and pass_word = #{passWord}
    </select>
</mapper>

user 表結構如下:

啟動項目,然后用 postman 請求 login 接口。

返回的 jwt 字符串如下:

eyJhbGciOiJIUzI1NiIsInppcCI6IkRFRiJ9.eNqqVspLzE1VslJ6OnHFsxnzX67coKSjlJgOFDEzqAUAAAD__w.qib2DrjRKcFnY77Cuh_b1zSzXfISOpCA-g8PlAZCWoU

接著我們寫一個接口接收這個 jwt,并做驗證。

@RestController
@RequestMapping("/jwt")
public class TestController {
    @Resource
    private JwtUtil jwtUtil;
    @RequestMapping("/test")
    public Map<String, Object> test(@RequestParam("jwt") String jwt) {
        //這個步驟可以使用自定義注解+AOP編程做解析jwt的邏輯,這里為了簡便就直接寫在controller里
        Claims claims = jwtUtil.parseJWT(jwt);
        String name = claims.get("name", String.class);
        String age = claims.get("age", String.class);
        HashMap<String, Object> map = new HashMap<>();
        map.put("name", name);
        map.put("age", age);
        map.put("code", "0");
        map.put("msg", "請求成功");
        return map;
    }
}

像這樣能正常解析成功的話,就表示該用戶登錄未過期,并且已認證成功,所以可以正常調用服務。那么有人會問了,這個 jwt 字符串能不能被偽造呢?

除非你知道 secretKey,否則是不能偽造的。比如客戶端隨便猜一個 secretKey 的值,然后偽造一個jwt:

eyJhbGciOiJIUzI1NiIsInppcCI6IkRFRiJ9.eNqqVspLzE1VslJ6OnHFsxnzX67coKSjlJgOFDEzqAUAAAD__w.bHr9p3-t2qR4R50vifRVyaYYImm2viZqiTlDdZHmF5Y

然后傳進去解析,會報以下錯誤:

還記得原理吧,是根據前面兩部分(Header、Payload)加上 secretKey 使用 Header 指定的哈希算法計算出第三部分(Signature),所以可以看出最關鍵就是 secretKey。secretKey只有服務端自己知道,所以客戶端不知道 secretKey 的值是偽造不了jwt字符串的。

總結

最后講講 JWT 的缺點,因為任何技術都不是完美的,所以我們得用辯證思維去看待任何一項技術。

安全性沒法保證,所以 jwt 里不能存儲敏感數據。因為 jwt 的 payload 并沒有加密,只是用 Base64 編碼而已。無法中途廢棄。因為一旦簽發(fā)了一個 jwt,在到期之前始終都是有效的,如果用戶信息發(fā)生更新了,只能等舊的 jwt 過期后重新簽發(fā)新的 jwt。續(xù)簽問題。當簽發(fā)的 jwt 保存在客戶端,客戶端一直在操作頁面,按道理應該一直為客戶端續(xù)長有效時間,否則當 jwt有效期到了就會導致用戶需要重新登錄。那么怎么為 jwt 續(xù)簽呢?最簡單粗暴就是每次簽發(fā)新的 jwt,但是由于過于暴力,會影響性能。如果要優(yōu)雅一點,又要引入 Redis 解決,但是這又把無狀態(tài)的 jw t硬生生變成了有狀態(tài)的,違背了初衷。

所以印證了那句話,沒有最好的技術,只有適合的技術。

到此這篇關于一文了解什么是JWT的文章就介紹到這了,更多相關JWT內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

最新評論

临夏市| 定边县| 化州市| 包头市| 尖扎县| 年辖:市辖区| 滦平县| 庄浪县| 东兰县| 海伦市| 怀安县| 蒙阴县| 清水河县| 类乌齐县| 建湖县| 金川县| 平山县| 佛山市| 闻喜县| 莱州市| 龙里县| 绥阳县| 吉木乃县| 天全县| 渝北区| 夏津县| 延长县| 高州市| 芜湖市| 凤台县| 阿克| 贡山| 滦平县| 万宁市| 上林县| 隆昌县| 襄樊市| 昌黎县| 定安县| 合肥市| 河北省|