【无标题】Spring Cloud Gateway 与权限认证

服务网关的概念有点类似于传统的反向代理服务器(如nginx),但反向代理一般都只是做业务无关的转发请求,而服务网关与服务的整合程度更高,可以看作也是整个服务体系的组成部分,通过过滤器等组件可以在网关中集成一些业务处理的操作(比如权限认证等)。Spring Cloud Gateway正是Spring官方推出的服务网关的实现框架,它主要包含三个核心的概念:

Route: 负责将某个外部请求路由到一个合适的地址,包含一个ID,一个目标地址,一系列的Predicate和Filter;
Predicate: 基于Java 8 Function Predicate的断言机制,用于将请求匹配到某一个Route
Filter: 类似于Servlet filter,可以在请求传递给下一级处理器之前对请求或响应进行修改,用于实现权限验证,日志记录,限流等功能
在这里插入图片描述

网关集成

我们现在来为我们的demo项目加入一个服务网关。首先需要创建一个新的模块,名字叫Gateway,在pom.xml中加入如下依赖:

org.springframework.cloud spring-cloud-starter-gateway

在application.yml中加入如下内容:

server:
  port: 9000

spring:
  application:
    name: gateway
  cloud:
    consul:
      host: 192.168.1.220
      port: 8500
      discovery:
        prefer-ip-address: true
    gateway:
      routes:
        - id: order-service
          #lb协议会激活LoadBalancerClient来解析后续的地址,自动根据注册的服务实例进行负载均衡
          uri: lb://order-service
          filters:
            - Log
            # 转发时去掉请求地址的服务名前缀
            - StripPrefix=1
          predicates:
            - Path=/order-service/**

从以上配置可以很容易看出来,gateway模块其实也会注册到consul中成为一个服务,并通过consul获取其它服务的相关信息。上面的配置中我们加入了一个名为order-service的路由,其中predicates定义了这个路由的匹配规则,也就是访问路径以/order-service/开头的请求,就会被路由到 lb://order-service的地址 (地址代表的含义参见注释)。

断言

predicates用于定义route的匹配规则,可以针对请求的几乎所有内容进行匹配,例如针对特定的header进行匹配:

predicates:
  - Header=X-Request-Id, \d+**

针对Cookie进行匹配:

predicates:
  - Cookie=mycookie,mycookievalue

匹配特定域名的请求

predicates:
  - Host=**.somehost.org,**.anotherhost.org

更多predicates种类的介绍可以查看 这里

过滤器

刚才的路由配置中,我们定义了两个过滤器: Log,StripPrefix,这些都属于GatewayFilter,每个Route可以定义多个GatewayFilter。Spring Cloud Gateway已经内置了多个很有用的GatewayFilter实现,例如StripPrefix就是内置的用于转发时修改请求地址的过滤器。其它内置过滤器的作用可以查看 这里。如果内置过滤器不能满足我们的需求,那就需要自行实现新的过滤器了。

我们现在来添加一个简单的过滤器日志过滤器,用于打印出每次请求所花费的时间:

@Slf4j
public class LogGatewayFilterFactory extends AbstractGatewayFilterFactory<LogGatewayFilterFactory.Config> {

    private static final String REQUEST_START_TIME = "request_start_time";


    public LogGatewayFilterFactory() {
        // 这里需要将自定义的config传过去,否则会报告ClassCastException
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            exchange.getAttributes().put(REQUEST_START_TIME, System.currentTimeMillis());
            return chain.filter(exchange).then(
                    Mono.fromRunnable(() -> {
                        Long startTime = exchange.getAttribute(REQUEST_START_TIME);
                        if (startTime != null) {
                            log.info("请求地址:{},消耗时间:{}ms", exchange.getRequest().getURI(), System.currentTimeMillis() - startTime);
                        }
                    })
            );
        };
    }

    public static class Config {
    }
}

自定义过滤器需要实现一个新的GatewayFilterFactory,其类名也需要遵循XXXGatewayFilterFactory的规则,这样的话在配置中只需要配置“XXX”的部分就可以正常被识别了,例如 LogGatewayFilterFactory就只需要配置成“Log”就行了。代码中的内部类Config是用于接收配置时传递的参数(类似于Log=true),这里不需要参数所以只是一个空类。需要注意的是Spring Cloud Gateway是使用 Spring WebFlux 来构建的,所以filter这里的写法是基于Reactor异步模式的,和传统的同步请求模式(如Spring MVC)不太一样。

定义了新的过滤器之后需要将其注册到容器:

  @Bean
    public LogGatewayFilterFactory logGatewayFilterFactory() {
        return new LogGatewayFilterFactory();
    }

GatewayFilter都是基于Route进行配置的,Spring Cloud Filter还定义了一种GlobalFilter,不需要在配置文件中配置,作用在所有的路由上。GlobalFilter同样支持自定义新的过滤器,只需要实现GlobalFilter和Ordered接口即可,详细情况我们后面在讲到权限的时候再介绍。

权限管理
服务网关的一大作用就是可以对外部的请求进行集中权限认证,这样每个具体的服务就不用操心权限管理的问题了,可以专心于业务的实现。基本的思路是外部客户端首先需要获取一个由系统中独立的认证中心负责签发的accessToken,然后每次请求服务时在http header中携带该Token,服务网关负责校验accessToken的有效性以及是否具备访问该服务的权限,具体的思路和我之前介绍单系统权限管理的思路比较类似,可以查看 Spring Boot整合Shiro和JWT的无状态权限管理方案 这篇文章。

我们首先需要在服务网关中定义一个GlobalFilter对所有的外部请求进行过滤,代码如下:

@Slf4j
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    private AuthService authService;

    private AuthConfigProperties authConfig;

    public AuthGlobalFilter(AuthConfigProperties authConfig, AuthService authService) {
        this.authConfig = authConfig;
        this.authService = authService;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String reqPath = exchange.getRequest().getURI().getPath();
        String token = exchange.getRequest().getHeaders().getFirst(authConfig.getHeaderKeyOfToken());
        if (!authService.verifyToken(reqPath, token)) {
            log.warn("没有授权的访问,{}", reqPath);
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        //获取token中存储的用户唯一标识,并放入request header中,供后端业务服务使用
        String account = authService.getAccountByToken(token);
        ServerHttpRequest request = exchange.getRequest().mutate()
                .header(authConfig.getHeaderKeyOfAccount(), account).build();
        return chain.filter(exchange.mutate().request(request).build());
    }

    /**
     * 过滤器的优先级,越低越高
     */
    @Override
    public int getOrder() {
        return 1;
    }

}
功能很简单,就是对请求头部的token进行校验,如果成功就将从token中解析出来的用户账户信息放入转发的请求头中供后端的业务服务使用,否则返回UNAUTHORIZED。这个Filter也需要注册到容器中:

    @Bean
    public AuthGlobalFilter authGlobalFilter(AuthService authService) {
        return new AuthGlobalFilter(authConfig, authService);
    }
