樂優商城(五十二)服務鑑權
一、使用者鑑權
客戶端請求服務時,根據提交的token獲取使用者資訊,看是否有使用者資訊及使用者資訊是否正確,這個在樂優商城中已經實現。
二、服務鑑權
微服務中,一般有多個服務,服務與服務之間相互呼叫時,有的服務介面比較敏感,比如資金服務,不允許其他服務隨便呼叫,所以要進行服務呼叫的許可權鑑定認證。其實原理是一樣的,服務呼叫的時候攜帶token,然後在被調服務中對token進行解析,判斷是否滿足既定的規則,滿足的話放行,不滿足直接返回401即可。
三、簡易版服務鑑權
兩個微服務service1和service2,其中service1呼叫service2,那麼在service1中新增Feign攔截器,將token放入head中,然後在service2中配置mvc攔截器,判斷head中的token是否滿足要求。
3.1 註冊中心
3.1.1 依賴
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>service-authentication</artifactId> <groupId>com.service.authentication</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <groupId>com.service.authentication</groupId> <artifactId>registry</artifactId> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> </dependencies> </project>
3.1.2 配置
server: port: 9000 spring: application: name: registry eureka: client: fetch-registry: false register-with-eureka: false service-url: defaultZone: http://127.0.0.1:9000/eureka server: enable-self-preservation: false #關閉自我保護 eviction-interval-timer-in-ms: 5000 #每隔5秒進行一次服務列表清理
3.1.3 啟動器
package com.service.called.auth;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* @Author: 98050
* @Time: 2018-12-20 14:58
* @Feature:
*/
@SpringBootApplication
@EnableEurekaServer
public class ServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceApplication.class,args);
}
}
3.2 Service2
3.2.1 依賴
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>service-authentication</artifactId>
<groupId>com.service.authentication</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.service.authentication</groupId>
<artifactId>service-2</artifactId>
<dependencies>
<!--web啟動器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Eureka客戶端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
</project>
3.2.2 配置
server:
port: 9002
spring:
application:
name: be-called-service
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:9000/eureka
registry-fetch-interval-seconds: 5
instance:
instance-id: ${spring.application.name}:${server.port}
prefer-ip-address: true #當你獲取host時,返回的不是主機名,而是ip
ip-address: 127.0.0.1
lease-expiration-duration-in-seconds: 10 #10秒不傳送九過期
lease-renewal-interval-in-seconds: 5 #每隔5秒發一次心跳
3.2.3 啟動器
package com.service.called.becalled;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* @Author: 98050
* @Time: 2018-12-20 15:10
* @Feature:
*/
@SpringBootApplication
@EnableDiscoveryClient
public class BeCalledServiceApplication {
public static void main(String[] args) {
SpringApplication.run(BeCalledServiceApplication.class,args);
}
}
3.2.4 Controller
package com.service.called.becalled.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author: 98050
* @Time: 2018-12-20 15:12
* @Feature:
*/
@RestController
@RequestMapping("be-called-service")
public class BeCalledController {
@GetMapping("call")
public String call(){
return "hello";
}
}
3.2.5 Config
package com.service.called.becalled.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @Author: 98050
* @Time: 2018-12-20 15:47
* @Feature:
*/
@Configuration
@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
}
}
package com.service.called.becalled.config;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @Author: 98050
* @Time: 2018-12-20 15:48
* @Feature:
*/
public class MyInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1.查詢token
String token = request.getHeader("token");
String service = "9001";
if (token == null || !token.equals(service)){
response.setStatus(HttpStatus.UNAUTHORIZED.value());
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
super.afterCompletion(request, response, handler, ex);
}
}
3.2.6 API
package com.service.called.becalled.api;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @Author: 98050
* @Time: 2018-12-20 15:13
* @Feature:
*/
@RequestMapping("be-called-service")
public interface BeCalledApi {
/**
* 被調服務介面
* @return
*/
@RequestMapping("call")
String call();
}
3.3 Service1
3.3.1 依賴
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>service-authentication</artifactId>
<groupId>com.service.authentication</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.service.authentication</groupId>
<artifactId>service-1</artifactId>
<dependencies>
<!--web啟動器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Eureka客戶端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.service.authentication</groupId>
<artifactId>service-2</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
3.3.2 配置
server:
port: 9001
spring:
application:
name: call-service-1
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:9000/eureka
registry-fetch-interval-seconds: 5
instance:
instance-id: ${spring.application.name}:${server.port}
prefer-ip-address: true #當你獲取host時,返回的不是主機名,而是ip
ip-address: 127.0.0.1
lease-expiration-duration-in-seconds: 10 #10秒不傳送九過期
lease-renewal-interval-in-seconds: 5 #每隔5秒發一次心跳
3.3.3 啟動器
package com.service.called;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @Author: 98050
* @Time: 2018-12-20 15:06
* @Feature:
*/
@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
public class CalledServiceApplication {
public static void main(String[] args) {
SpringApplication.run(CalledServiceApplication.class,args);
}
}
3.3.4 Client
package com.service.called.client;
import com.service.called.becalled.api.BeCalledApi;
import org.springframework.cloud.openfeign.FeignClient;
/**
* @Author: 98050
* @Time: 2018-12-20 15:15
* @Feature:
*/
@FeignClient(value = "be-called-service")
public interface CallServiceClient extends BeCalledApi {
}
3.3.5 Feign攔截器
放入token
package com.service.called.config;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Configuration;
/**
* @Author: 98050
* @Time: 2018-12-20 15:41
* @Feature:
*/
@Configuration
public class FeignInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header("token","9001");
}
}
3.3.6 Controller
package com.service.called.controller;
import com.service.called.client.CallServiceClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author: 98050
* @Time: 2018-12-20 15:17
* @Feature:
*/
@RestController
@RequestMapping("call-service")
public class CallController {
@Autowired
private CallServiceClient callServiceClient;
@GetMapping("call")
public String test(){
return this.callServiceClient.call();
}
}
3.3 測試
註冊中心:
訪問http://localhost:9001/call-service/call:
訪問:http://localhost:9002/be-called-service/call
只有攜帶token,並且其值為“9001”的服務才能成功呼叫service2。
程式碼:https://github.com/lyj8330328/service-authentication
四、JWT服務鑑權
此時就需要一個獨立的服務鑑權中心,service1首先去鑑權中心,拿到token,然後通過Feign攔截器將tokenf放入到head中;service2中通過mvc攔截器(可以細粒度的控制每一個介面是否能被其它服務呼叫),獲取請求head中存放的token,解析後獲取到服務資訊,根據既定的規則來決定是否放行。在這個過程中可以使用redis快取來存放service1獲取到的token,設定一個過期時間,到期後再重新去服務鑑權中心去拿。還有一點,整個過程結合rsa非對稱加密完成,減少服務鑑權中心的壓力,token的解析放在服務本地,不用去訪問鑑權中心。