SpringCloud网关Gateway认证鉴权【SpringCloud系列7】_springcloud gateway 鉴权-程序员宅基地

技术标签: spring  java点滴积累  SpringCloud  spring cloud  gateway  

SpringCloud 大型系列课程正在制作中,欢迎大家关注与提意见。
程序员每天的CV 与 板砖,也要知其所以然,本系列课程可以帮助初学者学习 SpringBooot 项目开发 与 SpringCloud 微服务系列项目开发

本文章是系列文章中的一篇

在这里插入图片描述
本文章实现的是 Gateway 网关中的令牌校验功能 ,上图中所示用户所有的访问全部走网关,然后在网关每次都调用 auth-api 鉴权,当访问量足够大的时候,还是会有访问性能问题,所以优化如下:
在这里插入图片描述

1 网关Gateway 添加 security 与 oauth2 相关配置

这里添加的 security 与 oauth2 相关配置 ,是为了解密 auth-api 中生成的 access_token 令牌信息

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>

然后添加安全拦截配置

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;

/**
 * @description 安全配置类
 * @author 早起的年轻人
 */
 @EnableWebFluxSecurity
 @Configuration
 public class SecurityConfig {
    
  //安全拦截配置
  @Bean
  public SecurityWebFilterChain webFluxSecurityFilterChain(ServerHttpSecurity http) {
    

   return http.authorizeExchange()
           .pathMatchers("/**").permitAll()
           .anyExchange().authenticated()
           .and().csrf().disable().build();
  }
 }

配置 JwtAccessTokenConverter 所使用的密钥信息

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

/**
 * @author 早起的年轻人
 * @version 1.0
 **/
@Configuration
public class TokenConfig {
    

    String SIGNING_KEY = "test_key";

    @Autowired
    private JwtAccessTokenConverter accessTokenConverter;

    @Bean
    public TokenStore tokenStore() {
    
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
    
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(SIGNING_KEY);
        return converter;
    }

}

然后 网关配置文件中添加 auth-api 相关的路由

server:
  port: 10001
spring:
  application:
    name: '@project.name@'
  cloud:
    nacos:
      server-addr: localhost:8848 # nacos地址
    gateway:
      routes: # 网关路由配置
        - id: rewritepath_route
          uri: https://www.baidu.com/
          predicates:
            - Path=/search/**
          filters:
            - RewritePath=/search/(?<segment>.*), /$\{
    segment}

        - id: user-service # 路由id,自定义,只要唯一即可
          # uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
          uri: lb://user-service # 路由的目标地址 lb就是负载均衡,后面跟服务名称
          predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
            - Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求
        - id: order-service # 路由id,自定义,只要唯一即可
          # uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
          uri: lb://order-service
          predicates:
            - Path=/order/**

        - id: auth-api # 路由id,自定义,只要唯一即可
          # uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
          uri: lb://auth-api
          predicates:
            - Path=/oauth/**
2 网关认证过虑器
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * @author 早起的年轻轻人
 * @version 1.0
 * @description 网关认证过虑器
 */
@Component
@Slf4j
public class GatewayAuthFilter implements GlobalFilter, Ordered {
    


    //白名单
    private static List<String> whitelist = null;

    static {
    
        //加载白名单
        try (
                InputStream resourceAsStream = GatewayAuthFilter.class.getResourceAsStream("/security-whitelist.properties");
        ) {
    
            Properties properties = new Properties();
            properties.load(resourceAsStream);
            Set<String> strings = properties.stringPropertyNames();
            whitelist= new ArrayList<>(strings);

        } catch (Exception e) {
    
            whitelist = new ArrayList<>();
            log.error("加载/security-whitelist.properties出错:{}",e.getMessage());
            e.printStackTrace();
        }


    }

