1. 程式人生 > >樂優商城(五十二)服務鑑權

樂優商城(五十二)服務鑑權

一、使用者鑑權

客戶端請求服務時,根據提交的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的解析放在服務本地,不用去訪問鑑權中心。