springCloudAlibaba整合JWT

一、jwt簡介
JWT(Json Web Token)是為了在網(wǎng)絡(luò)應(yīng)用環(huán)境間傳遞聲明而執(zhí)行的一種基于 Json 的開放標(biāo)準(zhǔn)。JWT 的聲明一般被用來在身份提供者和服務(wù)提供者間傳遞被認(rèn)證的用戶身份信息,以便于從資源服務(wù)器獲取資源。
JWT 由三部分構(gòu)成,第一部分稱為頭部(Header),第二部分稱為消息體(Payload),第三部分是簽名(Signature)。
JWT 生成的 Token 格式為:
token = encodeBase64(header) + '.' + encodeBase64(payload) + '.' + encodeBase64(signature)

頭部的信息通常由兩部分內(nèi)容組成,令牌的類型和使用的簽名算法,比如下面的代碼:
{"alg": "HS256", "typ": "JWT"}
消息體中可以攜帶一些你需要的信息,比如用戶 ID。因?yàn)槟愕弥肋@個 Token 是哪個用戶的,比如下面的代碼: { "userId": "1", "admin": true,"userAgent":"PostmanRuntime/7.1.1"}
簽名是用來判斷消息在傳遞的路上是否被篡改,從而保證數(shù)據(jù)的安全性,格式如下:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
通過這三部分就組成了我們的 Json Web Token。
更多介紹可以查看 JWT 官網(wǎng) https://jwt.io/introduction/。

二、使用場景
在前后分離場景下,越來越多的項(xiàng)目使用token作為接口的安全機(jī)制,APP端或者WEB端(使用VUE、REACTJS等構(gòu)建)使用token與后端接口交互,以達(dá)到安全的目的。

三、思路整理


登錄流程圖

四、springCloudAlibaba引入JWT的依賴

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

五、代碼

工具類:

@Slf4j
public class JwtUtils {
    /**
     * JWT_WEB_TTL:WEBAPP應(yīng)用中token的有效時間,默認(rèn)60分鐘
     */
    public static final long JWT_WEB_TTL = 60* 60 * 1000;
    /**
     * 將jwt令牌保存到header中的key
     */
    public static final String JWT_HEADER_KEY = "auth_token";

    // 指定簽名的時候使用的簽名算法,也就是header那部分,jjwt已經(jīng)將這部分內(nèi)容封裝好了。
    private static final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;
    private static final String JWT_SECRET = "xxxxx";// JWT密匙 c
    private static  final SecretKey JWT_KEY;// 使用JWT密匙生成的加密key

    static {
        byte[] encodedKey = Base64.decodeBase64(JWT_SECRET);
        JWT_KEY = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
    } 
  
    /**
     * 檢查Token是否合法
     * @param token
     * @return JWTResult
     */
    public static JWTResult checkToken(String token) {
        try {
            Claims claims = Jwts.parser().setSigningKey(JWT_KEY).parseClaimsJws(token).getBody();
            String sub = claims.get("sub", String.class);
            return new JWTResult(true, sub, "合法請求", ResponseCode.SUCCESS_CODE.getCode());
        } catch (ExpiredJwtException e) {
            // 在解析JWT字符串時,如果‘過期時間字段’已經(jīng)早于當(dāng)前時間,將會拋出ExpiredJwtException異常,說明本次請求已經(jīng)失效
            return new JWTResult(false, null, "token已過期", ResponseCode.TOKEN_TIMEOUT_CODE.getCode());
        }catch (Exception e) {
            return new JWTResult(false, null, "非法請求", ResponseCode.NO_AUTH_CODE.getCode());
        }
    }

