Gateway集成方法以及拦截器和过滤器的使用

前提:请先创建好一个SpringBoot项目

1. 引入依赖

SpringCloud 和 alibabaCloud 、 SpringBoot间对版本有强制要求,我使用的springboot是3.0.2的版本。版本对应关系请看:版本说明 · alibaba/spring-cloud-alibaba Wiki · GitHub

XML 复制代码
    <dependencyManagement>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>2022.0.3</version>
        </dependency>
    </dependencyManagement>
    <dependencies>
        <!-- SpringCloud组件之一,不加会提示错误 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-loadbalancer</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
            <version>3.1.4</version>
        </dependency>
        <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>
    </dependencies>

2. 搭建网关

配置bootstrap.yml文件:

XML 复制代码
server:
  port: 8083
spring:
  application:
    name: big-news-admin-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 你的ip:8848
      config:
        server-addr: 你的ip:8848
        file-extension: yml

Nacos配置中心:

yml示例:

XML 复制代码
spring:
  data:
    redis:
      host: localhost
      port: 6379
  cloud:
    gateway:
      globalcors:
        add-to-simple-url-handler-mapping: true
        corsConfigurations:
          '[/**]':
            allowedHeaders: "*"
            allowedOrigins: "*"
            allowedMethods:
              - GET
              - POST
              - DELETE
              - PUT
              - OPTION
      routes:
        # 平台管理
        - id: user
          uri: lb://big-news-user
          predicates:
            - Path=/user/**
        # 分类
        - id: category
          uri: lb://big-news-category
          predicates:
            - Path=/category/**
        # 文章
        - id: article
          uri: lb://big-news-article
          predicates:
            - Path=/article/**
        # 文件
        - id: common
          uri: lb://big-news-common
          predicates:
            - Path=/upload/**
token:
  secretKey: rikka7e7f74ef-62b5-4b29-96ae-c698f7c823c1
  expirationTime: 1080000060

解释:

该配置用于解决跨域问题

路由断言规则,id需唯一,uri中的名称需要对应服务的应用名。path用于匹配路由。

以下图举例:uri意味着将请求匹配到nacos中名叫big-news-user的服务,path意味着根据只要请求携带`user`就匹配服务。

可用 filters 过滤掉请求中的字段,比如下图。意味着最后到服务的实际请求不会携带`user`,你就不需要在controller的接收路径上上写`user`。

3. 全局过滤器实现jwt校验

思路分析:

  1. 用户进入网关开始登陆,网关过滤器进行判断,如果是登录,则路由到后台管理微服务进行登录

  2. 用户登录成功,后台管理微服务签发JWT TOKEN信息返回给用户

  3. 用户再次进入网关开始访问,网关过滤器接收用户携带的TOKEN

  4. 网关过滤器解析TOKEN ,判断是否有权限,如果有,则放行,如果没有则返回未认证错误

3.1 拷贝一份jwt工具类到网关服务

java 复制代码
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Component
public class JwtUtil {

    private static final String USER_CLAIMS_KEY = "user";

    // 过期时间
    @Value("${token.expirationTime}")
    private long EXPIRE_TIME;

    // 密钥
    @Value("${token.secretKey}")
    private String SECRET;

    /**
     * 创建JWT Token
     *
     * @param payload 载荷(Claims)
     * @return JWT Token
     */
    public String createToken(Map<String, Object> payload) {
        // 1. 创建一个密钥
        SecretKey key = Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8));

        // 2. 创建JWT Builder
        // 注意:这里的签名算法不能是 RS256,需要使用 HS256
        io.jsonwebtoken.JwtBuilder builder = Jwts.builder()
                .setClaims(payload)
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE_TIME))
                .signWith(key, SignatureAlgorithm.HS256);

        // 3. 生成JWT Token
        return builder.compact();
    }

    /**
     * 解析JWT Token
     *
     * @param token JWT Token
     * @return 载荷(Claims)
     */
    public Map<String, Object> parseToken(String token) {
        // 1. 获取密钥
        SecretKey key = Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8));

        // 2. 解析JWT Token
        Claims claims = Jwts.parser()
                .setSigningKey(key)
                .build()
                .parseClaimsJws(token)
                .getBody();

        // 3. 将Claims里的内容转换成Map
        Map<String, Object> payload = new HashMap<>(claims);
        payload.remove("exp");
        payload.remove("iat");
        payload.remove("iss");
        payload.remove("aud");
        payload.remove("nbf");
        payload.remove("sub");
        payload.remove("jti");

        return payload;
    }

    /**
     * 获取JWT Token的过期时间
     *
     * @param token JWT Token
     * @return 过期时间
     */
    public Date getExpirationDateFromToken(String token) {
        Claims claims = parseClaims(token);
        if (claims != null) {
            return claims.getExpiration();
        }
        return null;
    }

    /**
     * 验证JWT Token是否有效
     *
     * @param token JWT Token
     * @return 是否有效
     */
    public boolean validateToken(String token) {
        try {
            parseClaims(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 获取payload中的用户信息
     *
     * @param token JWT Token
     * @return 用户信息
     */
    public Map<String, Object> getUserFromToken(String token) {
        Map<String, Object> user = null;
        Claims claims = parseClaims(token);
        if (claims != null) {
            user = (Map<String, Object>) claims.get(USER_CLAIMS_KEY);
        }
        return user;
    }

    /**
     * 解析JWT Token中的Claims
     *
     * @param token JWT Token
     * @return Claims
     */
    public Claims parseClaims(String token) {
        try {
            SecretKey key = Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8));
            return Jwts.parser()
                    .setSigningKey(key)
                    .build()
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            return null;
        }
    }

}

