(五十八) springcloud+springcloud+vue+uniapp分佈式微服務電商 商城之Spring Cloud基於JWT創建統一的認證服務

認證服務肯定要有用戶信息,不然怎麼認證是否爲合法用戶?因爲是內部的調用認證,可以簡單一點,用數據庫管理就是一種方式。或者可以配置用戶信息,然後集成分佈式配置管理就完美了。

表結構

本教程中的案例把查數據庫這一步驟省略了,大家可以自行補充,但是表的設計還是要跟大家講解的。用戶表的形式如圖 1 所示。

相關的代碼如下所示。

create table auth_user(
  id int(4) not null,
  accessKey varchar(100) not null,
  secretKey varchar(100) not null,
  Primary key (id)
);
Alter table auth_user comment '認證用戶信息表';

這裏只有簡單的幾個字段,若大家有別的需求可以自行去擴展。代碼中的 accessKey 和 secretKey 是用戶身份的標識。

JWT 工具類封裝

JWT 的 GitHub 地址是:https://github.com/jwtk/jjwt,依賴配置代碼如下所示。

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

用工具類進行認證主要有以下幾個方法:

  • 生成 Token。
  • 檢查 Token 是否合法。
  • 刷新 RSA 公鑰以及私鑰。


生成 Token 是在進行用戶身份認證之後,通過用戶的 ID 來生成一個 Token,這個 Token 採用 RSA 加密的方式進行加密,Token 的內容包括用戶的 ID 和過期時間。

檢查 Token 則是根據調用方帶來的 Token 檢查是否爲合法用戶,就是對 Token 進行解密操作,能解密並且在有效期內表示合法,合法則返回用戶 ID。

刷新 RSA 公鑰及私鑰的作用是防止公鑰、私鑰泄露,公鑰、私鑰一般是寫死的,不過我們可以做成配置的。集成配置管理中心後,可以對公鑰、私鑰進行動態修改,修改之後需要重新初始化公鑰、私鑰的對象信息。

獲取 Token 代碼如下所示。

/**
* 獲取 Token
*
* @param uid 用戶 ID
* @param exp 失效時間, 單位分鐘
* @return
*/
public static String getToken(String uid, int exp) {
    Long endTime = System.currentTimeMillis() + 1000 * 60 * exp;
    return Jwts.builder().setSubject(uid).setExpiration(new Date(endTime))
            .signWith(SignatureAlgorithm.RS512, priKey).compact();
}

檢查 Token 是否合法代碼如下所示。

/**
* 檢查 Token 是否合法
*
* @param token
* @return JWTResult
*/
public JWTResult checkToken(String token) {
    try {
        Claims claims = Jwts.parser().setSigningKey(pubKey).parseClaimsJws(token).getBody();
        String sub = claims.get("sub", String.class);
        return new JWTResult(true, sub, "合法請求", ResponseCode.SUCCESS_CODE.getCode());
    } catch (ExpiredJwtException e) {
        // 在解析 JWT 字符串時, 如果'過期時間字段'已經早於當前時間,
        // 將會拋出 ExpiredJwtException 異常, 說明本次請求已經失效
        return new JWTResult(false, null, "token已過期 ", ResponseCode.TOKEN_TIMEOUT_CODE.getCode());
    } catch (SignatureException e) {
        // 在解析 JWT 字符串時, 如果密鑰不正確, 將會解析失敗, 拋出
        // SignatureException 異常, 說明該 JWT 字符串是僞造的
        return new JWTResult(false, null, "非法請求", ResponseCode.NO_AUTH_CODE.getCode());
    } catch (Exception e) {
        return new JWTResult(false, null, "非法請求", ResponseCode.NO_AUTH_CODE.getCode());
    }
}

完整代碼如下所示。

