|
|
|
|
@ -36,57 +36,118 @@ import java.util.Objects;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* SpringCloud Gateway Token 拦截器
|
|
|
|
|
* 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
|
|
|
|
|
* 该类的主要作用是作为Spring Cloud Gateway中的一个过滤器工厂,用于拦截经过网关的请求,
|
|
|
|
|
* 对特定路径下的请求进行Token验证以及相关的请求头信息处理等操作,以此来保障系统的安全性以及方便后续服务获取用户相关信息。
|
|
|
|
|
* 公众号:马丁玩编程,回复:加群,添加马哥微信(备注:12306)获取项目资料
|
|
|
|
|
*/
|
|
|
|
|
@Component
|
|
|
|
|
// TokenValidateGatewayFilterFactory类继承自AbstractGatewayFilterFactory<Config>,表明它是一个自定义的Spring Cloud Gateway过滤器工厂,
|
|
|
|
|
// 用于创建具体的GatewayFilter实例,并且可以接收一个Config类型的配置对象来配置过滤器的相关行为,例如配置一些黑名单路径前缀等信息。
|
|
|
|
|
public class TokenValidateGatewayFilterFactory extends AbstractGatewayFilterFactory<Config> {
|
|
|
|
|
|
|
|
|
|
// 构造函数,调用父类的构造函数并传入Config.class,
|
|
|
|
|
// 这是按照AbstractGatewayFilterFactory的规范来初始化,告知父类该过滤器工厂期望接收的配置对象类型为Config类型,
|
|
|
|
|
// 以便后续在创建过滤器实例时能够正确地获取相应配置来进行初始化操作。
|
|
|
|
|
public TokenValidateGatewayFilterFactory() {
|
|
|
|
|
super(Config.class);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 注销用户时需要传递 Token
|
|
|
|
|
* 定义了一个表示注销用户操作的路径常量,
|
|
|
|
|
* 用于后续在处理请求时判断当前请求是否是注销用户的请求,
|
|
|
|
|
* 如果是注销用户的请求,可能需要进行一些特殊的Token相关处理,例如额外将Token信息添加到请求头中传递给后续服务,方便注销逻辑使用。
|
|
|
|
|
*/
|
|
|
|
|
public static final String DELETION_PATH = "/api/user-service/deletion";
|
|
|
|
|
|
|
|
|
|
// 实现AbstractGatewayFilterFactory抽象类中的apply方法,该方法用于创建实际的GatewayFilter实例,
|
|
|
|
|
// 定义了过滤器的核心逻辑,也就是对请求进行拦截、验证以及处理的具体操作,根据不同情况决定是继续传递请求还是返回错误响应等。
|
|
|
|
|
@Override
|
|
|
|
|
public GatewayFilter apply(Config config) {
|
|
|
|
|
// 返回一个GatewayFilter实例,该实例代表了一个具体的过滤器逻辑,采用lambda表达式的形式来定义过滤器的具体行为,
|
|
|
|
|
// lambda表达式接收exchange和chain两个参数,exchange包含了请求和响应相关的上下文信息,chain则用于将请求沿着过滤器链继续传递下去。
|
|
|
|
|
return (exchange, chain) -> {
|
|
|
|
|
// 从exchange对象中获取当前请求对象,后续会基于这个请求对象进行各种判断和处理,
|
|
|
|
|
// 例如获取请求路径、请求头中的Token等信息,以此来决定对该请求采取何种处理方式。
|
|
|
|
|
ServerHttpRequest request = exchange.getRequest();
|
|
|
|
|
// 获取当前请求的路径,以字符串形式表示,方便后续判断该请求路径是否在黑名单前缀列表中,
|
|
|
|
|
// 进而决定是否需要进行Token验证等操作,不同的请求路径可能有不同的处理逻辑要求。
|
|
|
|
|
String requestPath = request.getPath().toString();
|
|
|
|
|
// 调用isPathInBlackPreList方法判断当前请求路径是否在黑名单前缀列表中,
|
|
|
|
|
// 如果在黑名单前缀列表中,则需要进行Token验证等相关处理,
|
|
|
|
|
// 如果不在,则直接放行请求,让请求继续沿着过滤器链往下执行,不做额外的验证和处理操作。
|
|
|
|
|
if (isPathInBlackPreList(requestPath, config.getBlackPathPre())) {
|
|
|
|
|
// 从请求头中获取名为"Authorization"的Token字符串,
|
|
|
|
|
// 在基于Token认证的系统中,客户端通常会将Token放在这个请求头字段中传递给服务端进行验证,以此来证明用户的身份合法性。
|
|
|
|
|
String token = request.getHeaders().getFirst("Authorization");
|
|
|
|
|
// TODO 需要验证 Token 是否有效,有可能用户注销了账户,但是 Token 有效期还未过
|
|
|
|
|
// TODO 需要验证Token是否有效,有可能用户注销了账户,但是Token有效期还未过
|
|
|
|
|
// 此处调用JWTUtil工具类的parseJwtToken方法尝试解析Token,
|
|
|
|
|
// 将解析得到的用户信息封装到UserInfoDTO对象中,
|
|
|
|
|
// 这里假设JWTUtil.parseJwtToken方法能够从有效的Token中提取出用户相关的信息,如用户ID、用户名等。
|
|
|
|
|
UserInfoDTO userInfo = JWTUtil.parseJwtToken(token);
|
|
|
|
|
// 调用validateToken方法验证解析得到的用户信息是否有效,
|
|
|
|
|
// 目前这里简单地判断用户信息对象是否为null来确定Token是否有效,
|
|
|
|
|
// 实际应用中可能还需要更复杂的验证逻辑,比如验证Token是否过期、是否被篡改等情况,以确保系统的安全性和数据的准确性。
|
|
|
|
|
if (!validateToken(userInfo)) {
|
|
|
|
|
// 如果Token验证不通过,从exchange对象中获取当前请求对应的响应对象,
|
|
|
|
|
// 用于设置响应的状态码等信息,告知客户端请求未被授权,也就是缺少合法有效的认证信息(此处即Token无效)。
|
|
|
|
|
ServerHttpResponse response = exchange.getResponse();
|
|
|
|
|
// 设置响应的状态码为HttpStatus.UNAUTHORIZED(401未授权状态码),
|
|
|
|
|
// 按照HTTP协议规范,该状态码表示客户端的请求由于缺少有效的认证信息而不能被处理,服务端拒绝提供服务。
|
|
|
|
|
response.setStatusCode(HttpStatus.UNAUTHORIZED);
|
|
|
|
|
// 返回设置好状态码的响应对象,表示该请求处理结束,直接返回给客户端相应的错误响应,
|
|
|
|
|
// 客户端接收到这个响应后可以根据状态码等信息进行相应的提示或者处理,比如提示用户重新登录等操作。
|
|
|
|
|
return response.setComplete();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果Token验证通过,创建一个ServerHttpRequest的构建器对象,
|
|
|
|
|
// 用于对原始请求对象进行修改,主要是添加一些与用户相关的请求头信息,方便后续的服务能够获取到用户的相关数据,
|
|
|
|
|
// 进行更具体的业务逻辑处理,例如权限判断、用户个性化展示等操作都可能依赖这些用户信息。
|
|
|
|
|
ServerHttpRequest.Builder builder = exchange.getRequest().mutate().headers(httpHeaders -> {
|
|
|
|
|
// 将用户的ID信息添加到请求头中,键为UserConstant.USER_ID_KEY,
|
|
|
|
|
// 后续的微服务等可以从请求头中获取到当前登录用户的ID,以此来进行相关业务逻辑处理,比如查询用户特定的数据、进行权限验证等。
|
|
|
|
|
httpHeaders.set(UserConstant.USER_ID_KEY, userInfo.getUserId());
|
|
|
|
|
// 将用户的用户名信息添加到请求头中,键为UserConstant.USER_NAME_KEY,
|
|
|
|
|
// 方便后续服务获取用户名信息,用于显示或者其他与用户名相关的业务逻辑判断等,例如在日志记录中显示操作的用户名等情况。
|
|
|
|
|
httpHeaders.set(UserConstant.USER_NAME_KEY, userInfo.getUsername());
|
|
|
|
|
// 对用户的真实姓名进行URL编码,并添加到请求头中,键为UserConstant.REAL_NAME_KEY,
|
|
|
|
|
// 这是因为真实姓名可能包含一些特殊字符,进行编码可以确保在网络传输等过程中不会出现问题,
|
|
|
|
|
// 并且符合HTTP请求头信息的规范要求,防止因特殊字符导致的请求解析错误等情况发生。
|
|
|
|
|
httpHeaders.set(UserConstant.REAL_NAME_KEY, URLEncoder.encode(userInfo.getRealName(), StandardCharsets.UTF_8));
|
|
|
|
|
// 判断当前请求路径是否是注销用户的路径(通过与之前定义的DELETION_PATH常量对比),
|
|
|
|
|
// 如果是注销用户的请求,还需要将Token信息添加到请求头中,键为UserConstant.USER_TOKEN_KEY,
|
|
|
|
|
// 可能后续在注销用户的相关业务逻辑中会用到这个Token进行一些额外的验证或者记录等操作,比如记录注销时的Token状态等。
|
|
|
|
|
if (Objects.equals(requestPath, DELETION_PATH)) {
|
|
|
|
|
httpHeaders.set(UserConstant.USER_TOKEN_KEY, token);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
// 使用构建器创建修改后的请求对象,并通过exchange.mutate().request方法替换原始请求对象,
|
|
|
|
|
// 然后继续沿着过滤器链往下执行请求,让请求带着添加好的用户相关请求头信息继续后续的处理流程,
|
|
|
|
|
// 使得后续的服务能够获取到完整且准确的用户信息来进行相应的业务操作。
|
|
|
|
|
return chain.filter(exchange.mutate().request(builder.build()).build());
|
|
|
|
|
}
|
|
|
|
|
// 如果当前请求路径不在黑名单前缀列表中,直接放行请求,让请求继续沿着过滤器链往下执行,
|
|
|
|
|
// 不做额外的Token验证等处理,保证请求能够顺利到达对应的后端服务进行处理。
|
|
|
|
|
return chain.filter(exchange);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 私有方法,用于判断给定的请求路径是否在黑名单前缀列表中,
|
|
|
|
|
// 通过遍历黑名单前缀列表,使用Java 8的Stream API的anyMatch方法来判断请求路径是否以列表中的某个前缀开头,以此来确定是否需要进行Token验证等操作。
|
|
|
|
|
private boolean isPathInBlackPreList(String requestPath, List<String> blackPathPre) {
|
|
|
|
|
// 首先判断黑名单前缀列表是否为空,如果为空则直接返回false,
|
|
|
|
|
// 表示当前请求路径不在任何黑名单前缀对应的路径范围内,不需要进行Token验证等处理,直接放行该请求即可。
|
|
|
|
|
if (CollectionUtils.isEmpty(blackPathPre)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// 使用Stream API的anyMatch方法遍历黑名单前缀列表,
|
|
|
|
|
// 判断请求路径是否以列表中的某个前缀开头,如果有匹配的前缀,则返回true,
|
|
|
|
|
// 表示当前请求路径在黑名单前缀对应的需要验证Token的路径范围内,需要进一步进行Token相关的验证和处理操作。
|
|
|
|
|
return blackPathPre.stream().anyMatch(requestPath::startsWith);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 私有方法,用于简单验证Token是否有效,目前的实现只是判断解析得到的用户信息对象是否为null,
|
|
|
|
|
// 实际应用中可以根据具体的业务需求扩展更复杂的验证逻辑,比如验证Token的有效期、签名等信息,以更严谨地判断Token的合法性和有效性。
|
|
|
|
|
private boolean validateToken(UserInfoDTO userInfo) {
|
|
|
|
|
return userInfo != null;
|
|
|
|
|
return userInfo!= null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|