一、用户鉴权
客户端请求服务时,根据提交的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的解析放在服务本地,不用去访问鉴权中心。