一.背景
微服務架構下,我們的系統根據業務被拆分成了多個職責單一的微服務。
每個服務都有自己的一套API提供給別的服務呼叫,那麼如何保證安全性呢?
不是說你想呼叫就可以呼叫,一定要有認證機制,是我們內部服務發出的請求,才可以呼叫我們的介面。
需要注意的是我們這邊講的是微服務之間呼叫的安全認證,不是統一的在API官網認證,需求不一樣,API閘道器處的統一認證是和業務掛鉤的,我們這邊是為了防止介面被別人隨便呼叫。
二.方案
OAUTH2
Spring Cloud可以使用OAUTH2來實現多個微服務的統一認證授權
通過向OAUTH2服務進行集中認證和授權,獲得access_token
而這個token是受其他微服務信任的,在後續的訪問中都把access_token帶過去,從而實現了微服務的統一認證授權。
JWT
JWT是一種安全標準。基本思路就是使用者提供使用者名稱和密碼給認證伺服器,伺服器驗證使用者提交資訊資訊的合法性;如果驗證成功,會產生並返回一個Token,使用者可以使用這個token訪問伺服器上受保護的資源。
感覺這2種好像沒多大區別呀,其實是有區別的:OAuth2是一種授權框架 ,JWT是一種認證協議
無論使用哪種方式切記用HTTPS來保證資料的安全性。
三.用哪種
我個人建議用JWT,輕量級,簡單,適合分散式無狀態的應用
用OAUTH2的話就麻煩點,各種角色,認證型別,客戶端等等一大堆概念
四.怎麼用
首先呢建立一個通用的認證服務,提供認證操作,認證成功後返回一個token
@RestController
@RequestMapping(value="/oauth")
public class AuthController {
@Autowired
private AuthService authService;
@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()));
}
@GetMapping("/token")
public ResponseData oauth(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()));
}
}
複製程式碼
JWT可以加入依賴,然後寫個工具類即可,建議寫在全域性的包中,所有的服務都要用,具體程式碼請參考:JWTUtils
GITHUB地址:github.com/jwtk/jjwt
JWT提供了很多加密的演算法,我這邊用的是RSA,目前是用的一套公鑰以及私鑰,這種做法目前來說是不好的,因為萬一祕鑰洩露了,那就談不上安全了,所以後面會採用配置中心的方式來動態管理祕鑰。
類裡主要邏輯是生成token,然後提供一個檢查token是否合法的方法,以及是否過期等等判斷。
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
複製程式碼
統一認證的服務有了,我們只需要將認證服務註冊到註冊中心即可給別的服務消費。
那麼我們如何使用剛剛的認證服務來做認證呢,最簡單的辦法就是用Filter來處理
比如說我現在有一個服務fangjia-fsh-house-service,之前是隨便誰都能呼叫我提供的介面,現在我想加入驗證,只有驗證通過的才可以讓它呼叫我的介面
那就在fangjia-fsh-house-service中加一個過濾器來判斷是否有許可權呼叫介面,我們從請求頭中獲取認證的token資訊,不需要依賴Cookie
這個過濾器我也建議寫在全域性的專案中,因為也是所有服務都要用,程式碼請參考:HttpBasicAuthorizeFilter
主要邏輯就是獲取token然後通過JWTUtils來驗證是否合法,不合法給提示,合法則放過
這邊需要注意的地方是解密的祕鑰必須跟加密時是相同的,不然解密必然失敗,就是bug了
//驗證TOKEN
if (!StringUtils.hasText(auth)) {
PrintWriter print = httpResponse.getWriter();
print.write(JsonUtils.toJson(ResponseData.fail("非法請求【缺少Authorization資訊】",
ResponseCode.NO_AUTH_CODE.getCode())));
return;
}
JWTUtils.JWTResult jwt = jwtUtils.checkToken(auth);
if (!jwt.isStatus()) {
PrintWriter print = httpResponse.getWriter();
print.write(JsonUtils.toJson(ResponseData.fail(jwt.getMsg(), jwt.getCode())));
return;
}
chain.doFilter(httpRequest, response);
複製程式碼
到這步為止,只要呼叫方在認證通過之後,通過認證服務返回的token,然後塞到請求頭Authorization中,就可以呼叫其他需要認證的服務了。
這樣看起來貌似很完美,但是用起來不方便呀,每次呼叫前都需要去認證,然後塞請求頭,如何做到通用呢,不需要具體的開發人員去關心,對使用者透明,下篇文章,我們繼續探討如何實現方便的呼叫。
具體程式碼可以參考我的github:
更多技術分享請關注微信公眾號:猿天地