SpringCloudGateway网关实战(三)

SpringCloudGateway网关实战(三)

上一章节我们讲了gateway的内置过滤器Filter,本章节我们来讲讲全局过滤器。

自带全局过滤器

在实现自定义全局过滤器前, spring-cloud-starter-gateway依赖本身就自带一些全局过滤器,我们举些比较常用的例子:

  1. NettyRoutingFilter:该过滤器使用Netty作为底层的HTTP客户端,负责将请求转发到下游服务。
  2. RouteToRequestUrlFilter:该过滤器将根据路由配置中的URI信息,将请求转发到指定的URL。
  3. WebsocketRoutingFilter:该过滤器用于处理WebSocket协议的请求转发。
  4. GatewayMetricsFilter:该过滤器用于收集网关的基本性能指标数据,例如请求的数量、响应时间等。

自定义全局过滤器

自定义全局过滤器需要实现GlobalFilter接口和Ordered接口。

AuthFilter

token验证全局过滤器

引入依赖:

xml 复制代码
        <!-- Jwt -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

具体代码:

java 复制代码
@Component
public class AuthFilter implements GlobalFilter, Ordered {

    private static final Logger log = LoggerFactory.getLogger(AuthFilter.class);

    @Autowired
    private IgnoreWhiteProperties ignoreWhite;
    @Autowired
    public RedisTemplate redisTemplate;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        ServerHttpRequest request = exchange.getRequest();
        ServerHttpRequest.Builder mutate = request.mutate();

        // 跳过白名单
        String url = request.getURI().getPath();
        if (com.smallred.gateway.utils.StringUtils.matches(url, ignoreWhite.getWhites())) {
            return chain.filter(exchange);
        }

        // 检查令牌是否存在
        String token = getToken(request);
        if (StringUtils.isEmpty(token)) {
            return unauthorizedResponse(exchange, "令牌不能为空!");
        }

        //
        Claims claims = JwtUtils.parseToken(token);
        if (claims == null) {
            return unauthorizedResponse(exchange, "令牌已过期或验证不正确!");
        }

        // 判断登录状态
        String userKey = JwtUtils.getUserKey(claims);
        Boolean islogin = redisTemplate.hasKey(getTokenKey(userKey));
        if (!islogin) {
            return unauthorizedResponse(exchange, "登录状态已过期!");
        }

        // 验证用户信息
        String userId = JwtUtils.getUserId(claims);
        String userName = JwtUtils.getUserName(claims);
        if (StringUtils.isEmpty(userId) || StringUtils.isEmpty(userName)) {
            return unauthorizedResponse(exchange, "令牌验证失败");
        }


        // 设置用户信息到请求
        addHeader(mutate, "user_key", userKey);
        addHeader(mutate, "user_id", userId);
        addHeader(mutate, "username", userName);
        // 内部请求来源参数清除
        removeHeader(mutate, "from-source");
        return chain.filter(exchange.mutate().request(mutate.build()).build());
    }

    /**
     *  添加头
     */
    private void addHeader(ServerHttpRequest.Builder mutate, String name, Object value) {
        if (value == null) {
            return;
        }

        String valueStr = value.toString();
        String valueEncode = urlEncode(valueStr);
        mutate.header(name, valueEncode);
    }

    /**
     *  删除头
     */
    private void removeHeader(ServerHttpRequest.Builder mutate, String name) {

        mutate.headers(httpHeaders -> httpHeaders.remove(name)).build();
    }

    /**
     *  获取缓存key
     */
    private String getTokenKey(String token) {
        return "login_tokens:" + token;
    }

    /**
     *  获取请求token
     */
    private String getToken(ServerHttpRequest request) {
        String token = request.getHeaders().getFirst("Authorization");
        // 如果前端设置了令牌前缀,则裁剪掉前缀
        if (StringUtils.isNotEmpty(token) && token.startsWith("Bearer")) {
            token = token.replaceFirst("Bearer", StringUtils.EMPTY);
        }
        return token;
    }

    /**
     * 内容编码
     *
     * @param str 内容
     * @return 编码后的内容
     */
    public static String urlEncode(String str)
    {
        try
        {
            return URLEncoder.encode(str, "UTF-8");
        }
        catch (UnsupportedEncodingException e)
        {
            return StringUtils.EMPTY;
        }
    }

    /**
     *  验证失败返回
     */
    private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String msg) {
        log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath());
        return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, 401);
    }

    @Override
    public int getOrder() {
        return -200;
    }
}

依赖类

白名单:

java 复制代码
@Configuration
@RefreshScope
@ConfigurationProperties(prefix = "security.ignore")
public class IgnoreWhiteProperties {

    private List<String> whites = new ArrayList<>();

    public List<String> getWhites() {
        return whites;
    }

    public void setWhites(List<String> whites) {
        this.whites = whites;
    }
}

白名单配置:

yaml 复制代码
# 安全配置
security:
  ignore:
    whites:
      - /auth/logout
      - /auth/login
      - /auth/register
      - /*/v2/api-docs
      - /csrf

StringUtils类:

java 复制代码
public class StringUtils {

    /** 空字符串 */
    private static final String NULLSTR = "";

    public static boolean matches(String str, List<String> strs)
    {
        if (isEmpty(str) || isEmpty(strs))
        {
            return false;
        }
        for (String pattern : strs)
        {
            if (isMatch(pattern, str))
            {
                return true;
            }
        }
        return false;
    }

    public static boolean isEmpty(String str)
    {
        return isNull(str) || NULLSTR.equals(str.trim());
    }

    public static boolean isEmpty(Collection<?> coll)
    {
        return isNull(coll) || coll.isEmpty();
    }

    public static boolean isNull(Object object)
    {
        return object == null;
    }

    public static boolean isMatch(String pattern, String url)
    {
        AntPathMatcher matcher = new AntPathMatcher();
        return matcher.match(pattern, url);
    }

}

