当前位置: 首页 > news >正文

SpringCloudAlibaba2.2.6.RELEASE集成Gateway并实现JWT鉴权

  • 搭建前提

本文基于Nacos作为注册中心,进行搭建,所以前提是已经搭建了Nacos

Nacos单机版搭建教程
Nacos集群版搭建教程

  • 创建Gateway模块
    项目创建请参考

1.导入依赖

注意gateway的pom文件不要引用MVC的依赖包,不然会报错。

  <dependencies>
        <!--网关gateway-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

        <!-- openfeign服务调用 此处是避免依赖冲突才引入的-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <!-- 服务注册/发现-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <!-- 引入自己定义的api-commons,这样就可以使用共有类 ,注意将自己的依赖包中排除mvc相关依赖,避免依赖传递-->
        <dependency>
            <groupId>com.wf</groupId>
            <artifactId>api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>

        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

2.编写启动类

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;


/**
 * @Description: 网关微服务
 */
@EnableDiscoveryClient
@SpringBootApplication
@Slf4j
public class GatewayServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayServiceApplication.class, args);
        log.info("网关微服务启动成功");
    }
}

3.编写配置文件

server:
  port: 9005
spring:
  application:
    name: gateway-service
  cloud:
    #nacos相关配置-------------------------------------------------
    nacos:
      discovery:
        #配置Nacos服务注册地址
        server-addr: 192.168.31.78:8848
    gateway:
      discovery:
        locator:
          #开启注册中心路由功能
          enabled: true

4.最终项目结构
在这里插入图片描述

  • 测试注册中心路由功能
    1.启动全部微服务
    在这里插入图片描述
    2.不通过Gateway访问9004微服务
    请求:http://localhost:9004/test
    在这里插入图片描述
    返回结果:
    在这里插入图片描述

3.通过Gateway访问9004微服务
请求:http://localhost:9005/message-services/test

注:本次请求携带了9004微服务的服务名称且端口号已经换成了9005
在这里插入图片描述

返回结果:
在这里插入图片描述

  • 自定义路由配置
    1.新增yml文件内容
server:
  port: 9005
spring:
  application:
    name: gateway-service
  cloud:
    #nacos相关配置-------------------------------------------------
    nacos:
      discovery:
        #配置Nacos服务注册地址
        server-addr: 192.168.31.78:8848
    gateway:
      discovery:
        locator:
          #开启注册中心路由功能
          enabled: true
      routes:
       #该组配置的一个id值,需要保证他的唯一,可以设置为和服务名一致
        - id: message-services
         #通过条件匹配之后需要路由到的新的服务地址,lb:// 表示开启负载均衡策略去路由 
          uri: lb://message-services
           #url的匹配条件,及断言,规则为url中必须携带 /user/management才能进行转发
          predicates:
            - Path=/message/board/**
          #在路由前对请求的地址进行额外的其他操作,例如拼接或者裁减等。 此处的作用在于去除掉 predicates中配置的路径,因为该路径只是断言,并无实际地址
          filters:
           #表示删除第二个路径,即删除predicates中配置的/message/board
            - StripPrefix=2

2.再次使用上文中的测试地址测试
请求:http://localhost:9005/message-services/test
在这里插入图片描述
返回结果:
在这里插入图片描述
提示404访问不到资源,因为此处使用了自定义路由,那么就应该携带自定义的请求路径

3.使用自定义的请求路由访问
请求:http://localhost:9005/message/board/test

注:此处使用的/message/board在9004微服务中并不存在该资源,此处只是在gateway中yml文件中predicates配置的自定义路由,然后通过filters中的配置去除掉/message/board得到9004微服务的真实资源地址
在这里插入图片描述
返回结果:
在这里插入图片描述

解析请求过程:

在这里插入图片描述

  • JWT统一鉴权
    1.配置过滤器
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.deploy.nativesandbox.comm.Response;
import com.wf.apicommons.utils.CodeEnum;
import com.wf.apicommons.utils.CommonResult;
import com.wf.apicommons.utils.JWTUtils;
import com.wf.apicommons.utils.MD5Util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * @Description: token鉴权过滤
 */
@Component
@Slf4j
public class AuthJwtFilter implements GlobalFilter, Ordered {

    /**
     * 无需鉴权的URL
     */
    private static  final String[] skipAuthUrls={"/user/login","/user/register","/user/getCode/**"};

