java脚手架系列9-统一权限认证gateway

之所以想写这一系列,是因为之前工作过程中有几次项目是从零开始搭建的,而且项目涉及的内容还不少。在这过程中,遇到了很多棘手的非业务问题,在不断实践过程中慢慢积累出一些基本的实践经验,认为这些与业务无关的基本的实践经验其实可以复刻到其它项目上,在行业内可能称为脚手架,因此决定将此java基础脚手架的搭建总结下来,分享给大家使用。

注意由于框架不同版本改造会有些使用的不同,因此本次系列中主要使用基本框架是 spring-boo-2.3.12.RELEASE和spring-cloud.-Hoxton.SR12,所有代码都在commonFramework项目上:https://github.com/forever1986/commonFramework/tree/master

目录

  • [1 gateway](#1 gateway)
  • [2 动态加载路由表](#2 动态加载路由表)
  • [3 鉴权功能](#3 鉴权功能)

1 gateway

之所以将gateway放在这里讲,是因为承接上一篇的OAuth2,其中gateway有一个重要的功能就是鉴权,可以结合OAuth2进行鉴权。

在我们使用微服务架构的时候,免不了需要一个网关,而对于java项目来说,gateway是一个很好的选择。一般实践中,gateway有以下功能:

  • 路由转发:根据请求的特定条件(如 URL路径、请求参数、请求头等)来将请求转发到后端的多个服务,并支持动态路由配置
  • 过滤功能:提供了一套过滤器机制,允许开发人员对请求进行修改和验证,以及应用各种策略,如认证、安全、监控/指标、限流、日志、请求转发/重试等
  • 容错处理:集成断路器(Hystrix),为微服务网关提供容错处理的功能
  • 服务发现:与服务注册中心集成,动态从服务注册中心获取服务信息并进行路由
  • 请求转发:进行请求的协议转换,比如将 HTTP 请求转换成 WebSocket 请求
  • 请求限流:支持通过配置限流规则,对请求进行限流,防止恶意请求或异常情况下的流量冲击

下面以动态读取路由配置+过滤功能,实现gateway的路由转发可以动态加载,以及实现鉴权功能

2 动态加载路由表

参考gateway子模块

在实际应用中,我们不可能将路由配置放入项目的yaml配置文件中,这样修改路由时,都需要重新启动或者发布。这时候,配置中心就起到动态配置路由转发的存储功能,同时gateway也提供RouteDefinitionRepository可进行路由表动态加载。

原理:将路由配置文件放入nacos中,gateway子模块通过实现RouteDefinitionRepository,监听nacos的路由配置文件,实现动态更新路由配置

1)创建gateway子模块,引入以下依赖:

yaml 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--监听配置文件的更新 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--获取配置文件 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

2)配置bootstrap.yml,要配置服务发现和配置中心的信息

yaml 复制代码
server:
  port: 9991
spring:
  application:
    name: cloud-gateway-service
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        username: nacos
        password: nacos
      config:
        server-addr: 127.0.0.1:8848
        username: nacos
        password: nacos
        file-extension: yaml
        group: DEFAULT_GROUP
        prefix: ${spring.application.name}

3)实现NacosRouteDefinitionRepository(实现RouteDefinitionRepository),用于自动监听配置文件并刷新路由表

java 复制代码
package com.demo.route.config;

import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;

/**
 * nacos路由数据源
 */
@Component
public class NacosRouteDefinitionRepository implements RouteDefinitionRepository {

    private static final Logger logger = LoggerFactory.getLogger(NacosRouteDefinitionRepository.class);

    private static final String SCG_DATA_ID = "cloud-gateway-service";
    private static final String SCG_GROUP_ID = "DEFAULT_GROUP";

    private final ApplicationEventPublisher publisher;

    private final NacosConfigManager nacosConfigManager;

    public NacosRouteDefinitionRepository(ApplicationEventPublisher publisher, NacosConfigManager nacosConfigManager) {
        this.publisher = publisher;
        this.nacosConfigManager = nacosConfigManager;
        addListener();
    }

    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        List<RouteDefinition> routeDefinitionList = new ArrayList<>(0);
        try {
            String configContent = nacosConfigManager.getConfigService().getConfig(SCG_DATA_ID, SCG_GROUP_ID, 5000);

            if (StringUtils.isNotEmpty(configContent)) {
                routeDefinitionList = JSON.parseArray(configContent, RouteDefinition.class);
            }
        } catch (NacosException e) {
            logger.error("从Nacos加载配置的动态路由信息异常", e);
        }
        return Flux.fromIterable(routeDefinitionList);
    }

    /**
     * 添加Nacos监听
     */
    private void addListener() {
        try {
            nacosConfigManager.getConfigService()
                    .addListener(SCG_DATA_ID, SCG_GROUP_ID, new Listener() {

                        @Override
                        public Executor getExecutor() {
                            return null;
                        }

                        @Override
                        public void receiveConfigInfo(String configInfo) {
                            publisher.publishEvent(new RefreshRoutesEvent(this));
                        }
                    });
        } catch (NacosException e) {
            logger.error("添加Nacos监听异常", e);
        }
    }

    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
        return null;
    }

    @Override
    public Mono<Void> delete(Mono<String> routeId) {
        return null;
    }
}

