SpringBoot整合JWT Token

在文章之前,我們先介紹幾個概念

OAuth2、JWT,Spring Security、Spring Security OAuth2

OAuth2:Open Authorization,是一種授權協議,是規範,不是技術實現。

JWT:JSON Web Token,是一種具體的Token實現框架。

Spring Security:前身是 Acegi Security ,能夠爲 Spring企業應用系統提供聲明式的安全訪問控制。該框架老古董了。

Spring Security OAuth2:Spring 對 OAuth2 開源實現(與Spring Cloud技術棧無縫集成)。

目前用的最多是JWT,因此本文也是圍繞JWT來實現。

1.什麼是JWT

JWT的原則是在服務器身份驗證之後,將生成一個JSON對象並將其發送回用戶。當用戶與服務器通信時,客戶在請求中發回JSON對象。服務器僅依賴於這個JSON對象來標識用戶。

2.JWT的組成

JWT TOKEN分爲三部分:header.payload.signature,因此JWT通常如下表示xxx.yyyyy.zz

2.1header

頭部包含了兩部分,token 類型和採用的加密算法。alg字段指定了生成signature的算法,默認值爲HS256,typ默認值爲JWT。

如果你使用Node.js,可以用Node.js的包base64url來得到這個字符串。Base64是一種編碼,也就是說,它是可以被翻譯回原來的樣子來的。它並不是一種加密過程

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

2.2payload

這部分就是我們存放信息的地方了,你可以把用戶 ID 等信息放在這裏,JWT 規範裏面對這部分有進行了比較詳細的介紹,常用的由 iss(簽發者),exp(過期時間),sub(面向的用戶),aud(接收方),iat(簽發時間)。同樣的,它會使用 Base64 編碼組成 JWT 結構的第二部分。

{
    "iss": "admin",          //該JWT的簽發者
    "iat": 1535967430,        //簽發時間
    "exp": 1535974630,        //過期時間
    "nbf": 1535967430,         //該時間之前不接收處理該Token
    "sub": "www.admin.com",   //面向的用戶
    "jti": "9f10e796726e332cec401c569969e13e"   //該Token唯一標識
}

2.3Signature

String secret = "123456";//祕鑰

