日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

文章已收錄到我的Github精選,歡迎Star:https://github.com/yehongzhi/learningSummary

起源

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

session認證

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

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

token認證

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

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

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

什么是JWT

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

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

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

不懂就學,什么是JWT?

 

流程描述一下:

  1. 用戶使用賬號、密碼登錄應用,登錄的請求發送到Authentication Server。
  2. Authentication Server進行用戶驗證,然后創建JWT字符串返回給客戶端。
  3. 客戶端請求接口時,在請求頭帶上JWT。
  4. Application Server驗證JWT合法性,如果合法則繼續調用應用接口返回結果。

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

JWT的數據結構

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

xxxxx.yyyyy.zzzzz
不懂就學,什么是JWT?

 

Header

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

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

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

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

Payload

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

分別是,iss:發行人、exp:到期時間、sub:主題、aud:用戶、nbf:在此之前不可用、iat:發布時間、jti:JWT ID用于標識該JWT。

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

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

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

JSON對象也使用Base64 URL算法轉換為字符串保存。

Signature

JWT第三部分是簽名。是這樣生成的,首先需要指定一個secret,該secret僅僅保存在服務器中,保證不能讓其他用戶知道。然后使用Header指定的算法對Header和Payload進行計算,然后就得出一個簽名哈希。也就是Signature。

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

JWT的優點

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

怎么使用JWT

首先引入Maven依賴。

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

創建工具類,用于創建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()
                .setSubject(null) // 發行者
                .setId(id)
                .setSubject(subject)
                .setIssuedAt(new Date()) // 發行時間
                .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

接著創建一個響應體。

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方法
}

接著創建一個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表結構如下:

不懂就學,什么是JWT?

 

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

不懂就學,什么是JWT?

 

返回的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?

 

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

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

eyJhbGciOiJIUzI1NiIsInppcCI6IkRFRiJ9.eNqqVspLzE1VslJ6OnHFsxnzX67coKSjlJgOFDEzqAUAAAD__w.bHr9p3-t2qR4R50vifRVyaYYImm2viZqiTlDdZHmF5Y

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

不懂就學,什么是JWT?

 

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

總結

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

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

所以印證了那句話,沒有最好的技術,只有適合的技術。感謝大家的閱讀,希望看完之后能對你有所收獲。

覺得有用就點個贊吧,你的點贊是我創作的最大動力~

我是一個努力讓大家記住的程序員。我們下期再見!!!

能力有限,如果有什么錯誤或者不當之處,請大家批評指正,一起學習交流!

分享到:
標簽:JWT
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定