JAVA造輪子之-JWT跨域管理token

網上關於jwt建立token的資料不少,有講原理的,也有深度解析的,也存在不少的示例,但大部份示例,把簡單的方式搞的複雜化,所以稍作整理提供一個可用的工具類;java

JSON Web Token(縮寫 JWT)是目前最流行的跨域認證解決方案。
JWT 的三個部分依次以下
Header(頭部):是一個 JSON 對象,描述 JWT 的元數據{ "alg": "HS256", typ": "JWT" }
Payload(負載):也是一個 JSON 對象,用來存放實際須要傳遞的數據,JWT 規定了7個官方字段:
    iss (issuer):簽發人
    exp (expiration time):過時時間
    sub (subject):主題
    aud (audience):受衆
    nbf (Not Before):生效時間
    iat (Issued At):簽發時間
    jti (JWT ID):編號
Signature(簽名):對前兩部分的簽名,防止數據篡改

1.JWT中Header頭和Payload有效載荷序列化的算法都用到了Base64URL,簽名哈希部分是對Header與Payload兩部分數據簽名
2.客戶端接收服務器返回的JWT,將其存儲在Cookie或localStorage中,客戶端將在與服務器交互中都會帶JWT,將它放入HTTP請求的Header Authorization字段中
3.JWT的最大缺點是服務器不保存會話狀態,因此在使用期間不可能取消令牌或更改令牌的權限
4.JWT自己包含認證信息,所以一旦信息泄露,任何人均可以得到令牌的全部權限
5.JWT不建議使用HTTP協議來傳輸代碼,而是使用加密的HTTPS協議進行傳輸

pom.xml引入依賴包web

<!-- JWT Token驗證機制 -->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.8.1</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
</dependency>

工具類算法

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.Claim;
import org.apache.commons.lang3.time.DateUtils;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @Description JWT跨域管理token工具類
 * @Author JL
 * @Date 2019/08/08
 * @Version V1.0
 */
public class JwtTokenUtils {


    /**
     * 加密密鑰
     */
    private static final String SECRET = "wNmx01w27MQnPc3BtUQkty_23P0pVlAdj86o5XznUrE";

    /**
     * jwt建立token,考慮安全性,token中不因該放入太多信息(勿放密碼之類的敏感信息),只放入關鍵字段值便可,如用戶ID
     * @param sub     主題(能夠放入關鍵數據,如:userid, 用戶惟一值等)
     * @param timeout 過時時長(秒)
     * @return
     */
    public static String createToken(String sub, int timeout) {
        JWTCreator.Builder builder = JWT.create();
        builder.withSubject(sub);//主題
        builder.withIssuer("pro-server");
        builder.withExpiresAt(DateUtils.addSeconds(new Date(), timeout));//過時時間,30秒後過時
        String jwtToken = builder.sign(Algorithm.HMAC256(SECRET));
        return jwtToken;
    }

    /**
     * 對jwt建立的token進行驗籤與解析,返回Subject(主題)中存放的內容
     * @param token
     * @return
     * @throws TokenExpiredException          會話超時異常
     * @throws SignatureVerificationException 驗籤無效異常
     */
    public static String parseToken(String token) throws TokenExpiredException, SignatureVerificationException {
        return JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token).getSubject();
    }

    /**
     * jwt建立token,考慮安全性,token中不因該放入太多信息(勿放密碼之類的敏感信息)
     * @param loadMap   數據集合
     * @param timeout   過時時長(秒)
     * @return
     */
    public static String createToken(Map<String, Object> loadMap, int timeout) {
        JWTCreator.Builder builder = JWT.create();
        loadMap.forEach((k, v) -> {
            if (v instanceof String) {
                builder.withClaim(k, (String) v);
            } else if (v instanceof Date) {
                builder.withClaim(k, (Date) v);
            } else if (v instanceof Long) {
                builder.withClaim(k, (Long) v);
            } else if (v instanceof Integer) {
                builder.withClaim(k, (Integer) v);
            } else if (v instanceof Boolean) {
                builder.withClaim(k, (Boolean) v);
            }
        });
        builder.withIssuer("pro-server");
        builder.withExpiresAt(DateUtils.addSeconds(new Date(), timeout));//過時時間,30秒後過時
        String jwtToken = builder.sign(Algorithm.HMAC256(SECRET));
        return jwtToken;
    }

    /**
     * 對jwt建立的token進行驗籤與解析,返回集合
     * @param token
     * @return
     * @throws TokenExpiredException          會話超時異常
     * @throws SignatureVerificationException 驗籤無效異常
     */
    public static Map<String, Object> parseTokenToMap(String token) throws TokenExpiredException, SignatureVerificationException {
        Map<String, Claim> claimMap = JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token).getClaims();
        if (claimMap == null){
            return null;
        }
        Map<String, Object> loadMap = new HashMap<>();
        claimMap.forEach((k, v) -> {
            Object obj = null;
            if (v.asString() != null) {
                obj = v.asString();
            } else if (v.asBoolean() != null) {
                obj = v.asBoolean();
            } else if (v.asDate() != null || v.asLong() != null) {//Date類型按Long方式來處理
                obj = v.asLong();
            } else if (v.asInt() != null) {
                obj = v.asInt();
            }
            loadMap.put(k, obj);
        });
        return loadMap;
    }

    //測試方法
    //登陸成功後,將用戶的id放入到JwtTokenUtils.createToken(userid, 60 * 30);
    //每次請求在攔截器或過濾器中,獲取請求中的token調用JwtTokenUtils.parseToken(jwtToken)驗證是否有效,或從token中獲取userid進行業務邏輯操做
    public static void main(String[] args) {
        //建立token和解析token
        String subject = "userid_001";
        System.out.println("新建subject = " + subject);
        String jwtToken = JwtTokenUtils.createToken(subject, 60);
        System.out.println("生成token = " + jwtToken);
        try {
            subject = JwtTokenUtils.parseToken(jwtToken);
            System.out.println("解析subject = " + subject);
        } catch(TokenExpiredException tee){
            throw new TokenExpiredException("token已過有效期,請從新申請:" + tee.getMessage());
        } catch(SignatureVerificationException sve){
            //驗證簽名不經過(數據被篡改過)
            throw sve;
        }

        //過時測試
//        String jwtToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyaWRfMDAxIiwiaXNzIjoicHJvLXNlcnZlciIsImV4cCI6MTU2NTI0OTMxMH0.0cSnHLHTqDx-FXoL08yk6AtIwobiWcMNRofyE4dunGY";
//        subject = JwtTokenUtils.parseToken(jwtToken);

        //無效驗鑑(將最後一位更改成z)
//        String jwtToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyaWRfMDAxIiwiaXNzIjoicHJvLXNlcnZlciIsImV4cCI6MTU2NTI0OTMxMH0.0cSnHLHTqDx-FXoL08yk6AtIwobiWcMNRofyE4dunGz";
//        subject = JwtTokenUtils.parseToken(jwtToken);


        //將多種數據放入集合中,經過jwt建立token
        /*
        Map<String, Object> loadMap = new HashMap<>();
        loadMap.put("userId", (Long) 1000000L);
        loadMap.put("userName", "test");
        loadMap.put("isLogin", true);
        String jwtToken = JwtTokenUtils.createToken(loadMap, 60);
        System.out.println(jwtToken);

        Map<String, Object> loadMap2 = JwtTokenUtils.parseTokenToMap(jwtToken);
        for (Map.Entry<String, Object> entry : loadMap2.entrySet()) {
            System.out.println(entry.getKey() + "=" + entry.getValue());
        }
        */
    }
}