对token进行校验的核心逻辑在authService.verifyToken方法中,代码如下:

 /**
     * 验证token的有效性及是否具备对该url的访问权限,
     * 判定规则参考了shiro的一些设定
     */
    public boolean verifyToken(String url, String token) {
        if (Strings.isNullOrEmpty(token)) {
            return false;
        }
        //获取每个Url所对应的权限控制符
        String urlPermission = getUrlPermission(url);
        if ("anno".equals(urlPermission)) {
            return true;
        } else {
            //获取token中包含的用户唯一标识
            String account = jwtHelper.getAccount(token);
            if (Strings.isNullOrEmpty(account)) {
                return false;
            }
            //获取token的加密密钥
            String secret = getUserSecret(account);
            //校验accessToken
            if (jwtHelper.verify(token, secret) == null) {
                return false;
            }
            // 如果url仅要求验证用户有效性,则直接通过
            if (Strings.isNullOrEmpty(urlPermission) ||
                    "authc".equals(urlPermission)) {
                return true;
            }
            // 进一步判断用户权限
            if (urlPermission.startsWith("perms")) {
                Set<String> userPerms = this.getUserPermissions(account);
                String perms = urlPermission.substring(urlPermission.indexOf("[") + 1, urlPermission.lastIndexOf("]"));
                return userPerms.containsAll(Arrays.asList(perms.split(",")));
            }
        }
        return false;
    }

