認證服務肯定要有用戶信息,不然怎麼認證是否爲合法用戶?因爲是內部的調用認證,可以簡單一點,用數據庫管理就是一種方式。或者可以配置用戶信息,然後集成分佈式配置管理就完美了。
表結構
本教程中的案例把查數據庫這一步驟省略了,大家可以自行補充,但是表的設計還是要跟大家講解的。用戶表的形式如圖 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 判斷是否有這個用戶。