    /**
     * 刷新令牌
     * @param token
     * @return
     */
    public static ResponseData refreshToken(String token){
        log.info("刷新令牌token:{}",token);
        JSONObject resultMap = new JSONObject();
        try {
            byte[] encodedKey = Base64.decodeBase64(JWT_SECRET);
            SecretKeySpec JWT_KEY = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
            Claims claims = Jwts.parser().setSigningKey(JWT_KEY).parseClaimsJws(token).getBody();
            //Claims claims = Jwts.parserBuilder().setSigningKey(JWT_KEY).build().parseClaimsJws(token).getBody();
            Date expiration = claims.getExpiration(); //過期時間
            Date issuedAt = claims.getIssuedAt(); //token簽發(fā)時間
            resultMap.put("userAgent",claims.get("userAgent"));
            Object userId = claims.get("userId");
            long nowTime = System.currentTimeMillis();
            //如果當(dāng)前的token已經(jīng)過期則需要重新登錄
            if(nowTime > expiration.getTime()){
                log.info("用戶userId:{}令牌token:{}已經(jīng)過期,需要重新登錄",userId,token);
                return  ResponseData.fail("token過期",ResponseCode.TOKEN_TIMEOUT_CODE.getCode());
            }
            long startTime = issuedAt.getTime();
            long pastTime =  nowTime - startTime;
            if(pastTime > 30*60*1000){
                log.info("用戶userId:{}令牌token:{}已經(jīng)存在超過30分鐘,每30分鐘刷新一次令牌",userId,token);
                //獲取新的token ,將原先的token進(jìn)行失效
                String newToken = createToken(claims,  JwtUtils.JWT_WEB_TTL);
                resultMap.put("token",newToken);
                log.info("用戶userId:{}令牌token:{}已經(jīng)存在超過30分鐘,每30分鐘刷新一次令牌,新令牌:{}",userId,token,newToken);
                return ResponseData.ok(resultMap);
            }
            String sub = claims.get("sub", String.class);
            resultMap.put("token",token);
            log.info("用戶userId:{}令牌token:{}",userId,token);
            return ResponseData.ok(resultMap);
        } catch (ExpiredJwtException e) {
            log.info("令牌token:{},過期了需要重新登錄",token);
            return  ResponseData.fail("token過期",ResponseCode.TOKEN_TIMEOUT_CODE.getCode());
        } catch (Exception e) {
           log.error("刷新令牌出現(xiàn)異常:{}",e);
        }
        return null;
    }


    /**
     * 創(chuàng)建JWT令牌,簽發(fā)時間為當(dāng)前時間
     *
     * @param claims
     *            創(chuàng)建payload的私有聲明(根據(jù)特定的業(yè)務(wù)需要添加,如果要拿這個做驗(yàn)證,一般是需要和jwt的接收方提前溝通好驗(yàn)證方式的)
     * @param ttlMillis
     *            JWT的有效時間(單位毫秒),當(dāng)前時間+有效時間=過期時間
     * @return jwt令牌
     */
    public static String createToken(Map<String, Object> claims, long ttlMillis) {
        byte[] encodedKey = Base64.decodeBase64(JWT_SECRET);
        SecretKeySpec JWT_KEY = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        // 生成JWT的時間,即簽發(fā)時間
        long nowMillis = System.currentTimeMillis();
        JwtBuilder builder = Jwts.builder()
                .setClaims(claims) //如果有私有聲明,一定要先設(shè)置這個自己創(chuàng)建的私有的聲明,這個是給builder的claim賦值,一旦寫在標(biāo)準(zhǔn)的聲明賦值之后,就是覆蓋了那些標(biāo)準(zhǔn)的聲明的
                .setId(UUID.randomUUID().toString().replace("-", ""))// 設(shè)置jti(JWT ID):是JWT的唯一標(biāo)識,根據(jù)業(yè)務(wù)需要,這個可以設(shè)置為一個不重復(fù)的值,主要用來作為一次性token,從而回避重放攻擊。
                .setIssuedAt(new Date(nowMillis))//jwt的簽發(fā)時間
                .signWith(SIGNATURE_ALGORITHM,JWT_KEY)//設(shè)置簽名使用的簽名算法和簽名使用的秘鑰
                .setExpiration(new Date(nowMillis + ttlMillis));//設(shè)置JWT的過期時間
        return builder.compact();
    }

}

public class RSAUtils {
    /**
     * * 生成公鑰和私鑰
     * * @throws NoSuchAlgorithmException  *
     */
    public static HashMap<String, Object> getKeys()
            throws NoSuchAlgorithmException { 
        HashMap<String, Object> map = new HashMap<String, Object>();
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
        keyPairGen.initialize(1024);
        KeyPair keyPair = keyPairGen.generateKeyPair();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        map.put("public", publicKey);
        map.put("private", privateKey);
        return map;
    }