服务网关首先需要知道不同的服务地址需要什么样的权限才允许访问,这里采用了类似Shiro配置的格式,类似这样如下的格式,实际环境中可能是从数据库或配置文件中读取:

 /**
     * 获取所有的接口url与用户权限的映射关系,格式仿造了shiro的权限配置格式
     */
    public Map<String, String> getAllUrlPermissionsMap() {
        Map<String, String> urlPermissionsMap = Maps.newHashMap();
        urlPermissionsMap.put("/api/order/orders", "authc");
        urlPermissionsMap.put("/api/order/create-order", "perms[order]");
        urlPermissionsMap.put("/api/storage/**", "perms[storage]");
        return urlPermissionsMap;
    }

通过Spring 提供的工具类AntPathMatcher,就可以查询到每个请求url所需要的权限标识符,再根据权限标识符去检查token对应的用户是否具备相应的权限
引用该文章,点击查看详情。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: Spring Cloud Gateway可以通过多种方式进行权限验证,其中包括: 1. 基于JWT的验证:使用JWT令牌对请求进行验证,可以通过配置JWT验证过滤器来实现。 2. 基于OAuth2的验证:使用OAuth2协议进行验证,可以通过配置OAuth2验证过滤器来实现。 3. 基于IP地址的验证:使用IP地址对请求进行验证,可以通过配置IP地址验证过滤器来实现。 4. 基于角色的验证:使用用户角色对请求进行验证,可以通过配置角色验证过滤器来实现。 以上是Spring Cloud Gateway权限验证的一些常见方式,具体实现方式可以根据实际需求进行选择和配置。 ### 回答2: ### 回答3: Spring Cloud Gateway是当前最受欢迎的网关解决方案之一。它可以通过路由请求来保护和管理你的微服务架构,使其更加安全可靠。 权限验证就是网关解决方案中必不可少的一部分,它可以防止未经授权的用户访问你的微服务。在Spring Cloud Gateway中,常见的权限控制方式有基于JWT(token)和基于OAuth2认证两种方式。 基于JWT(token)的权限控制 JWT(token)是目前常用的凭证验证方式,它将用户的身份信息以加密方式保存在token中,然后将token返回给客户端。客户端随后将token作为身份验证凭据,随后的每个请求都需要携带这个凭据请求。这样就可以实现客户端与服务器之间的认证、授权和信息传递。 在Spring Cloud Gateway中,我们可以通过过滤器来实现基于JWT的权限验证。例如,我们可以通过RTokenAuthenticationFilter来拦截所有请求并解析JWT Token,在请求消息头中添加解析后的用户身份信息,这样我们就可以在后续的服务中进行判断这个用户是否有权限访问对应的服务资源。 基于OAuth2认证权限控制 OAuth2认证是一种基于令牌(Token)的授权方式,也是当前比较流行的授权方式之一。OAuth2通过认证服务器和资源服务器之间的通信完成授权,并且允许用户将自己的授权信息分享给第三方应用程序,以此来限制访问和提高安全性。 在Spring Cloud Gateway中,我们同样可以通过过滤器来实现OAuth2的授权验证。例如,通过OAuth2Filter过滤器来进行Token的鉴权认证,首先从请求头中获取Token,然后请求认证服务器进行认证并返回请求结果。如果Token合法,则放行到下一步,否则拒绝访问。 总的来说,基于JWT(Token)的授权验证更为简单,而且中间代理所需的资源较少,但是其安全性比较低。基于OAuth2认证的方式需要通过认证服务器来完成,可以实现更高级别的安全控制。但是其配置较为复杂,需要更多的中间代理层的支持。个人认为,对于小型应用程序来说,使用JWT基本已经足够,对于中大型应用程序,可以考虑使用OAuth2。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SuperChen12356

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值