/**
* API調用認證工具類,採用RSA加密
*/
public class JWTUtils {
    private static RSAPrivateKey priKey;
    private static RSAPublicKey pubKey;
    private static class SingletonHolder {
        private static final JWTUtils INSTANCE = new JWTUtils();
    }
    public synchronized static JWTUtils getInstance(String modulus, String privateExponent, String publicExponent) {
        if (priKey == null && pubKey == null) {
            priKey = RSAUtils.getPrivateKey(modulus, privateExponent);
            pubKey = RSAUtils.getPublicKey(modulus, publicExponent);
        }
        return SingletonHolder.INSTANCE;
    }
    public synchronized static void reload(String modulus, String privateExponent, String publicExponent) {
        priKey = RSAUtils.getPrivateKey(modulus, privateExponent);
        pubKey = RSAUtils.getPublicKey(modulus, publicExponent);
    }
    public synchronized static JWTUtils getInstance() {
        if (priKey == null && pubKey == null) {
            priKey = RSAUtils.getPrivateKey(RSAUtils.modulus, RSAUtils.private_exponent);
            pubKey = RSAUtils.getPublicKey(RSAUtils.modulus, RSAUtils.public_exponent);
        }
        return SingletonHolder.INSTANCE;
    }
    /**
     * 獲取Token
     *
     * @param uid 用戶ID
     * @param exp 失效時間,單位分鐘
     * @return
     */
    public static String getToken(String uid, int exp) {
        long endTime = System.currentTimeMillis() + 1000 * 60 * exp;
        return Jwts.builder().setSubject(uid).setExpiration(new Date(endTime))
                .signWith(SignatureAlgorithm.RS512, priKey).compact();
    }
    /**
     * 獲取Token
     *
     * @param uid 用戶ID
     * @return
     */
    public String getToken(String uid) {
        long endTime = System.currentTimeMillis() + 1000 * 60 * 1440;
        return Jwts.builder().setSubject(uid).setExpiration(new Date(endTime))
                .signWith(SignatureAlgorithm.RS512, priKey).compact();
    }
    /**
     * 檢查Token是否合法
     *
     * @param token
     * @return JWTResult
     */
    public JWTResult checkToken(String token) {
        try {
            Claims claims = Jwts.parser().setSigningKey(pubKey).parseClaimsJws(token).getBody();
            String sub = claims.get("sub", String.class);
            return new JWTResult(true, sub, "合法請求", ResponseCode.SUCCESS_CODE.getCode());
        } catch (ExpiredJwtException e) {
            // 在解析JWT字符串時,如果‘過期時間字段’已經早於當前時間,將會拋出ExpiredJwtException異常,說明本次請求已經失效
            return new JWTResult(false, null, "token已過期", ResponseCode.TOKEN_TIMEOUT_CODE.getCode());
        } catch (SignatureException e) {
            // 在解析JWT字符串時,如果密鑰不正確,將會解析失敗,拋出SignatureException異常,說明該JWT字符串是僞造的
            return new JWTResult(false, null, "非法請求", ResponseCode.NO_AUTH_CODE.getCode());
        } catch (Exception e) {
            return new JWTResult(false, null, "非法請求", ResponseCode.NO_AUTH_CODE.getCode());
        }
    }
    public static class JWTResult {
        private boolean status;
        private String uid;
        private String msg;
        private int code;
        public JWTResult() {
            super();
        }
        public JWTResult(boolean status, String uid, String msg, int code) {
            super();
            this.status = status;
            this.uid = uid;
            this.msg = msg;
            this.code = code;
        }
        public int getCode() {
            return code;
        }
        public void setCode(int code) {
            this.code = code;
        }
        public String getMsg() {
            return msg;
        }
        public void setMsg(String msg) {
            this.msg = msg;
        }
        public boolean isStatus() {
            return status;
        }
        public void setStatus(boolean status) {
            this.status = status;
        }
        public String getUid() {
            return uid;
        }
        public void setUid(String uid) {
            this.uid = uid;
        }
    }
}

認證接口

認證接口用於調用方進行認證時,認證通過則返回一個加密的 Token 給對方,對方就可以用這個 Token 去請求別的服務了,認證獲取 Token 代碼如下所示。

@PostMapping("/token")
public ResponseData auth(@RequestBody AuthQuery query) throws Exception {
    if (StringUtils.isBlank(query.getAccessKey()) || StringUtils.isBlank(query.getSecretKey())) {
        return ResponseData.failByParam("accessKey and secretKey not null");
    }
    User user = authService.auth(query);
    if (user == null) {
        return ResponseData.failByParam(" 認證失敗 ");
    }
    JWTUtils jwt = JWTUtils.getInstance();
    return ResponseData.ok(jwt.getToken(user.getId().toString()));
}

認證參數代碼如下所示。

/**
* API 用戶認證參數類
*/
public class AuthQuery {
    private String accessKey;
    private String secretKey;
    // get set ...
}

AuthService 中的 auth 方法就是根據 accessKey 和 secretKey 判斷是否有這個用戶。

推薦電子商務源碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章