    /**
     * * 使用模和指數(shù)生成RSA公鑰
     * * @param modulus  模
     * * @param exponent  指數(shù)   *
     *
     * @return
     */
    public static RSAPublicKey getPublicKey(String modulus, String exponent) {
        try {
            BigInteger b1 = new BigInteger(modulus);
            BigInteger b2 = new BigInteger(exponent);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            RSAPublicKeySpec keySpec = new RSAPublicKeySpec(b1, b2);
            return (RSAPublicKey) keyFactory.generatePublic(keySpec);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * * 使用模和指數(shù)生成RSA私鑰
     * * /None/NoPadding
     * * @param modulus
     * 模   * @param
     * exponent指數(shù)   * @return
     */
    public static RSAPrivateKey getPrivateKey(String modulus, String exponent) {
        try {
            BigInteger b1 = new BigInteger(modulus);
            BigInteger b2 = new BigInteger(exponent);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(b1, b2);
            return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * * 公鑰加密   *
     * * @param data
     * * @param publicKey
     * * @return
     * * @throws
     * Exception
     */
    public static String encryptByPublicKey(String data, RSAPublicKey publicKey)
            throws Exception {
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        // 模長
        int key_len = publicKey.getModulus().bitLength() / 8;
        // 加密數(shù)據(jù)長度 <= 模長-11
        String[] datas = splitString(data, key_len - 11);
        String mi = "";
        // 如果明文長度大于模長-11則要分組加密
        for (String s : datas) {
            mi += bcd2Str(cipher.doFinal(s.getBytes()));
        }
        return mi;
    }

    /**
     * * 私鑰解密   *
     * * @param data
     * * @param privateKey
     * * @return
     * * @throws
     * Exception
     */
    public static String decryptByPrivateKey(String data,
                                             RSAPrivateKey privateKey) throws Exception {
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        // 模長
        int key_len = privateKey.getModulus().bitLength() / 8;
        byte[] bytes = data.getBytes();
        byte[] bcd = ASCII_To_BCD(bytes, bytes.length);
        // System.err.println(bcd.length);
        // 如果密文長度大于模長則要分組解密
        String ming = "";
        byte[][] arrays = splitArray(bcd, key_len);
        for (byte[] arr : arrays) {
            ming += new String(cipher.doFinal(arr));
        }
        return ming;
    }

    /**
     * * ASCII碼轉(zhuǎn)BCD碼   *
     */
    public static byte[] ASCII_To_BCD(byte[] ascii, int asc_len) {
        byte[] bcd = new byte[asc_len / 2];
        int j = 0;
        for (int i = 0; i < (asc_len + 1) / 2; i++) {
            bcd[i] = asc_to_bcd(ascii[j++]);
            bcd[i] = (byte) (((j >= asc_len) ? 0x00 : asc_to_bcd(ascii[j++])) + (bcd[i] << 4));
        }
        return bcd;
    }

    public static byte asc_to_bcd(byte asc) {
        byte bcd;

        if ((asc >= '0') && (asc <= '9'))
            bcd = (byte) (asc - '0');
        else if ((asc >= 'A') && (asc <= 'F'))
            bcd = (byte) (asc - 'A' + 10);
        else if ((asc >= 'a') && (asc <= 'f'))
            bcd = (byte) (asc - 'a' + 10);
        else
            bcd = (byte) (asc - 48);
        return bcd;
    }

    /**
     * * BCD轉(zhuǎn)字符串
     */
    public static String bcd2Str(byte[] bytes) {
        char temp[] = new char[bytes.length * 2], val;

        for (int i = 0; i < bytes.length; i++) {
            val = (char) (((bytes[i] & 0xf0) >> 4) & 0x0f);
            temp[i * 2] = (char) (val > 9 ? val + 'A' - 10 : val + '0');

            val = (char) (bytes[i] & 0x0f);
            temp[i * 2 + 1] = (char) (val > 9 ? val + 'A' - 10 : val + '0');
        }
        return new String(temp);
    }

    /**
     * * 拆分字符串
     */
    public static String[] splitString(String string, int len) {
        int x = string.length() / len;
        int y = string.length() % len;
        int z = 0;
        if (y != 0) {
            z = 1;
        }
        String[] strings = new String[x + z];
        String str = "";
        for (int i = 0; i < x + z; i++) {
            if (i == x + z - 1 && y != 0) {
                str = string.substring(i * len, i * len + y);
            } else {
                str = string.substring(i * len, i * len + len);
            }
            strings[i] = str;
        }
        return strings;
    }

    /**
     * *拆分?jǐn)?shù)組
     */
    public static byte[][] splitArray(byte[] data, int len) {
        int x = data.length / len;
        int y = data.length % len;
        int z = 0;
        if (y != 0) {
            z = 1;
        }
        byte[][] arrays = new byte[x + z][];
        byte[] arr;
        for (int i = 0; i < x + z; i++) {
            arr = new byte[len];
            if (i == x + z - 1 && y != 0) {
                System.arraycopy(data, i * len, arr, 0, y);
            } else {
                System.arraycopy(data, i * len, arr, 0, len);
            }
            arrays[i] = arr;
        }
        return arrays;
    }
}

攔截器:

@Configuration
public class LoginConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注冊TestInterceptor攔截器
        InterceptorRegistration registration = registry.addInterceptor(new  ValidateLoginInterceptor());
        registration.addPathPatterns("/**");                      //所有路徑都被攔截
        registration.excludePathPatterns(                         //添加不攔截路徑
                "/user/login",           //登錄 
        );
    }
}

@Slf4j
public class ValidateLoginInterceptor implements HandlerInterceptor{
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
        //首先從請求頭中獲取jwt串,與頁面約定好存放jwt值的請求頭屬性名為author_token
        String jwt = httpServletRequest.getHeader(JwtUtils.JWT_HEADER_KEY);
        log.info("[登錄攔截器]-從header中獲取的jwt為:{}", jwt);
        //判斷jwt是否有效
        if(StringUtils.isNotBlank(jwt)){
            //校驗(yàn)jwt是否有效,有效則返回json信息,無效則返回空
          //  JWTResult jwtResult = JwtUtils.checkToken(jwt);
            ResponseData responseData = JwtUtils.refreshToken(jwt);
            log.info("[登錄攔截器]-校驗(yàn)JWT有效性返回結(jié)果:{}", responseData);
            //retJSON為空則說明jwt超時或非法
            if(responseData != null && responseData.getCode() == 200 ){
                Object data = responseData.getData();
                String s = JSONObject.toJSONString(data);
                JSONObject jsonObject = JSONObject.parseObject(s);
                log.info("[登錄攔截器]-校驗(yàn)JWT有效性,解析返回data:{}", jsonObject);
                //校驗(yàn)客戶端信息
                String userAgent = httpServletRequest.getHeader("User-Agent");
                if (userAgent.equals(jsonObject.getString("userAgent"))) {
                    //獲取刷新后的jwt值,設(shè)置到響應(yīng)頭中
                    httpServletResponse.setHeader(JwtUtils.JWT_HEADER_KEY, jsonObject.getString("token"));
                    //將客戶編號設(shè)置到session中
                    httpServletRequest.getSession().setAttribute("user_id", jsonObject.getString("userId"));
                    return true;
                }else{
                    log.warn("[登錄攔截器]-客戶端瀏覽器信息與JWT中存的瀏覽器信息不一致,重新登錄。當(dāng)前瀏覽器信息:{}", userAgent);
                }
            }else {
                log.warn("[登錄攔截器]-JWT非法或已超時,重新登錄");
            }
        }
        //輸出響應(yīng)流
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("hmac", "");
        jsonObject.put("status", "");
        jsonObject.put("code", "4007");
        jsonObject.put("msg", "未登錄");
        jsonObject.put("data", "");
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json; charset=utf-8");
        httpServletResponse.getOutputStream().write(jsonObject.toJSONString().getBytes("UTF-8"));
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

    }

}

登錄接口

 @RequestMapping("/login")
    public Result login(String userName ,String password,HttpServletResponse response,HttpServletRequest request){
        //todo--根據(jù)userName,parssword進(jìn)行登錄驗(yàn)證
        Integer userId = 1381 ;
        Map<String,Object> claims=new HashMap<String, Object>();
        claims.put("userId",userId);
        String userAgent = request.getHeader("User-Agent");
        claims.put("userAgent",userAgent);
      //  JwtUtils jwtUtils = new JwtUtils();
        String jwt = JwtUtils.createToken(claims, JwtUtils.JWT_WEB_TTL);
        response.setHeader(JwtUtils.JWT_HEADER_KEY, jwt);
        return Result.success(jwt);
    }

總結(jié):
此案例通過jwt獲取token,通過攔截器校驗(yàn),生成token的userAgent和當(dāng)前訪問的userAgent是否一致,以及token是否合法,實(shí)現(xiàn)登錄邏輯

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容