    @Override
    public Mono<Void>
    filter(ServerWebExchange exchange,GatewayFilterChain chain) {
        //获取请求url地址
        String url =exchange.getRequest().getURI().getPath();
        //跳过不需要验证的路径
        if (null != skipAuthUrls && isSkipUrl(url)) {
            return chain.filter(exchange);
        }

        //从请求头中取得token
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        //标识当前请求来源于网关,屏蔽非法请求

        exchange.getRequest().mutate().header("RequestSource", MD5Util.encryption("from_gateway")).build();

        CommonResult<String> result=new CommonResult<>();

        //判断token是否为null
        if(!StrUtil.hasBlank(token)){
            try{
                //验证令牌
                JWTUtils.verify(token);
                //放行请求
                return chain.filter(exchange);
            } catch (SignatureVerificationException e) {
                //无效签名
                result.setCode(CodeEnum.JWT_INVALID.getCode());
                result.setData(CodeEnum.JWT_INVALID.getMessage());
            } catch (TokenExpiredException e) {
                //token过期
                result.setCode(CodeEnum.JWT_OVERDUE.getCode());
                result.setData(CodeEnum.JWT_OVERDUE.getMessage());
            } catch (AlgorithmMismatchException e) {
                //token算法不一致\
                result.setCode(CodeEnum.JWT_ALGORITHM_INCONSISTENCY.getCode());
                result.setData(CodeEnum.JWT_ALGORITHM_INCONSISTENCY.getMessage());
            } catch (Exception e) {
                //token失效
                result.setCode(CodeEnum.JWT_LOSE_EFFECT.getCode());
                result.setData(CodeEnum.JWT_LOSE_EFFECT.getMessage());
            }
        }else{
            //无效签名
            result.setCode(CodeEnum.JWT_INVALID.getCode());
            result.setData(CodeEnum.JWT_INVALID.getMessage());
        }



        return getFailResponse(exchange.getResponse(),result);
    }

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

    /**
     * 判断当前访问的url是否开头URI是在配置的忽略
     * url列表中
     *
     * @param url
     * @return
     */
    public boolean isSkipUrl(String url) {
        for (String skipAuthUrl : skipAuthUrls) {
            if (url.startsWith(skipAuthUrl)) {
                return true;
            }
        }
        return false;
    }


    /**
     * 获取失败返回信息
     * @param response
     * @param result
     * @return
     */
    private Mono<Void> getFailResponse(ServerHttpResponse response, CommonResult<String> result) {
        DataBuffer buffer = null;
        try {
        //将map转化成json,response使用的是Jackson
        String resultStr = new ObjectMapper().writeValueAsString(result);
            response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
            response.getHeaders().set("Access-Control-Allow-Origin","*");
            response.setStatusCode(HttpStatus.OK);
            response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
            buffer = response.bufferFactory().wrap(resultStr.getBytes("UTF-8"));
        } catch (Exception e) {
            log.info("gateway鉴权错误,{}",e.getMessage());
            e.printStackTrace();
        }
        return response.writeWith(Flux.just(buffer));
    }

}

此处的使用到的自定义工具类:
CodeEnum


import lombok.Data;

/**
 * 状态码枚举
 */
public enum CodeEnum {

    /**操作成功**/
    SUCCESS(200,"操作成功"),
    /**服务调用异常**/
    SERVICE_CALL_EXCEPTION(400,"服务调用异常"),
    /**操作失败**/
    ERROR(500,"操作失败"),
    /**参数不合法**/
    ILLEGAL_PARAMETER(5001,"参数不合法"),
    /**验证码已失效**/
    VERIFICATION_CODE_FAILURE(5002,"验证码已失效"),
    /**用户昵称重复**/
    DUPLICATE_NICKNAME(5003,"用户昵称重复"),
    /**用户名或密码错误**/
    LOGIN_FAILED(5004,"用户名或密码错误"),
    /**文件上传失败**/
    FILE_UPLOAD_FAILED(5005,"文件上传失败"),
    /**资源不存在*/
    RESOURCE_DOES_NOT_EXIST(5006,"资源不存在"),
    /**无效签名**/
    JWT_INVALID(2001,"无效签名"),
    /**token过期**/
    JWT_OVERDUE(2002,"token过期"),
    /**token算法不一致**/
    JWT_ALGORITHM_INCONSISTENCY(2003,"token算法不一致"),
    /**token失效**/
    JWT_LOSE_EFFECT(2004,"token失效"),
    /**非法请求**/
    ILLEGAL_REQUEST(2005,"非法请求,请求来源不合法");

    /**
     * 自定义状态码
     **/
    private Integer code;
    /**自定义描述**/
    private String message;