    @Autowired
    private TokenStore tokenStore;


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    
        //请求的url
        String requestUrl = exchange.getRequest().getPath().value();
        AntPathMatcher pathMatcher = new AntPathMatcher();
        //白名单放行
        for (String url : whitelist) {
    
            if (pathMatcher.match(url, requestUrl)) {
    
                return chain.filter(exchange);
            }
        }
        //检查token是否存在
        String token = getToken(exchange);
        if (StringUtils.isBlank(token)) {
    
            return buildReturnMono("没有认证",exchange);
        }
        //判断是否是有效的token
        OAuth2AccessToken oAuth2AccessToken;
        try {
    
            oAuth2AccessToken = tokenStore.readAccessToken(token);

            boolean expired = oAuth2AccessToken.isExpired();
            if (expired) {
    
                return buildReturnMono("认证令牌已过期",exchange);
            }
            return chain.filter(exchange);
        } catch (InvalidTokenException e) {
    
            log.info("认证令牌无效: {}", token);
            return buildReturnMono("认证令牌无效",exchange);
        }

    }

    /**
     * 获取token
     */
    private String getToken(ServerWebExchange exchange) {
    
        String tokenStr = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (StringUtils.isBlank(tokenStr)) {
    
            return null;
        }
        return tokenStr;
    }




    private Mono<Void> buildReturnMono(String error, ServerWebExchange exchange) {
    
        ServerHttpResponse response = exchange.getResponse();
        Map<String,Object> map = new HashMap<>();
        map.put("code",403);
        map.put("message",error);

        String jsonString = JSON.toJSONString(map);
        byte[] bits = jsonString.getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }


    @Override
    public int getOrder() {
    
        return 0;
    }
}

Spring Cloud Gateway 根据作用范围划分为 GatewayFilter 和 GlobalFilter

  • GatewayFilter : 需要通过spring.cloud.routes.filters 配置在具体路由下,只作用在当前路由上或通过spring.cloud.default-filters配置在全局,作用在所有路由上。
  • GlobalFilter : 不需要在配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器
3 启动服务 测试

在这里插入图片描述
首先通过网关访问订单详情

http://localhost:10001/order/109

使用很久之前的一个token
在这里插入图片描述
然后再通过网关获取令牌
在这里插入图片描述
然后使用新获取到的令牌来访问订单详情
在这里插入图片描述

4 获取token中的用户信息

在网关中将token解析,获取登录 token 中对应的用户 userId , 在网关中的令牌校验过滤器 GatewayAuthFilter 中添加内容:

public class GatewayAuthFilter implements GlobalFilter, Ordered {
    


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    
        
        ... ... 
            //----------获取token中的用户令牌--------------------------------------------------------------------
            OAuth2Authentication authentication = tokenStore.readAuthentication(token);
            User authUser = (User) authentication.getPrincipal();
            //获取保存的用户令牌 我这里是一个JSON 
            String username = authUser.getUsername();
            //使用Fastjson 将json字符串转为map
            Map<String, Object> parse = JSON.parseObject(username, Map.class);
            //获取其中的 userId
            String userId = parse.get("userId").toString();

            ServerHttpRequest req = exchange.getRequest();
            HttpHeaders httpHeaders = req.getHeaders();
            ServerHttpRequest.Builder requestBuilder = req.mutate();
            // 先删除,后新增
            //requestBuilder.headers(k -> k.remove("要修改的header的key"));
            // requestBuilder.header("要修改的header的key", 处理完之后的header的值);

            // 或者直接修改,要求修改的变量为final
            requestBuilder.headers(k -> k.set("userId", userId));
            log.info("令牌解析成功:userId is {}",userId);

            ServerHttpRequest request = requestBuilder.build();
            exchange.mutate().request(request).build();

            return chain.filter(exchange);
        } catch (InvalidTokenException e) {
    
            log.info("认证令牌无效: {}", token);
            return buildReturnMono("认证令牌无效", exchange);
        }
    }
}

这里是获取了用户的 userId ,然后将userId添加到请求头中,比如在后续的 admin 管理后台的服务中,可以直接通过 @RequestHeader 获取
在这里插入图片描述

到此 网关中的鉴权功能开发完成。

本项目源码 https://gitee.com/android.long/spring-cloud-biglead/tree/master/biglead-api-07-auth
如果有兴趣可以关注一下公众号 biglead ,每周都会有 java、Flutter、小程序、js 、英语相关的内容分享

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zl18603543572/article/details/129699896

智能推荐

报表开发步骤-程序员宅基地

文章浏览阅读1.5k次。报表开发步骤_报表开发

Orcel数据库之迷(一)——HR用户不见了?_oracle 19c hr用户不存在-程序员宅基地

文章浏览阅读1.7k次,点赞8次,收藏11次。Orcel数据库之迷(一)——HR用户不见了?Orcle数据库学习问题发现——我的HR用户呢?问题由来——为什么HR它就没了呢?问题解决——我的HR用户又回来了!以sys或system用户执行脚本文件步骤解锁HR用户并修改用户密码学习就能进步Orcle数据库学习今天发现了一个较为普遍的问题,在此进行解决,欢迎讨论!!!问题发现——我的HR用户呢?我们在学习Orcle数据库的过程中,总是会使..._oracle 19c hr用户不存在

MySQL 查询时间差值大于某一个值的 记录-程序员宅基地