3.2 网关微服务中新建全局过滤器

java 复制代码
import cn.hutool.json.JSONUtil;
import com.alibaba.cloud.commons.lang.StringUtils;
import com.xin.common.properties.TokenProperties;
import com.xin.common.utils.JwtUtil;
import io.jsonwebtoken.Claims;
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.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
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.Mono;

import java.util.Objects;

@Component
public class AuthorizeFilter implements Ordered, GlobalFilter {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    //用于接收token的信息,你可按实际请看书写,也可以直接在这个类里定义静态参数。
    //tokenProperties主要包含:密钥key、过期时间
    @Autowired
    private TokenProperties tokenProperties;

    @Autowired
    private JwtUtil jwtUtil;
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.获取请求
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        //2.判断是否是登录操作
        if (request.getURI().getPath().contains("/login")) {
            //放行
            return chain.filter(exchange);
        }

        String token = request.getHeaders().getFirst("Authorization");
        //3.若token为空,校验失败
        if (StringUtils.isEmpty(token)){
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            //结束请求
            return response.setComplete();
        }
        try {
            //4. 解析token
            Claims claims = jwtUtil.parseClaims(token);
            //获得token解析后中的用户信息
            Object o = claims.get("user");
            String user = JSONUtil.toJsonStr(o);
            String id = user.substring(user.indexOf(":")+1, user.indexOf(","));
            //5.判断token是否在redis中过期,或删除
            String object = stringRedisTemplate.opsForValue().get("token:" + id + ":" + token);
            if (Objects.isNull(object)) {
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                //结束请求
                return response.setComplete();
            }


            // 将用户信息存放进 header中
            ServerHttpRequest serverHttpRequest = request.mutate().headers(httpHeaders -> {
                httpHeaders.add("user", user + "");
            }).build();
            exchange.mutate().request(serverHttpRequest).build();
        }catch (Exception e){
            e.printStackTrace();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }
        //5.放行
        return chain.filter(exchange);
    }

    /**
     * 优先级设置,值越小 优先级越高
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

注意,该过滤器只是实现了对Token的校验,并将解析结果存放进header进一步转发。获取user信息,还需在实际的服务里定义拦截器获取:

java 复制代码
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String userString = request.getHeader("user");
        Optional<String> optional = Optional.ofNullable(userString);
        if(optional.isPresent()) {
            //把用户存入threadLocal中
            TreadLocalUtil.setUser(userString);
            log.info("设置用户信息到threadlocal中...");
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        TreadLocalUtil.clear();
        log.info("清理threadlocal...");
    }
}

在WebMvcConfig中配置该拦截器:

java 复制代码
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**");
    }
}

最后最后!!!各位看官觉得有用就收藏、点赞、评论一下吧。我看到问题后,我会第一时间回复的!

相关推荐
向哆哆10 分钟前
Java与NoSQL数据库的集成与优化
java·开发语言·nosql
茂茂在长安13 分钟前
Linux 命令大全完整版(11)
java·linux·运维·服务器·前端·centos
songbaoxian25 分钟前
ElasticSearch
java·linux·elasticsearch
非 白40 分钟前
【Java】代理模式
java·开发语言·代理模式
Good Note1 小时前
Golang的静态强类型、编译型、并发型
java·数据库·redis·后端·mysql·面试·golang
我就是我3521 小时前
记录一次SpringMVC的406错误
java·后端·springmvc
向哆哆1 小时前
Java应用程序的跨平台性能优化研究
java·开发语言·性能优化
ekkcole2 小时前
windows使用命令解压jar包,替换里面的文件。并重新打包成jar包,解决Failed to get nested archive for entry
java·windows·jar
handsomestWei3 小时前
java实现多图合成mp4和视频附件下载
java·开发语言·音视频·wutool·图片合成视频·视频附件下载
全栈若城3 小时前
03 Python字符串与基础操作详解
java·开发语言·python