動(dòng)力節(jié)點(diǎn)SpringSecurity框架視頻教程-springsecurity+

Base64及JWT學(xué)習(xí)筆記整理
老師的課件分享給大家咯~
1 base64編碼
1.1 什么是Base64
所謂Base64,就是說選出64個(gè)字符:小寫字母a-z、大寫字母A-Z、數(shù)字0-9、符號(hào)"+"、"/"(再加上作為墊字的"=",實(shí)際上是使用65個(gè)字符),作為一個(gè)基本字符集。然后,其他所有符號(hào)都轉(zhuǎn)換成這個(gè)字符集中的字符。
1.2 linux base64命令
1.2.1 Linux下用base64命令編解碼字符串
編碼:
echo -n 'Hello World' | base64
SGVsbG8gV29ybGQ=
解碼:
echo -n 'SGVsbG8gV29ybGQ=' | base64 -d
Hello World
備注:
??echo 命令是帶換行符的
??echo -n 不換行輸出
??echo -n '{"alg":"HS256","typ":"JWT"}' | base64
1.2.2 base64 編解碼文件
#base64編碼
# base64 待編碼的文件名 > 編碼后的文件名
base64 ?1.mp3 > mymp3
#base64 解碼
#base64 -d 待解碼的文件名 >解碼后的文件名
base64 -d mymp3>88.mp3
1.3 Base64和Base64Url 的區(qū)別
Base64Url是一種在Base64的基礎(chǔ)上編碼形成新的編碼方式,為了編碼能在網(wǎng)絡(luò)中安全順暢傳輸,需要對(duì)Base64進(jìn)行的編碼,特別是互聯(lián)網(wǎng)中。
Base64Url 編碼的流程:
?1、明文使用BASE64進(jìn)行編碼
?2、在Base64編碼的基礎(chǔ)上進(jìn)行以下的處理:
????1)去除尾部的"="
????2)把"+"替換成"-"
????3)斜線"/"替換成下劃線"_"
2 跨域認(rèn)證問題和JWT 實(shí)現(xiàn)登錄原理圖
2.1 跨域認(rèn)證問題
互聯(lián)網(wǎng)服務(wù)離不開用戶認(rèn)證。一般流程是下面這樣。
l?用戶向服務(wù)器發(fā)送用戶名和密碼。
l?服務(wù)器驗(yàn)證通過后,在當(dāng)前對(duì)話(session)里面保存相關(guān)數(shù)據(jù),比如用戶角色、登錄時(shí)間等等。
l?服務(wù)器向用戶返回一個(gè) jsession_id,寫入用戶的 Cookie。
l?用戶隨后的每一次請(qǐng)求,都會(huì)通過 Cookie,將 session_id 傳回服務(wù)器。
l?服務(wù)器收到 session_id,找到前期保存的數(shù)據(jù),由此得知用戶的身份。
這種模式的問題在于,擴(kuò)展性(scaling)不好。單機(jī)當(dāng)然沒有問題,如果是服務(wù)器集群,或者是跨域的服務(wù)導(dǎo)向架構(gòu),就要求 session 數(shù)據(jù)共享,每臺(tái)服務(wù)器都能夠讀取 session。
舉例來說,A 網(wǎng)站和 B 網(wǎng)站是同一家公司的關(guān)聯(lián)服務(wù)?,F(xiàn)在要求,用戶只要在其中一個(gè)網(wǎng)站登錄,再訪問另一個(gè)網(wǎng)站就會(huì)自動(dòng)登錄,請(qǐng)問怎么實(shí)現(xiàn)?
一種解決方案是 session 數(shù)據(jù)持久化,寫入數(shù)據(jù)庫或別的持久層。各種服務(wù)收到請(qǐng)求后,都向持久層請(qǐng)求數(shù)據(jù)。這種方案的優(yōu)點(diǎn)是架構(gòu)清晰,缺點(diǎn)是工程量比較大。另外,持久層萬一掛了,就會(huì)單點(diǎn)失敗。
另一種方案是服務(wù)器索性不保存 session 數(shù)據(jù)了,所有數(shù)據(jù)都保存在客戶端,每次請(qǐng)求都發(fā)回服務(wù)器。JWT 就是這種方案的一個(gè)代表。?服務(wù)器不存數(shù)據(jù),客戶端存,服務(wù)器解析就行了
2.2 JWT 實(shí)現(xiàn)登錄原理圖