文章浏览阅读2.2k次。"SELECT table_id, FROM table WHERE (timediff('%s',raise_time)<'00:05:00')" % \(table_id, str(datetime.datetime.now()))转载于:https://www.cnblogs.com/buxizhizhoum/p/6780181.html_mysql查询 相邻记录时间差大于某个数

tensorflow学习笔记1(代码转自官网)_tensor(const:0)-程序员宅基地

文章浏览阅读608次。tensorflow学习笔记_tensor(const:0)

最适合入门的100个深度学习项目-程序员宅基地

文章浏览阅读8.9w次,点赞259次,收藏2k次。本专栏整理了《PyTorch深度学习项目实战100例》,内包含了各种不同的深度学习项目,包含项目原理以及源码,每一个项目实例都附带有完整的代码+数据集。_深度学习项目

力士乐A10V泵控制比例溢流阀放大器-程序员宅基地

文章浏览阅读69次。具有故障自动防护功能,可用于诸如制动或转向系统。– 具有精细测量范围内的更高分辨率。– 电磁比例压差控制,与电流相关。– 可进行转速传感式功率控制。– 标准外置式比例放大器。– 油缸行程的末端带阻尼。– 易于控制最大流量。

随便推点

NLP论文阅读记录-EMNLP 2023 | 01 Improving Summarization with Human Edits-程序员宅基地

文章浏览阅读915次,点赞30次,收藏17次。通过人工编辑改进摘要codepaper最近的工作表明,利用人类反馈范式进行学习可以产生人类决定的高质量文本。现有的工作使用人类反馈来训练通用领域抽象摘要中的大型语言模型(LLM),并获得了超过传统似然训练的摘要质量。在本文中,我们重点关注一种较少探索的人类反馈形式——人类编辑。我们提出了序列比对(非)似然训练(SALT),这是一种在训练循环中同时使用人工编辑和模型生成的数据的新技术。

Webrtc delay-base-bwe代码分析(6): 整体分析_remotebitrateestimatorsinglestream::detector-程序员宅基地

文章浏览阅读2k次,点赞2次,收藏2次。Webrtc delay-base-bwe代码分析(6): 整体分析_remotebitrateestimatorsinglestream::detector

稳健估计/M估计/最小二乘法-程序员宅基地

文章浏览阅读2.9w次,点赞19次,收藏140次。稳健估计是在粗差不可避免的情况下,选择适当的估计方法使未知量估计尽可能减免粗差的影响,得出正常模式下的最佳估计。本文先介绍基于最小二乘法的多元线性回归理论,再引出基于M估计的加权最小二乘估计。_m估计

明明没用到hystrix相关的东西,为什么启动会报Hystrix相关的错误呢?_hystrixcommand org.springframework.boot.autoconfig-程序员宅基地

文章浏览阅读385次。最近在优化网关性能,去掉无用的依赖是发现,我项目里明明没有用到hystrix相关的东西,为什么把hystrix依赖去掉,启动就会报错呢?报错信息如下:java.lang.IllegalStateException: Error processing condition on org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration.propertySourcesPlaceholderConfig_hystrixcommand org.springframework.boot.autoconfigure.condition.onpropertyco

SLF4J: Failed to load class “org.slf4j.impl.StaticLoggerBinder” 解决方法_slf4j: failed to load class "org.slf4j.impl.static-程序员宅基地

文章浏览阅读766次。提示SLF4J: Failed to load class “org.slf4j.impl.StaticLoggerBinder”完整解释SLF4J解释原因为: Failed to load class org.slf4j.impl.StaticLoggerBinder This warning message is reported when the org.slf4j.impl..._slf4j: failed to load class "org.slf4j.impl.staticloggerbinder

Linux_无法定位软件包的处理方法(vsftpd、dsniff、tree)_linux无法定位软件包-程序员宅基地

文章浏览阅读1.9k次,点赞2次,收藏8次。刚刚重新安装完系统,发现无法定位文件位置,诸如vsftpd、dsniff、tree等等,这是因为服务器的源不在国内,需要定位一个国内的服务器源,然后将指令更新才行,按顺序分别点击 ->1 文件 ->2 etc ->3 apt里面来,双击sources.list。我这里是虚拟的ubunt64位系统,开始用的桥接方式,发现不能联网,这也就不能在后续更新指令。ubuntu软件 -> 点击源代码(需要密码) -> 服务器选择:其他站点。-> 确定 -> 开启虚拟机。_linux无法定位软件包

推荐文章

热门文章

相关标签