4)设置GatewayApplication设置@EnableDiscoveryClient注册到nacos

5)在nacos的public命名空间和DEFAULT_GROUP分组中,配置nacos的路由配置名为:cloud-gateway-service的json文件

json 复制代码
[
  {
    "id": "baidu",
    "order": 0,
    "predicates": [{
      "args": {
        "pattern": "/baidu/**"
      },
      "name": "Path"
    }],
    "uri": "http://www.baidu.com",
    "filters": [{
      "args": {
        "_genkey_0": "2"
      },
      "name": "StripPrefix"
    }]
  },
  {
    "id": "manager",
    "predicates": [{
      "args": {
        "pattern": "/manager/**"
      },
      "name": "Path"
    }],
    "uri": "http://localhost:9981",
    "filters": [{
      "args": {
        "_genkey_0": "1"
      },
      "name": "StripPrefix"
    }]
  }
]

6)通过请求以下访问,可以得到转发;同时你可以在nacos上面修改cloud-gateway-service的json文件路由,可以测试动态刷新

http://localhost:8891/baidu/aa

http://localhost:8891/business/business

3 鉴权功能

参考gateway子模块

网关的过滤功能:提供了一套过滤器机制,允许开发人员对请求进行修改和验证,以及应用各种策略,如认证、安全、监控/指标、限流、日志、请求转发/重试等。我们这里的鉴权就是使用过滤功能。在配置鉴权功能之前,我们有必要了解一下内容:

  • 鉴权原理:在系列8中我们讲过security、JWT、oauth的内容,这里利用auth-authentication子模块生成token,我们知道验证token的有效性2种方法,一种是使用访问auth-authentication子模块的access_token接口验证,一种是通过RSA非对称加密验证。这里的token采用RSA非对称加密验证(这是因为本身路由访问非常频繁的服务,不适合频繁访问access_token接口)。
  • gateway过滤:在gateway中有3个比较重要的过滤:ReactiveAuthenticationManager->ReactiveAuthorizationManager->Gateway Filters。

1)其中ReactiveAuthenticationManager用于封装JWT为OAuth2Authentication并判断Token的有效性;

2)ReactiveAuthorizationManager用于基于URL的鉴权;

3)Gateway Filters是网关的过滤流程。

下面我们通过配置JWT+RSA的解密对token进行认证,另外通过实现ReactiveAuthorizationManager对其进行鉴权。

1)引入以下依赖:

xml 复制代码
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-oauth2-resource-server</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-oauth2-jose</artifactId>
</dependency>

3)编写ResourceServerManager(实现ReactiveAuthorizationManager),实现其check接口,获得用户信息,对权限进行判断(这里只是演示一下,没有实际权限判断代码,实际业务中可以加入一些权限判断,比如路由权限、RABC等业务判断功能)

4)编写ResourceServerConfig,通过注入securityWebFilterChain类,将JWT认证和ResourceServerManager都设置到过滤链中

5)在ResourceServerConfig中,通过注入jwtAuthenticationConverter,实现JWT+RSA的公钥,对token进行认证

6)编写SecurityGlobalFilter(实现GlobalFilter,和Ordered),做最后处理(当然如果没有此需求,也可以不做处理)

7)通过请求以下访问,可以得到转发(前提是你需要在header中增加token)

http://localhost:8891/baidu/aa

http://localhost:8891/business/business

相关推荐
《源码好优多》11 分钟前
基于Java Springboot出租车管理网站
java·开发语言·spring boot
·云扬·4 小时前
Java IO 与 BIO、NIO、AIO 详解
java·开发语言·笔记·学习·nio·1024程序员节
求积分不加C4 小时前
Spring Boot中使用AOP和反射机制设计一个的幂等注解(两种持久化模式),简单易懂教程
java·spring boot·后端
枫叶_v5 小时前
【SpringBoot】26 实体映射工具(MapStruct)
java·spring boot·后端
东方巴黎~Sunsiny5 小时前
java-图算法
java·开发语言·算法
2401_857617626 小时前
汽车资讯新趋势:Spring Boot技术解读
java·spring boot·后端
小林学习编程6 小时前
从零开始理解Spring Security的认证与授权
java·后端·spring
写bug的羊羊6 小时前
Spring Boot整合Nacos启动时 Failed to rename context [nacos] as [xxx]
java·spring boot·后端
努力的小陈^O^7 小时前
docker学习笔记跟常用命令总结
java·笔记·docker·云原生
童先生7 小时前
如何将java项目打包成docker 镜像并且可运行
java·开发语言·docker