請求中集成使用apache

1.登陸成功後,將用戶的id放入到JwtTokenUtils.createToken(userid, 60 * 30);
2.對須要鑑權的每次URL請求在攔截器或過濾器中處理,獲取請求中的token(通常放在請求頭部,每次請求必帶)調用JwtTokenUtils.parseToken(jwtToken)驗證是否有效,或從token中獲取userid進行業務邏輯操做json

 

說明:跨域

作過項目的人都知道,不少寫過的可重複利用的代碼塊或有用的工具類沒有怎麼整理,當須要的時候,又得打開項目查找一翻,雖然功能開發不難,可是又得花時間成本去寫去測試,這樣的重複造輪子的事情太屢次了;所以不如把輪子保留,供你們一塊兒使用;安全

1.這個輪子能夠有:須要使用的時候確實還不存在這個組件。
2.我須要的時候輪子不在:每一種技術或工具產生都有它的項目背景,當代碼寫在項目裏的時候,我知道有這個東西,當換了一個項目或公司後,沒有備份也沒有記錄,這個時候你不在了,又得花時間手打一遍;
3.我不知道是否是造輪子:大多數狀況下初學者很難分清楚本身是否是在重複造輪子,事實上造輪子不是我目的。個人目的是完成工做任務,任務完成的速度越快越好,質量越高越好。而不是去判斷本身在不在造輪子。
4.不想重複花時間造輪子:有時候還會碰到一些並不困難可是很佔時間的東西,固然有現成的輪子是花時間最少的;
5.我就是想學習輪子:初學者的並非在重複造輪子,而是學習後以提升爲本身的知識與技能。服務器

輪子有過測試,但不免有失誤,若有錯誤處,還敬請指出;工具

相關文章
相關標籤/搜索