需求
现在我们是微服务系统,需要设计一个注解 **@RequiredLogin ,**当标识这个注解时表示系统需要登录才能继续操作。
实现思路
首先,需要明确我们要拦截的是从浏览器过来的请求,服务之间的互相调用是不需要拦截的(比如 Feign 调用)。下图是一些交互情况。
我们在网关的全局过滤器中添加一个标识 X (表示他是从浏览器过来的),然后在每次 Feign 远程调用之前标识一下 Y(表示是内部远程调用),最后在每个服务的拦截器(这个可以抽出通用拦截器配置)中拦截请求头中的信息进行判断即可。
代码实现
定义注解
java
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequireLogin {
}
网关的全局过滤器
java
/**
* 定义全局过滤器,功能如下:
* 1.把客户端真实IP通过请求同的方式传递给微服务
* 2.在请求头中添加FEIGN_REQUEST的请求头,值为0,标记请求不是Feign调用,而是客户端调用
* 3.刷新Token的有效时间
*/
@Component
public class CommonFilter implements GlobalFilter {
@Autowired
private StringRedisTemplate redisTemplate;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
/**
* pre拦截逻辑
* 在请求去到微服务之前,做了两个处理
* 1.把客户端真实IP通过请求同的方式传递给微服务
* 2.在请求头中添加FEIGN_REQUEST的请求头,值为0,标记请求不是Feign调用,而是客户端调用
*/
ServerHttpRequest request = exchange.getRequest().mutate().
header(CommonConstants.REAL_IP,exchange.getRequest().getRemoteAddress().getHostString()).
header(CommonConstants.FEIGN_REQUEST_KEY,CommonConstants.FEIGN_REQUEST_FALSE).
build();
return chain.filter(exchange.mutate().request(request).build()).then(Mono.fromRunnable(()->{
/**
* post拦截逻辑
* 在请求执行完微服务之后,需要刷新token在redis的时间
* 判断token不为空 && Redis还存在这个token对于的key,这时候需要延长Redis中对应key的有效时间.
*/
String token,redisKey;
if(!StringUtils.isEmpty(token = exchange.getRequest().getHeaders().getFirst(CommonConstants.TOKEN_NAME))
&& redisTemplate.hasKey(redisKey = CommonRedisKey.USER_TOKEN.getRealKey(token))){
redisTemplate.expire(redisKey, CommonRedisKey.USER_TOKEN.getExpireTime(), CommonRedisKey.USER_TOKEN.getUnit());
}
}));
}
}
Feign 远程调用的前置拦截
java
/**
* Feign调用时添加标记为此时是通过Feign进行调用(不需要登录)
*/
public class FeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
template.header(CommonConstants.FEIGN_REQUEST_KEY,CommonConstants.FEIGN_REQUEST_TRUE);
}
}
每个请求都要走的通用登录拦截器
java
public class RequireLoginInterceptor implements HandlerInterceptor {
private StringRedisTemplate redisTemplate;
public RequireLoginInterceptor(StringRedisTemplate redisTemplate){
this.redisTemplate = redisTemplate;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if(handler instanceof HandlerMethod){
HandlerMethod handlerMethod = (HandlerMethod) handler;
String feignRequest = request.getHeader(CommonConstants.FEIGN_REQUEST_KEY);
//如果是feign请求,直接放行
if(!StringUtils.isEmpty(feignRequest) && CommonConstants.FEIGN_REQUEST_TRUE.equals(feignRequest)){
return true;
}
//如果不是Feign请求,判断是否有贴RequireLogin注解
if(handlerMethod.getMethodAnnotation(RequireLogin.class)!=null){
response.setContentType("application/json;charset=utf-8");
String token = request.getHeader(CommonConstants.TOKEN_NAME);
if(StringUtils.isEmpty(token)){
response.getWriter().write(JSON.toJSONString(Result.error(CommonCodeMsg.TOKEN_INVALID)));
return false;
}
String phone = JSON.parseObject(redisTemplate.opsForValue().get(CommonRedisKey.USER_TOKEN.getRealKey(token)),String.class);
if(phone==null){
response.getWriter().write(JSON.toJSONString(Result.error(CommonCodeMsg.TOKEN_INVALID)));
return false;
}
}
}
return true;
}
}