Spring Cloud进阶之路 | 十:服务网关整合安全框架(zuul+ Spring Cloud Oauth2)

转载请注明作者及出处:

作者:银河架构师

原文链接:https://www.cnblogs.com/luas/p/12201460.html

前言

Spring Cloud进阶之路 | 七:服务网关(zuul)一文中,已详细阐述了网关的作用及重要性,其不仅仅可进行路由转发和请求过滤,还可以进行权限校验、审计、接口监控、限流以及日志收集等逻辑。

要完成这些额外逻辑,需集成其它框架,或者开发相应的过滤器,亦或是代码逻辑。本文就以整合安全框架中为例,演示如何在网关进行身份认证。

前面说过,微服务架构中,把一个大型的单个应用程序和服务拆分为数个甚至数十个的支持微服务,为了保证高可用,每个微服务都可能会部署集群。

根据之前文章Spring Cloud进阶之路 | 八:授权服务(Spring Cloud Oauth2)Spring Cloud进阶之路 | 九:资源服务(Spring Cloud Oauth2)的阐述,拆分后的单个服务均为资源服务器,提供资源服务。

既然是对外提供资源服务,势必会引起一个问题,即安全问题。此时,便有两种方案:各资源服务自行处理、统一交由网关处理,各资源服务只关注业务。

如果各资源服务自行处理,微服务拆分后,每个服务均需处理相同安全逻辑,工作量严重重复。即便提取公共部分,也解决不了大问题。因为公共部分只能解决身份认证问题,解决不了鉴权,每个资源服务权限都不尽相同。

统一交由网关处理,网关本身作为资源服务,先期直接进行身份认证,身份认证通过之后再进行鉴权,均通过以后再执行后续逻辑。此时,各资源服务只用关注自身业务,无需处理这些繁琐的安全策略。

至于权限问题,可开发统一的权限支撑平台,统一管理用户及所有资源服务的权限,服务上线自动注册资源到权限支撑平台,再由维护人员统一分配即可。

 

准备工作

 

复用之前文章Spring Cloud进阶之路 | 七:服务网关(zuul)Spring Cloud进阶之路 | 八:授权服务(Spring Cloud Oauth2)Spring Cloud进阶之路 | 九:资源服务(Spring Cloud Oauth2)的工程。

 

改造xmall-product

 

据前文所述,各资源服务不再作为授权服务相对的资源服务而存在,所以,需要删除Spring Cloud Oauth2相关内容。

 

删除Spring Cloud Oauth2依赖

修改后的pom文件如下。

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion><parent>
    <groupId>com.luas.cloud</groupId>
    <artifactId>java-boot-parent-2.1</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <relativePath>../../java-boot-parent-2.1</relativePath>
  </parent><groupId>com.luas.cloud</groupId>
  <artifactId>xmall-product</artifactId>
  <version>1.0.0-SNAPSHOT</version><name>xmall-product</name>
  <description>Spring Cloud Learning,nacos-client</description><dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency><!-- nacos cloud -->
    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency><dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency><dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies><build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build></project>

 

删除oauth2资源配置

删除application.yml中oauth2资源配置信息,删除后内容如下。

server:
  port: 8080

 

删除ResourceServer相关配置

如启动类有@EnableResourceServer注解,则删除。

package com.luas.xmall;
​
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
​
@SpringBootApplication
public class XmallProductApplication {
​
  public static void main(String[] args) {
    SpringApplication.run(XmallProductApplication.class, args);
  }
​
}

 

如存在ResourceServerConfiguration配置类,则删除。

package com.luas.xmall.configuration;
​
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
​
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .requestMatchers()
                .anyRequest()
                .and()
                .authorizeRequests()
                .antMatchers("/application").permitAll()
                .anyRequest()
                .authenticated();
    }
}

 

改造网关

添加Spring Cloud Oauth2依赖

添加spring-cloud-starter-oauth2依赖,修改后的pom文件如下。

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion><parent>
        <groupId>com.luas.cloud</groupId>
        <artifactId>java-boot-parent-2.1</artifactId>
        <version>1.0.0-SNAPSHOT</version>
        <relativePath>../../java-boot-parent-2.1</relativePath>
    </parent><groupId>com.luas.xmall</groupId>
    <artifactId>xmall-zuul</artifactId>
    <version>1.0.0-SNAPSHOT</version><name>xmall-zuul</name>
    <description>网关服务</description><properties>
    </properties><dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency><dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency><!-- nacos cloud -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency><dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency><dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
​
​
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build></project>

 

修改application配置

添加授权服务器路由规则、oauth2资源配置

server:
  port: 5566
