乐优商城(五十二)服务鉴权

一、用户鉴权

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

猜你喜欢

转载自blog.csdn.net/lyj2018gyq/article/details/85125767