    CodeEnum(Integer code, String message){
        this.code = code;
        this.message = message;
    }

    public Integer getCode() {
        return code;
    }
    public String getMessage() {
        return message;
    }
}

CommonResult


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 请求信息类,用于返回请求是否成功
 * @param <T>
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T>{
    /**
     * 响应状态码
     */
    private int code;

    /**
     * 响应结果描述
     */
    private String message;

    /**
     * 返回的数据
     */
    private T data;

    /**
     * 成功返回
     * @param data
     * @param <T>
     * @return
     */
    public static <T> CommonResult<T> success(T data) {
        CommonResult<T> response= new CommonResult<>();
        response.setCode(CodeEnum.SUCCESS.getCode());
        response.setMessage(CodeEnum.SUCCESS.getMessage());
        response.setData(data);
        return response;
    }

    /**
     *  失败返回,自定义code
     * @param code
     * @param message
     * @param <T>
     * @return
     */
    public static <T> CommonResult<T> fail(Integer code, String message) {
        CommonResult<T> response = new CommonResult<>();
        response.setCode(code);
        response.setMessage(message);
        return response;
    }

    /**
     *  失败返回
     * @param codeEnum
     * @param <T>
     * @return
     */
    public static <T> CommonResult<T> fail(CodeEnum codeEnum) {
        CommonResult<T> response = new CommonResult<>();
        response.setCode(codeEnum.getCode());
        response.setMessage(codeEnum.getMessage());
        return response;
    }
    /**
     *  失败返回
     * @param message
     * @param <T>
     * @return
     */
    public static <T> CommonResult<T> fail(String message) {
        CommonResult<T> response = new CommonResult<>();
        response.setCode(CodeEnum.ERROR.getCode());
        response.setMessage(message);
        return response;
    }

}

JWTUtils



import cn.hutool.jwt.JWTUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;

/**
 * @Description: JWT工具类
 */
public class JWTUtils {
    //秘钥
    private static final String SIGNATURE = "323123213123213123313";
    //过期时间为1天
   public static  final Integer EXPIRATION_TIME= 1* 24 * 60 * 60;



    /**
     * 生成token
     * @param payload token需要携带的信息
     * @return token字符串
     */
    public static String getToken(Map<String,String> payload){
        // 指定token过期时间为1天
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.SECOND, EXPIRATION_TIME);
        JWTCreator.Builder builder = JWT.create();
        // 构建payload
        payload.forEach((k,v) -> builder.withClaim(k,v));
        // 指定过期时间和签名算法
        return  builder.withExpiresAt(calendar.getTime()).sign(Algorithm.HMAC256(SIGNATURE));
    }




    /**
     * 验证token
     * @param token
     */
    public static void verify(String token){
        JWT.require(Algorithm.HMAC256(SIGNATURE)).build().verify(token);
    }

    /**
     * 获取token中payload
     * @param token
     * @return
     */
    public static DecodedJWT getToken(String token){
        return JWT.require(Algorithm.HMAC256(SIGNATURE)).build().verify(token);
    }

}

MD5Util

import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;


/**
 * @Description: MD5工具类
 */
public class MD5Util {

    /**
     * 加密
     * @param content 需要加密的内容
     * @return
     */
    public static String encryption(String content) {
        if(StrUtil.isNotBlank(content)){
            return SecureUtil.md5(content);
        }
        return null;
    }

    /**
     * 验证
     *
     * @param ciphertext 密文
     * @return
     */
    public static boolean verifyingCiphertext(String ciphertext,String content) {
        if(StrUtil.isNotBlank(ciphertext)&&StrUtil.isNotBlank(content)){
            return   ciphertext.equals(encryption(content));
        }
       return false;
    }


}

注:以上代码需要引入胡图工具包

    <!--胡图工具包-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.2</version>
        </dependency>
  • 让微服务只允许来自网关的请求

真实场景中,往往是把微服务放到各个服务器,并不会对外暴露出来的,仅仅会对网关处的服务器进行开放,因此最好的做法就是不会暴露服务,nginx反向代理到网关,网关转发到对应的服务,真正对外提供的仅有nginx

此处我们在gateway转发请求是在Header中存入一个加密标识(from_gateway),等转发到微服务后再将其获取出来,进行解密,比对,只有正确的才放行

上述AuthJwtFilter类添加如下代码:

     //标识当前请求来源于网关,屏蔽非法请求
 exchange.getRequest().mutate().header("RequestSource", MD5Util.encryption("from_gateway")).build();