HMACSHA256( base64UrlEncode(header) + "." +base64UrlEncode(payload),secret

前面兩部分都是使用 Base64 進行編碼的,即前端可以解開知道里面的信息。Signature 需要使用編碼後的 header 和 payload 以及我們提供的一個密鑰,然後使用 header 中指定的簽名算法(HS256)進行簽名。簽名的作用是保證 JWT 沒有被篡改過。

2.4JWT的樣式

三個部分通過.連接在一起就是我們的 JWT 了,它可能長這個樣子,長度貌似和你的加密算法和私鑰有關係。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjU3ZmVmMTY0ZTU0YWY2NGZmYzUzZGJkNSIsInhzcmYiOiI0ZWE1YzUwOGE2NTY2ZTc2MjQwNTQzZjhmZWIwNmZkNDU3Nzc3YmUzOTU0OWM0MDE2NDM2YWZkYTY1ZDIzMzBlIiwiaWF0IjoxNDc2NDI3OTMzfQ.PA3QjeyZSUh7H0GfE0vJaKW4LjKJuC3dVLQiY4hii8s

其實到這一步可能就有人會想了,HTTP 請求總會帶上 token,這樣這個 token 傳來傳去佔用不必要的帶寬啊。如果你這麼想了,那你可以去了解下 HTTP2,HTTP2 對頭部進行了壓縮,相信也解決了這個問題。

2.5簽名的目的

最後一步簽名的過程,實際上是對頭部以及負載內容進行簽名,防止內容被竄改。如果有人對頭部以及負載的內容解碼之後進行修改,再進行編碼,最後加上之前的簽名組合形成新的JWT的話,那麼服務器端會判斷出新的頭部和負載形成的簽名和JWT附帶上的簽名是不一樣的。如果要對新的頭部和負載進行簽名,在不知道服務器加密時用的密鑰的話,得出來的簽名也是不一樣的。

2.6信息暴露

在這裏大家一定會問一個問題:Base64是一種編碼,是可逆的,那麼我的信息不就被暴露了嗎?

是的。所以,在JWT中,不應該在負載裏面加入任何敏感的數據。在上面的例子中,我們傳輸的是用戶的User ID。這個值實際上不是什麼敏感內容,一般情況下被知道也是安全的。但是像密碼這樣的內容就不能被放在JWT中了。如果將用戶的密碼放在了JWT中,那麼懷有惡意的第三方通過Base64解碼就能很快地知道你的密碼了。

因此JWT適合用於向Web應用傳遞一些非敏感信息。JWT還經常用於設計用戶認證和授權系統,甚至實現Web應用的單點登錄。

流程參考如下:資料引用於https://www.cnblogs.com/wenqiangit/p/9592132.html

  1. 首先,前端通過Web表單將自己的用戶名和密碼發送到後端的接口。這一過程一般是一個HTTP POST請求。建議的方式是通過SSL加密的傳輸(https協議),從而避免敏感信息被嗅探。
  2. 後端覈對用戶名和密碼成功後,將用戶的id等其他信息作爲JWT Payload(負載),將其與頭部分別進行Base64編碼拼接後簽名,形成一個JWT。形成的JWT就是一個形同lll.zzz.xxx的字符串。
  3. 後端將JWT字符串作爲登錄成功的返回結果返回給前端。前端可以將返回的結果保存在localStorage或sessionStorage上,退出登錄時前端刪除保存的JWT即可。
  4. 前端在每次請求時將JWT放入HTTP Header中的Authorization位。(解決XSS和XSRF問題)
  5. 後端檢查是否存在,如存在驗證JWT的有效性。例如,檢查簽名是否正確;檢查Token是否過期;檢查Token的接收方是否是自己(可選)。
  6. 驗證通過後後端使用JWT中包含的用戶信息進行其他邏輯操作,返回相應結果。

3JWT TOKEN的實現

3.1引入依賴

    <!--JWT Token-->
        <dependency>
                <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>

3.2Token工具類

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Description: Token工具類
 * @author hutao
 * @mail [email protected]
 * @date 2020年1月11日
 */
public class TokenUtil {
    
    

    /**
     * 簽名祕鑰
     */
    public static final String SECRET = "123456";
    /**
     * 簽發地
     */
    public static final String ISSUER  = "hutao.com";
    /**
     * 過期時間
     */
    public static final long TTLMILLIS = 3600*1000*60;

    /**
     * @Description: 生成Token令牌
     * @author hutao
     * @mail [email protected]
     * @date 2020年1月11日
     * @param claims    私有聲明
     * @param id        編號
     * @param issuer    該JWT的簽發者,是否使用是可選的
     * @param subject   該JWT所面向的用戶,是否使用是可選的;
     * @param ttlMillis 有效時間
     * @return token String
     */
    public static String generateJwtToken(Map<String,Object> claims,String id, String issuer, String subject, long ttlMillis) {

        //指定簽名的時候使用的簽名算法,也就是header那部分,jjwt已經將這部分內容封裝好了
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        //生成JWT的時間
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);

        //創建payload的私有聲明(根據特定的業務需要添加,如果要拿這個做驗證,一般是需要和jwt的接收方提前溝通好驗證方式的)
/*        Map<String,Object> claims = new ConcurrentHashMap<String,Object>();
        claims.put("aaaa", "aaaa");
        claims.put("bbbb", "bbbb");
        claims.put("cccc", "cccc");*/
        
        // 通過祕鑰簽名JWT
        byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(SECRET);
        Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());

        // 讓我們設置JWT聲明
        JwtBuilder builder = Jwts.builder();
        if(claims!=null) {
            builder.setClaims(claims);//如果有私有聲明,一定要先設置這個自己創建的私有的聲明,這個是給builder的claim賦值,一旦寫在標準的聲明賦值之後,就是覆蓋了那些標準的聲明的
        }
        builder.setId(id);//jti(JWT ID):是JWT的唯一標識,根據業務需要,這個可以設置爲一個不重複的值,主要用來作爲一次性token,從而回避重放攻擊。
        builder.setIssuedAt(now);////iat: jwt的簽發時間
        builder.setSubject(subject);//sub(Subject):代表這個JWT的主體,即它的所有人,這個是一個json格式的字符串,可以存放什麼userid,roldid之類的,作爲什麼用戶的唯一標誌。
        builder.setIssuer(issuer);//iss(issuer):簽發地
        builder.signWith(signatureAlgorithm, signingKey); //設置簽名使用的簽名算法和簽名使用的祕鑰

        // 如果已指定,則添加到期時間
        if (ttlMillis >= 0) {
            long expMillis = nowMillis + ttlMillis;
            Date exp = new Date(expMillis);
            //設置過期時間
            builder.setExpiration(exp);
        }

        // 構建JWT並將其序列化爲一個緊湊的url安全字符串
        return builder.compact();
    }


    /**
     * @Description: 解析Token
     * @author hutao
     * @mail [email protected]
     * @date 2020年1月11日
     */
    public static Claims parseJWT(String jwt) {
        // 如果這行代碼不是簽名的JWS(如預期),那麼它將拋出異常
        Claims claims = Jwts.parser()
                .setSigningKey(DatatypeConverter.parseBase64Binary(SECRET))
                .parseClaimsJws(jwt).getBody();
        return claims;
    }
    public static void main(String[] args) {

        String token = TokenUtil.generateJwtToken(null,"100",ISSUER,"hutao",TTLMILLIS);
        
        System.out.println(token);

        Claims claims = TokenUtil.parseJWT(token);

        System.out.println(claims);

    }
}
 

發佈了7 篇原創文章 · 獲贊 12 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章