ServletUtils类:

java 复制代码
public class ServletUtils {

    public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, Object value, int code)
    {
        return webFluxResponseWriter(response, HttpStatus.OK, value, code);
    }

    public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, HttpStatus status, Object value, int code)
    {
        return webFluxResponseWriter(response, MediaType.APPLICATION_JSON_VALUE, status, value, code);
    }

    public static Mono<Void> webFluxResponseWriter(ServerHttpResponse response, String contentType, HttpStatus status, Object value, int code)
    {
        response.setStatusCode(status);
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, contentType);
        R<?> result = R.fail(code, value.toString());
        DataBuffer dataBuffer = response.bufferFactory().wrap(JSON.toJSONString(result).getBytes());
        return response.writeWith(Mono.just(dataBuffer));
    }

}

JwtUtils类:

java 复制代码
public class JwtUtils {

    public static String secret = "abcdefghijklmnopqrstuvwxyz";

    /**
     * 从数据声明生成令牌
     *
     * @param claims 数据声明
     * @return 令牌
     */
    public static String createToken(Map<String, Object> claims)
    {
        String token = Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, secret).compact();
        return token;
    }

    /**
     * 从令牌中获取数据声明
     *
     * @param token 令牌
     * @return 数据声明
     */
    public static Claims parseToken(String token)
    {
        return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
    }

    /**
     * 根据令牌获取用户标识
     *
     * @param token 令牌
     * @return 用户ID
     */
    public static String getUserKey(String token)
    {
        Claims claims = parseToken(token);
        return getValue(claims, "user_key");
    }

    /**
     * 根据令牌获取用户标识
     *
     * @param claims 身份信息
     * @return 用户ID
     */
    public static String getUserKey(Claims claims)
    {
        return getValue(claims, "user_key");
    }

    /**
     * 根据令牌获取用户ID
     *
     * @param token 令牌
     * @return 用户ID
     */
    public static String getUserId(String token)
    {
        Claims claims = parseToken(token);
        return getValue(claims, "user_id");
    }

    /**
     * 根据身份信息获取用户ID
     *
     * @param claims 身份信息
     * @return 用户ID
     */
    public static String getUserId(Claims claims)
    {
        return getValue(claims, "user_id");
    }

    /**
     * 根据令牌获取用户名
     *
     * @param token 令牌
     * @return 用户名
     */
    public static String getUserName(String token)
    {
        Claims claims = parseToken(token);
        return getValue(claims, "username");
    }

    /**
     * 根据身份信息获取用户名
     *
     * @param claims 身份信息
     * @return 用户名
     */
    public static String getUserName(Claims claims)
    {
        return getValue(claims, "username");
    }

    /**
     * 根据身份信息获取键值
     *
     * @param claims 身份信息
     * @param key 键
     * @return 值
     */
    public static String getValue(Claims claims, String key)
    {
        return toStr(claims.get(key), "");
    }

    public static String toStr(Object value, String defaultValue)
    {
        if (null == value)
        {
            return defaultValue;
        }
        if (value instanceof String)
        {
            return (String) value;
        }
        return value.toString();
    }

}

IpAddressFilter

根据请求记录ip地址日志:

java 复制代码
@Component
public class IpAddressFilter implements GlobalFilter, Ordered {

    public static final Map<String, AtomicInteger> CACHE = new ConcurrentHashMap<>();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    InetSocketAddress host = exchange.getRequest().getHeaders().getHost();
    if (host == null || host.getHostName() == null) {
      exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
      return exchange.getResponse().setComplete();
     }
    String hostName = host.getHostName();
    AtomicInteger count = CACHE.getOrDefault(hostName, new AtomicInteger(0));
    count.incrementAndGet();
    CACHE.put(hostName, count);
    System.out.println("IP地址:" + hostName + ",访问次数:" + count.intValue());
    return chain.filter(exchange);

    }

    @Override
    public int getOrder() {
        return 101;
    }
}
相关推荐
中草药z几秒前
【Spring】深入解析 Spring 原理:Bean 的多方面剖析(源码阅读)
java·数据库·spring boot·spring·bean·源码阅读
信徒_8 分钟前
常用设计模式
java·单例模式·设计模式
神仙别闹14 分钟前
基于C#实现的(WinForm)模拟操作系统文件管理系统
java·git·ffmpeg
小爬虫程序猿14 分钟前
利用Java爬虫速卖通按关键字搜索AliExpress商品
java·开发语言·爬虫
m0_7482567819 分钟前
SpringBoot 依赖之Spring Web
前端·spring boot·spring
组合缺一20 分钟前
Solon v3.0.5 发布!(Spring 可以退休了吗?)
java·后端·spring·solon
程序猿零零漆22 分钟前
SpringCloud 系列教程:微服务的未来(二)Mybatis-Plus的条件构造器、自定义SQL、Service接口基本用法
java·spring cloud·mybatis-plus
猿来入此小猿24 分钟前
基于SpringBoot在线音乐系统平台功能实现十二
java·spring boot·后端·毕业设计·音乐系统·音乐平台·毕业源码
愤怒的代码37 分钟前
Spring Boot对访问密钥加解密——HMAC-SHA256
java·spring boot·后端
带多刺的玫瑰38 分钟前
Leecode刷题C语言之切蛋糕的最小总开销①
java·数据结构·算法