說明:
JWT只通過算法實(shí)現(xiàn)對(duì)Token合法性的驗(yàn)證,不依賴數(shù)據(jù)庫,Memcached的等存儲(chǔ)系統(tǒng),因此可以做到跨服務(wù)器驗(yàn)證,只要密鑰和算法相同,不同服務(wù)器程序生成的Token可以互相驗(yàn)證。
3 JWT學(xué)習(xí)
3.1 簡介
JSON Web Token(JWT)是一個(gè)開放標(biāo)準(zhǔn)(RFC 7519),它定義了一種緊湊且獨(dú)立的方式,用于在各方之間作為JSON對(duì)象安全地傳輸信息。 此信息可以通過數(shù)字簽名進(jìn)行驗(yàn)證和信任。 JWT可以使用密鑰(使用HMAC算法)或使用RSA或ECDSA的公鑰/私鑰對(duì)進(jìn)行簽名。
??官方網(wǎng)址:https://jwt.io/
??調(diào)試頁面:https://jwt.io/
??學(xué)習(xí)文檔:https://jwt.io/introduction/
3.2 用途
??授權(quán):這是我們使用JWT最廣泛的應(yīng)用場(chǎng)景。一次用戶登錄,后續(xù)請(qǐng)求將會(huì)包含JWT,對(duì)于那些合法的token,允許用戶連接路由,服務(wù)和資源。目前JWT廣泛應(yīng)用在SSO(Single Sign On)(單點(diǎn)登錄)上。因?yàn)樗麄冮_銷很小并且可以在不同領(lǐng)域輕松使用。
??信息交換:JSON Web Token是一種在各方面之間安全信息傳輸?shù)暮玫姆绞?因?yàn)镴WT可以簽名 - 例如,使用公鑰/私鑰對(duì) - 您可以確定發(fā)件人是他們所說的人。 此外,由于使用標(biāo)頭和有效負(fù)載計(jì)算簽名,您還可以驗(yàn)證內(nèi)容是否未被篡改。
3.3 JWT組成
一個(gè)JWT由三部分組成,各部分以點(diǎn)分隔:
Header(頭部)-----base64Url編碼的Json字符串
Payload(載荷)---base64url編碼的Json字符串
Signature(簽名)---使用指定算法,通過Header和Playload加鹽計(jì)算的字符串
一個(gè)JWT看起來像下面這樣:
xxxxx.yyyyy.zzzzz
?
下面這樣:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
3.3.1 Header
此部分有兩部分組成:
??一部分是token的類型,目前只能是JWT
??另一部分是簽名算法,比如HMAC 、 SHA256 、 RSA
示例:
{
??"alg":"HS256",
??"typ":"JWT"
}
base64編碼命令:
echo -n '{"alg":"HS256","typ":"JWT"}' | base64
?
3.3.2 Payload
token的第二部分是payload(有效負(fù)載),其中包含claims(聲明)。Claims是關(guān)于一個(gè)實(shí)體(通常是用戶)和其他數(shù)據(jù)類型的聲明。
claims有三種類型:registered,public,and private claims。
?
??Registered(已注冊(cè)的聲明):這些是一組預(yù)定義聲明,不是強(qiáng)制性的,但建議使用,以提供一組有用的,可互操作的聲明。 其中一些是:iss(發(fā)行人),exp(到期時(shí)間),sub(主題),aud(觀眾)and others。(請(qǐng)注意,聲明名稱只有三個(gè)字符,因?yàn)镴WT意味著緊湊。)
JWT 規(guī)定了7個(gè)官方字段,供選用。
iss (issuer):簽發(fā)人
exp (expiration time):過期時(shí)間
sub (subject):主題
aud (audience):受眾
nbf (Not Before):生效時(shí)間
iat (Issued At):簽發(fā)時(shí)間
jti (JWT ID):編號(hào)
除了官方字段,你還可以在這個(gè)部分定義私有字段,下面就是一個(gè)例子。
{
??"sub": "1234567890",
??"name": "John Doe",
??"admin": true
}
注意,JWT 默認(rèn)是不加密的,任何人都可以讀到,所以不要把秘密信息(密碼,手機(jī)號(hào)等)放在這個(gè)部分。
這個(gè) JSON 對(duì)象也要使用 Base64URL 算法轉(zhuǎn)成字符串。
?
?
?
??Public(公開聲明):這些可以由使用JWT的人隨意定義。 但為避免沖突,應(yīng)在IANA JSON Web Token Registry中定義它們,或者將其定義為包含防沖突命名空間的URI。
?
??private (私人聲明):這些聲明是為了在同意使用它們的各方之間共享信息而創(chuàng)建的,并且既不是注冊(cè)聲明也不是公開聲明。
示例:
{
??"sub": "1234567890",
??"name": "John Doe",
??"admin": true
}
3.3.3 Signature(保證數(shù)據(jù)安全性的)
Signature 部分是對(duì)前兩部分的簽名,防止數(shù)據(jù)篡改。
首先,需要指定一個(gè)密鑰(secret)。這個(gè)密鑰只有服務(wù)器才知道,不能泄露給用戶。然后,使用 Header 里面指定的簽名算法(默認(rèn)是 HMAC SHA256),按照下面的公式產(chǎn)生簽名。
?HMACSHA256(
??base64UrlEncode(header) + "." +
??base64UrlEncode(payload),
??secret)
算出簽名以后,把 Header、Payload、Signature 三個(gè)部分拼成一個(gè)字符串,每個(gè)部分之間用"點(diǎn)"(.)分隔,就可以返回給用戶。
?
示例:
HMACSHA256(
??base64UrlEncode(header) + "." +
??base64UrlEncode(payload),
??secret)
3.4 JWT 的使用方式【重點(diǎn)】
客戶端收到服務(wù)器返回的 JWT,可以儲(chǔ)存在 Cookie 里面,也可以儲(chǔ)存在 localStorage。
此后,客戶端每次與服務(wù)器通信,都要帶上這個(gè) JWT。你可以把它放在 Cookie 里面自動(dòng)發(fā)送,但是這樣不能跨域,所以更好的做法是放在 HTTP 請(qǐng)求的頭信息Authorization字段里面。
Authorization: Bearer jwt
另一種做法是,跨域的時(shí)候,JWT 就放在 POST 請(qǐng)求的數(shù)據(jù)體里面。
3.5 JWT 的幾個(gè)特點(diǎn)
JWT 默認(rèn)是不加密,但也是可以加密的。生成原始 Token 以后,可以用密鑰再加密一次。
JWT 不加密的情況下,不能將秘密數(shù)據(jù)寫入 JWT。
JWT 不僅可以用于認(rèn)證,也可以用于交換信息。有效使用 JWT,可以降低服務(wù)器查詢數(shù)據(jù)庫的次數(shù)。
JWT 的最大缺點(diǎn)是,由于服務(wù)器不保存 session 狀態(tài),因此無法在使用過程中廢止某個(gè) token,或者更改 token 的權(quán)限。也就是說,一旦 JWT 簽發(fā)了,在到期之前就會(huì)始終有效,除非服務(wù)器部署額外的邏輯(JWT的登出問題)。就是因?yàn)榉?wù)端無狀態(tài)了
正常情況下 修改了密碼后就會(huì)跳轉(zhuǎn)到登錄頁面 :修改成功后清空瀏覽器保存的token了
后端怎么玩? 因?yàn)榉?wù)端不保留token 我用之前的token 還是可以繼續(xù)訪問的
從有狀態(tài)(后端也會(huì)存一個(gè))的變成無狀態(tài)的了
我們就要把它從無狀態(tài)再變成有狀態(tài)了
JWT 本身包含了認(rèn)證信息,一旦泄露,任何人都可以獲得該令牌的所有權(quán)限。為了減少盜用,JWT 的有效期應(yīng)該設(shè)置得比較短。對(duì)于一些比較重要的權(quán)限,使用時(shí)應(yīng)該再次對(duì)用戶進(jìn)行認(rèn)證。
為了減少盜用,JWT 不應(yīng)該使用 HTTP?80?協(xié)議明碼傳輸,要使用 HTTPS?443?協(xié)議傳輸。
?
我們頒發(fā)一個(gè)令牌?用戶名稱 用戶的權(quán)限信息??這個(gè)令牌2個(gè)小時(shí)有效
Jwt只要能解析 就認(rèn)為你是可用的??做不了 登出?后端不存儲(chǔ)用戶信息了 后端無狀態(tài)了
4 Java類庫



