SpringCloudGateway网关实战(三)
上一章节我们讲了gateway的内置过滤器Filter,本章节我们来讲讲全局过滤器。
自带全局过滤器
在实现自定义全局过滤器前, spring-cloud-starter-gateway
依赖本身就自带一些全局过滤器,我们举些比较常用的例子:
- NettyRoutingFilter:该过滤器使用Netty作为底层的HTTP客户端,负责将请求转发到下游服务。
- RouteToRequestUrlFilter:该过滤器将根据路由配置中的URI信息,将请求转发到指定的URL。
- WebsocketRoutingFilter:该过滤器用于处理WebSocket协议的请求转发。
- 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;
}
}