​
zuul:
  prefix: /gateway
  sensitive-headers:
  routes:
    auth:
      path: /auth/**
      service-id: xmall-auth
      strip-prefix: true
    product:
      path: /product/**
      service-id: xmall-product
      strip-prefix: true
​
security:
  oauth2:
    resource:
      user-info-uri: http://localhost:7777/oauth/user
      prefer-token-info: false

 

添加ResourceServerConfiguration配置类

此处有一个点,比较重要:为什么不直接在启动类添加@EnableResourceServer注解开启资源服务,而要特殊定义资源服务配置?

原因就在于,默认资源服务器安全策略,所以请求均需身份认证,那么获取token、token校验、token key获取等这些无需身份认证即可访问的端点,也会被拦截。所以,需要特殊定义资源服务安全策略,放开这些公共端点

ResourceServerConfiguration类如下。

package com.luas.xmall.gateway.configuration;
​
import com.luas.xmall.gateway.filter.PreAuthenticationFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
​
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
​
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .requestMatchers()
                .anyRequest()
                .and()
                .authorizeRequests()
                .antMatchers("/application").permitAll()
                // 放开token授权、token key获取、token验证端点
                .antMatchers(PreAuthenticationFilter.TOKEN_ENDPOINT).permitAll()
                .antMatchers(PreAuthenticationFilter.TOKEN_KEY_ENDPOINT).permitAll()
                .antMatchers(PreAuthenticationFilter.CHECK_TOKEN_ENDPOINT).permitAll()
                .anyRequest()
                .authenticated();
    }
}

 

改造PreAuthenticationFilter

同ResourceServerConfiguration,前置安全校验也需要放开公共端点。另外,此过滤器还可进行相关权限拦截、用户信息解析并传递等逻辑。至于其它的,如接口审计,可单独创建过滤器处理,不过需要注意执行顺序及过滤器类型

package com.luas.xmall.gateway.filter;
​
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.provider.authentication.BearerTokenExtractor;
import org.springframework.security.oauth2.provider.authentication.TokenExtractor;
import org.springframework.stereotype.Component;
​
import javax.servlet.http.HttpServletRequest;
​
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
​
@Component
public class PreAuthenticationFilter extends ZuulFilter {
​
    public static final String TOKEN_ENDPOINT = "/gateway/auth/oauth/token";
​
    public static final String TOKEN_KEY_ENDPOINT = "/gateway/auth/oauth/token_key";
​
    public static final String CHECK_TOKEN_ENDPOINT = "/gateway/auth/oauth/check_token";
​
    private Logger logger = LoggerFactory.getLogger(getClass());
​
    private TokenExtractor tokenExtractor = new BearerTokenExtractor();
​
    @Override
    public String filterType() {
        return PRE_TYPE;
    }
​
    @Override
    public int filterOrder() {
        return 0;
    }
​
    @Override
    public boolean shouldFilter() {
        RequestContext requestContext = RequestContext.getCurrentContext();
​
        HttpServletRequest request = requestContext.getRequest();
​
        String requestURI = request.getRequestURI();
​
        return !(requestURI.equals(TOKEN_ENDPOINT) && requestURI.equals(TOKEN_KEY_ENDPOINT) && requestURI.equals(CHECK_TOKEN_ENDPOINT));
    }
​
    @Override
    public Object run() throws ZuulException {
        RequestContext requestContext = RequestContext.getCurrentContext();
​
        HttpServletRequest request = requestContext.getRequest();
​
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("uri {}", request.getRequestURI());
        }
​
        Authentication authentication = this.tokenExtractor.extract(request);
​
        if (authentication == null || authentication.getPrincipal() == null) {
            //不会继续往下执行 不会调用服务接口了 网关直接响应给客户了
            requestContext.setSendZuulResponse(false);
            requestContext.setResponseBody("Full authentication is required to access this resource");
            requestContext.setResponseStatusCode(401);
            return null;
        }
​
        String accessToken = (String) authentication.getPrincipal();
​
        this.logger.info("token {}", accessToken);
​
        // todo 解析token,调用权限支撑平台,获取权限信息,组织到用户信息中传递给下游微服务
return null;
    }
}

 

验证

依次启动xmall-auth、xmall-product、xmall-zuul,端口分别为7777、8080、5566。

先直接访问http://localhost:8080/sku/1122,可正常访问。

通过网关访问http://localhost:5566/gateway/product/sku/1122,发现已不能正常访问,需要先进行身份认证。

通过网关调用授权服务,进行授权,地址为:

http://localhost:5566/gateway/auth/oauth/token?client_id=client_1&client_secret=123456&username=user_1&password=123456&scope=server&grant_type=password。

请求之后,正常返回授权结果。

此时,说明网关改造成功,已正常发挥资源服务器作用,针对所请求资源联合授权服务器进行身份认证。

header方式携带授权,重新访问http://localhost:8080/sku/1122,可正常访问。

网关整合安全框架以在网关进行身份认证完成,其它功能如日志收集、接口审计、鉴权等,读者可自行去参整合实现。后续也会出相关文章,对部分功能进行整合说明。

 

源码

github

https://github.com/liuminglei/SpringCloudLearning/tree/master/10/

gitee

https://gitee.com/xbd521/SpringCloudLearning/tree/master/10/

 

 

微信搜索【银河架构师】,发现更多精彩内容。

技术资料领取方法:关注公众号,回复微服务,领取微服务相关电子书;回复MK精讲,领取MK精讲系列电子书;回复JAVA 进阶,领取JAVA进阶知识相关电子书;回复JAVA面试,领取JAVA面试相关电子书,回复JAVA WEB领取JAVA WEB相关电子书。

posted @ 2020-01-16 15:30  银河架构师  阅读(2194)  评论(0编辑  收藏  举报