5 java中使用jwt
5.1 新建maven工程jwt-learn1
5.2 添加依賴
<!-- 添加jwt的依賴 -->
<dependency>
????<groupId>com.auth0</groupId>
????<artifactId>java-jwt</artifactId>
????<version>3.11.0</version>
</dependency>
5.3 編寫功能類
package com.powernode.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
?* 用于生成和解析JWT
?*/
public class JWTUtils {
????/**
?????* 聲明一個(gè)秘鑰
?????*/
????private static final String SECRET = "leige";
????/**
?????* 生成JWT
?????*
?????* @param userId ??用戶編號(hào)
?????* @param username 用戶名
?????* @param auth ????用戶權(quán)限
?????*/
????public String createToken(Integer userId, String username, List<String> auth) {
????????//得到當(dāng)前的系統(tǒng)時(shí)間
????????Date currentDate = new Date();
????????//根據(jù)當(dāng)前時(shí)間計(jì)算出過期時(shí)間 定死為5分鐘
????????Date expTime = new Date(currentDate.getTime() + (1000 * 60 * 5));
????????//組裝頭數(shù)據(jù)
????????Map<String, Object> header = new HashMap<>();
????????header.put("alg", "HS256");
????????header.put("typ", "JWT");
????????return JWT.create()
????????????????.withHeader(header) //頭
????????????????.withClaim("userId", userId) //自定義數(shù)據(jù)
????????????????.withClaim("username", username) //自定義數(shù)據(jù)
????????????????.withClaim("auth", auth) //自定義數(shù)據(jù)
????????????????.withIssuedAt(currentDate) //創(chuàng)建時(shí)間
????????????????.withExpiresAt(expTime)//過期時(shí)間
????????????????.sign(Algorithm.HMAC256(SECRET));
????}
????/**
?????* 驗(yàn)證JWT并解析
?????*
?????* @param token 要驗(yàn)證的jwt的字符串
?????*/
????public static Boolean verifyToken(String token) {
????????try{
????????????// 使用秘鑰創(chuàng)建一個(gè)解析對(duì)象
????????????JWTVerifier jwtVerifier=JWT.require(Algorithm.HMAC256(SECRET)).build();
????????????//驗(yàn)證JWT
????????????DecodedJWT decodedJWT = jwtVerifier.verify(token);
// ???????????String header = decodedJWT.getHeader();
// ???????????String payload = decodedJWT.getPayload();
// ???????????String signature = decodedJWT.getSignature();
// ???????????System.out.println("header = " + header);
// ???????????System.out.println("payload = " + payload);
// ???????????System.out.println("signature = " + signature);
//
// ???????????Date expiresAt = decodedJWT.getExpiresAt();
// ???????????System.out.println("expiresAt = " + expiresAt);
// ???????????Claim userId = decodedJWT.getClaim("userId");
// ???????????System.out.println("userId = " + userId.asInt());
// ???????????Claim username = decodedJWT.getClaim("username");
// ???????????System.out.println("username = " + username.asString());
// ???????????Claim auth = decodedJWT.getClaim("auth");
// ???????????System.out.println("auth = " + auth.asList(String.class));
????????????return true;
????????}catch (TokenExpiredException e){
????????????e.printStackTrace();
????????}
????????return false;
????}
????/**
?????* 獲取JWT里面相前的用戶編號(hào)
?????*/
????public Integer getUserId(String token){
????????try{
????????????// 使用秘鑰創(chuàng)建一個(gè)解析對(duì)象
????????????JWTVerifier jwtVerifier=JWT.require(Algorithm.HMAC256(SECRET)).build();
????????????//驗(yàn)證JWT
????????????DecodedJWT decodedJWT = jwtVerifier.verify(token);
????????????Claim userId = decodedJWT.getClaim("userId");
????????????return userId.asInt();
????????}catch (TokenExpiredException e){
????????????e.printStackTrace();
????????}
????????return null;
????}
????/**
?????* 獲取JWT里面相前的用戶名
?????*/
????public static String getUsername(String token){
????????try{
????????????// 使用秘鑰創(chuàng)建一個(gè)解析對(duì)象
????????????JWTVerifier jwtVerifier=JWT.require(Algorithm.HMAC256(SECRET)).build();
????????????//驗(yàn)證JWT
????????????DecodedJWT decodedJWT = jwtVerifier.verify(token);
????????????Claim username = decodedJWT.getClaim("username");
????????????return username.asString();
????????}catch (TokenExpiredException e){
????????????e.printStackTrace();
????????}
????????return null;
????}
????/**
?????* 獲取JWT里面相前權(quán)限
?????*/
????public List<String> getAuth(String token){
????????try{
????????????// 使用秘鑰創(chuàng)建一個(gè)解析對(duì)象
????????????JWTVerifier jwtVerifier=JWT.require(Algorithm.HMAC256(SECRET)).build();
????????????//驗(yàn)證JWT
????????????DecodedJWT decodedJWT = jwtVerifier.verify(token);
????????????Claim auth = decodedJWT.getClaim("auth");
????????????return auth.asList(String.class);
????????}catch (TokenExpiredException e){
????????????e.printStackTrace();
????????}
????????return null;
????}
}
5.4 寫主類測(cè)試一下
6 JWT的總結(jié)
JWT就是一個(gè)加密的帶用戶信息的字符串,沒學(xué)習(xí)JWT之前,我們?cè)陧?xiàng)目中都是返回一個(gè)基本的字符串,然后請(qǐng)求時(shí)帶上這個(gè)字符串,再從session或者redis中(共享session)獲取當(dāng)前用戶,學(xué)過JWT以后我們可以把用戶信息直接放在字符串返回給前端,然后用戶請(qǐng)求時(shí)帶過來,我們是在服務(wù)器進(jìn)行解析拿到當(dāng)前用戶,這就是兩種登錄方式,這兩種方式有各自的優(yōu)缺點(diǎn)。
?