主页 > 软件开发  > 

SpringCloudGateway网关实战(三)

SpringCloudGateway网关实战(三)
SpringCloudGateway网关实战(三)

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

自带全局过滤器

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

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

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

AuthFilter

token验证全局过滤器

引入依赖:

<!-- Jwt --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>

具体代码:

@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; } } 依赖类

白名单:

@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; } }

白名单配置:

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

StringUtils类:

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类:

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类:

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) pact(); 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地址日志:

@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; } }
标签:

SpringCloudGateway网关实战(三)由讯客互联软件开发栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“SpringCloudGateway网关实战(三)