Spring Cloud中如何保證各個微服務之間呼叫的安全性

猿天地發表於2018-05-27

一.背景

微服務架構下,我們的系統根據業務被拆分成了多個職責單一的微服務。

每個服務都有自己的一套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:

github.com/yinjihuan/s…

更多技術分享請關注微信公眾號:猿天地

image.png

相關文章