1.微服务获取用户信息
现在,网关已经可以完成登录校验并获取登录用户身份信息。但是当网关将请求转发到微服务时,微服务又该如何获取用户身份呢?
由于网关发送请求到微服务依然采用的是Http
请求,因此我们可以将用户信息以请求头的方式传递到下游微服务。然后微服务可以从请求头中获取登录用户信息。考虑到微服务内部可能很多地方都需要用到登录用户信息,因此我们可以利用SpringMVC的拦截器来实现登录用户信息获取,并存入ThreadLocal,方便后续使用。
据图流程图如下:
因此,接下来我们要做的事情有:
-
改造网关过滤器,在获取用户信息后保存到请求头,转发到下游微服务
-
编写微服务拦截器,拦截请求获取用户信息,保存到ThreadLocal后放行
1.1.保存用户到请求头
首先,我们修改登录校验拦截器的处理逻辑,保存用户信息到请求头中:
package com.hmall.gateway.filters;import com.hmall.common.exception.UnauthorizedException;
import com.hmall.gateway.config.AuthProperties;
import com.hmall.gateway.utils.JwtTool;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;import java.util.List;@Component
@RequiredArgsConstructor
@EnableConfigurationProperties(AuthProperties.class)
public class AuthGlobalFilter implements GlobalFilter, Ordered {private final AuthProperties authProperties;private final JwtTool jwtTool;private final AntPathMatcher antPathMatcher = new AntPathMatcher();@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {//1.获取到requestServerHttpRequest request = exchange.getRequest();//2.判断是否需要拦截String path = request.getPath().toString();if(isExcluded(path)){//如果不需要拦截,放行return chain.filter(exchange);}//3.从请求头获取到tokenList<String> headers = request.getHeaders().get("authorization");String token = null;if(headers != null && !headers.isEmpty()){token = headers.get(0);}//4.解析token,获取到用户idLong userId = null;try {userId = jwtTool.parseToken(token);} catch (UnauthorizedException e) {//token有问题,拦截ServerHttpResponse response = exchange.getResponse();response.setStatusCode(HttpStatus.UNAUTHORIZED);//设置401未登录状态码return response.setComplete();//直接结束}//5.TODO 传递用户信息到微服务String userInfo = userId.toString();ServerWebExchange swe = exchange.mutate().request(builder -> builder.header("user-info",userInfo)).build();//6.放行return chain.filter(swe);}private boolean isExcluded(String path) {//匹配,如果成功匹配则truefor (String pathPattern : authProperties.getExcludePaths()) {if (antPathMatcher.match(pathPattern, path)){return true;}}return false;}@Overridepublic int getOrder() {return 0;}
}
1.2.拦截器获取用户信息
在hm-common中已经有一个用于保存登录用户的ThreadLocal工具:
接下来,我们只需要编写拦截器,获取用户信息并保存到UserContext
,然后放行即可。
由于每个微服务都有获取登录用户的需求,因此拦截器我们直接写在hm-common
中,并写好自动装配。这样微服务只需要引入hm-common
就可以直接具备拦截器功能,无需重复编写。
我们在hm-common
模块下定义一个拦截器:
package com.hmall.common.interceptor;import cn.hutool.core.util.StrUtil;
import com.hmall.common.utils.UserContext;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class UserInfoInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//此拦截器不作拦截,而是微服务想获取到网关或者上游微服务传递的请求头(用户信息)//1.获取到请求头(用户信息)String header = request.getHeader("user-info");//2.判断是否为空,有则获取,存入ThreadLocalif(StrUtil.isNotBlank(header)){UserContext.setUser(Long.valueOf(header));}//3.放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserContext.removeUser();}
}
接着在hm-common
模块下编写SpringMVC
的配置类,配置登录拦截器:
package com.hmall.common.config;import com.hmall.common.interceptors.UserInfoInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
@ConditionalOnClass(DispatcherServlet.class)
public class MvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new UserInfoInterceptor());}
}
不过,需要注意的是,这个配置类默认是不会生效的,因为它所在的包是com.hmall.common.config
,与其它微服务的扫描包不一致,无法被扫描到,因此无法生效。
基于SpringBoot的自动装配原理,我们要将其添加到resources
目录下的META-INF/spring.factories
文件中:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.hmall.common.config.MyBatisConfig,\com.hmall.common.config.MvcConfig
1.3.恢复购物车代码
之前我们无法获取登录用户,所以把购物车服务的登录用户写死了,现在需要恢复到原来的样子。
找到cart-service
模块的com.hmall.cart.service.impl.CartServiceImpl
: