網關服務的作用: 身份認證、路由服務、為前端服務的後端—數據聚合 身份認證 如果我們的微服務和終端通信,勢必要考慮身份認證,如果我們的微服務都與每個終端用戶打交道,那麼這些代碼就需要拷貝多份, 並且植入到每個微服務業務代碼中,這就造成業務代碼和身份認證代碼耦合,降低代碼的復用性。 路由服務 由運維人 ...
網關服務的作用:
身份認證、路由服務、為前端服務的後端—數據聚合
- 身份認證
- 路由服務
由運維人員手動維護路由規則和服務實例列表是非常費工夫的且容易出錯。
- 為前端服務的後端
比如將多個服務的數據聚合在一起返回給前端
為瞭解決上面的架構問題,API網關的概念應運而生,它的定義類似於面向對象設計模式中的Facade模式,它的存在就像是整個微服務架構系統的門面一樣,所有的外部客戶端訪問都需要經過它來進行調度和過濾。
由它來實現請求路由、負載均衡、校驗過濾等功能,以及與服務治理框架的結合,請求轉發的熔斷機制、服務的聚合等。
SpringCloud Zuul組件能非常好的解決這些問題,在服務路由的時候,我們看它如何方便的解決了這個問題。
創建一個API服務網關工程
簡單使用:
1、添加依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> </dependency>
2、配置文件
zuul.routes.api-a-url.path=/hello/** zuul.routes.api-a-url.url=http://localhost:9000/
所有符合/hello/**規則的訪問都將被路由轉發到http://localhost:9000/地址上,其中api-a-url是路由的名字,可以任意定義。
3、啟動類
@SpringBootApplication @EnableZuulProxy public class GatewayServiceZuulApplication { public static void main(String[] args) { SpringApplication.run(GatewayServiceZuulApplication.class, args); } }
啟動類添加@EnableZuulProxy
,支持網關路由。
面向服務的路由
實際上上面那種方式同樣需要運維人員花費大量的時間來維護各個路由path和url的關係。為瞭解決這個問題,Zuul實現了於Eureka的無縫結合,我們可以讓路喲的path不是映射具體的url,而是讓它映射到某個具體的服務,而
具體的url則交給Eureka的服務發現機制去自動維護。
1、添加依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency>
增加spring-cloud-starter-eureka
包,添加對eureka的支持。
2、配置文件
配置修改為:
spring.application.name=gateway-service-zuul server.port=8888 zuul.routes.api-a.path=/producer/** zuul.routes.api-a.serviceId=spring-cloud-producer eureka.client.serviceUrl.defaultZone=http://localhost:8000/eureka/
項目採取方案
由於項目採取的並不是前後端分離的架構,所有的請求到達API 服務網關,zuul進行路由,可是並不能將各服務返回頁面的進行聚合再返回給瀏覽器。
而是採用了一種次等的策略,將所有的靜態資源放在API網關中,API網關接收請求,調用各個服務介面,將返回數據的數據進行聚合,然後給API網關的頁面進行渲染。
這裡身份認證JWT可以單獨作為一個認證服務被調用。基於JWT的token身份認證方案
以一個登陸請求為例:
API網關的UserController中的方法:
@RequestMapping(value="/accounts/signin",method={RequestMethod.POST,RequestMethod.GET}) public String loginSubmit(HttpServletRequest req){ String username = req.getParameter("username"); String password = req.getParameter("password"); if (username == null || password == null) { req.setAttribute("target", req.getParameter("target")); return "/user/accounts/signin"; } User user = accountService.auth(username, password); if (user == null) { return "redirect:/accounts/signin?" + "username=" + username + "&" + ResultMsg.errorMsg("用戶名或密碼錯誤").asUrlParams(); }else { UserContext.setUser(user); return StringUtils.isNotBlank(req.getParameter("target")) ? "redirect:" + req.getParameter("target") : "redirect:/index"; } } }
API網關的UserService中的方法
public User auth(String username, String password) { if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) { return null; } User user = new User(); user.setEmail(username); user.setPasswd(password); try { user = userDao.authUser(user); } catch (Exception e) { return null; } return user; }
API網關的UserDao中的方法
@HystrixCommand public User authUser(User user) { String url = "http://" + userServiceName + "/user/auth"; ResponseEntity<RestResponse<User>> responseEntity = rest.post(url, user, new ParameterizedTypeReference<RestResponse<User>>() {}); RestResponse<User> response = responseEntity.getBody(); if (response.getCode() == 0) { return response.getResult(); }{ throw new IllegalStateException("Can not add user"); } }
這裡的方法添加了@HystrixCommand用於進行服務的熔斷,這個後面會介紹它。
經過RestTemplate攔截請求,轉發到某個服務實例上。註意它返回的是ResponseEntity<T>泛型對象,其中T是由ParameterizedTypeReference<T>中的T指定。
responseEntity.getBody()就能獲取到實際返回的對象。
User-Service服務UserController中的auth方法:
@RequestMapping("auth") public RestResponse<User> auth(@RequestBody User user){ User finalUser = userService.auth(user.getEmail(),user.getPasswd()); return RestResponse.success(finalUser);
}
User-Service服務UserService中的auth方法:
/** * 校驗用戶名密碼、生成token並返回用戶對象 * @param email * @param passwd * @return */ public User auth(String email, String passwd) { if (StringUtils.isBlank(email) || StringUtils.isBlank(passwd)) { throw new UserException(Type.USER_AUTH_FAIL,"User Auth Fail"); } User user = new User(); user.setEmail(email); user.setPasswd(HashUtils.encryPassword(passwd)); //user.setEnable(1); List<User> list = getUserByQuery(user); if (!list.isEmpty()) { User retUser = list.get(0); onLogin(retUser); return retUser; } throw new UserException(Type.USER_AUTH_FAIL,"User Auth Fail"); } //生成token的操作 private void onLogin(User user) { //最後一個是時間戳 String token = JwtHelper.genToken(ImmutableMap.of("email", user.getEmail(), "name", user.getName(),"ts",Instant.now().getEpochSecond()+"")); renewToken(token,user.getEmail()); user.setToken(token); } //重新設置緩存過期時間 private String renewToken(String token, String email) { redisTemplate.opsForValue().set(email, token); redisTemplate.expire(email, 30, TimeUnit.MINUTES); return token; }