在9004微服务中新增如下代码:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wf.apicommons.utils.CodeEnum;
import com.wf.apicommons.utils.CommonResult;
import com.wf.apicommons.utils.MD5Util;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


/**
 * @Description: JWT验证拦截器
 */
public class JWTInterceptor implements HandlerInterceptor {


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        CommonResult<String> result=new CommonResult<>();
        
        //获取请求来源
        String requestSource = request.getHeader("RequestSource");
        boolean decrypt = MD5Util.verifyingCiphertext(requestSource,"request_from_gateway");
        //验证请求来源是否为网关转发
        if(decrypt){
            //放行请求
            return true;
        }else{
            result.setCode(CodeEnum.ILLEGAL_REQUEST.getCode());
            result.setData(CodeEnum.ILLEGAL_REQUEST.getMessage());
        }
        
        //将map转化成json,response使用的是Jackson
        String json = new ObjectMapper().writeValueAsString(result);
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json;charset=UTF-8");
        response.setHeader("Access-Control-Allow-Origin","*");
        response.getWriter().print(json);
        return false;
    }
}

测试:
请求:http://localhost:9004/test
在这里插入图片描述
返回结果:
在这里插入图片描述
通过网关测试:
请求:http://localhost:9005/message/board/test

在这里插入图片描述
返回结果:
在这里插入图片描述

相关文章:

  • Alevel经济知识点讲解:effects of deflation
  • 安装Docker后的一些配置
  • 智能驾驶功能软件平台设计规范第三部分:预测功能服务接口
  • 网课搜题公众号在线制作
  • Linux性能优化思路和方法
  • 什么是数据仓库?
  • 01|一条SQL查询语句是如何查询的?
  • NLP基础
  • 公众号查题系统平台
  • 129、LeetCode-392.判断子序列
  • Python面向对象编程
  • java计算机毕业设计霍山石斛网站源码+数据库+系统+lw文档+mybatis+运行部署
  • Python文件处理与垃圾回收机制
  • java计算机毕业设计基于MVC框架的在线书店设计源码+数据库+系统+lw文档+mybatis+运行部署
  • 计算机毕业设计springboot+vue基本微信小程序的外卖点餐订餐平台
  • (三)从jvm层面了解线程的启动和停止
  • 【159天】尚学堂高琪Java300集视频精华笔记(128)
  • Docker下部署自己的LNMP工作环境
  • Git的一些常用操作
  • httpie使用详解
  • MYSQL如何对数据进行自动化升级--以如果某数据表存在并且某字段不存在时则执行更新操作为例...
  • Mysql数据库的条件查询语句
  • Python爬虫--- 1.3 BS4库的解析器
  • Python语法速览与机器学习开发环境搭建
  • spring security oauth2 password授权模式
  • swift基础之_对象 实例方法 对象方法。
  • vue-cli在webpack的配置文件探究
  • vue从创建到完整的饿了么(18)购物车详细信息的展示与删除
  • Vue实战(四)登录/注册页的实现
  • Web标准制定过程
  • 发布国内首个无服务器容器服务,运维效率从未如此高效
  • 分布式熔断降级平台aegis
  • 猫头鹰的深夜翻译:Java 2D Graphics, 简单的仿射变换
  • 你真的知道 == 和 equals 的区别吗?
  • 全栈开发——Linux
  • 如何进阶一名有竞争力的程序员?
  • 树莓派 - 使用须知
  • 思否第一天
  • 通过几道题目学习二叉搜索树
  • 物联网链路协议
  • 最简单的无缝轮播
  • Oracle Portal 11g Diagnostics using Remote Diagnostic Agent (RDA) [ID 1059805.
  • 阿里云ACE认证之理解CDN技术
  • ​软考-高级-系统架构设计师教程(清华第2版)【第9章 软件可靠性基础知识(P320~344)-思维导图】​
  • ​软考-高级-信息系统项目管理师教程 第四版【第19章-配置与变更管理-思维导图】​
  • # C++之functional库用法整理
  • (9)目标检测_SSD的原理
  • (HAL库版)freeRTOS移植STMF103
  • (JSP)EL——优化登录界面,获取对象,获取数据
  • (Mirage系列之二)VMware Horizon Mirage的经典用户用例及真实案例分析
  • (牛客腾讯思维编程题)编码编码分组打印下标题目分析
  • (收藏)Git和Repo扫盲——如何取得Android源代码
  • (转)全文检索技术学习(三)——Lucene支持中文分词
  • (转)真正的中国天气api接口xml,json(求加精) ...
  • (转)重识new