微服务:如何解决用户登录验证问题的流程整理以及个人对于其中拦截器问题的思考

0-我们通过客户端=》网关=》微服务的顺序访问服务端

1-网关:这一步主要是获取token进行验证,成功后把用户信息保存到请求头以供微服务调取

因为微服务模块比较多,如果每一个都写拦截器会造成不必要的冗余,所以我们统一把拦截器放在网关模块

 网关的信息传递流程为 客户端=》断言=》过滤器=》微服务=》过滤器=》断言=》客户端

通过上面流程我们就知道可以把登录校验问题写的prefilter,这样一旦验证失效就不用访问微服务,完成鉴权功能

基本流程为:获取req-》获取path检查是否可以直接放行(比如登录,或者一些不需要登录也允许的操作)-》通过头信息获取token-》通过token获得用户信息-》携带用户信息转发给微服务

@Component
@RequiredArgsConstructor
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    private final AuthProperties authProperties;

    private final JwtTool jwtTool;
    private final AntPathMatcher antPathMatcher = new AntPathMatcher();
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 获取req
        ServerHttpRequest request = exchange.getRequest();
        // 获取路径
        RequestPath path = request.getPath();
        // 判断是否放行
        if (exclude(path.toString())) {
            return chain.filter(exchange);
        }
        // 获取token
        String token = null;
        List<String> headers = request.getHeaders().get("authorization");
        if (headers != null && !headers.isEmpty()) {
            token = headers.get(0);

        }
        Long userId = null;
        try {
            userId = jwtTool.parseToken(token);
        } catch (UnauthorizedException e) {
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }
        // 传递用户信息,这样在后续的过滤器中就可以获取到用户信息了
        String userIdString = userId.toString();
        ServerWebExchange serverWebExchange = exchange.mutate()
                .request(builder -> {
                    builder.header("user-info", userIdString);
                }).build();
        return chain.filter(serverWebExchange);

    }

    private boolean exclude(String string) {
        for (String way : authProperties.getExcludePaths()) {
            if (antPathMatcher.match(way, string)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public int getOrder() {
        return 1;
    }
}

 

2.微服务:这一步我们主要通过拦截器获取用户信息,以便使用

因为微服务模块比较多,如果每一个都写拦截器会造成不必要的冗余,所以我们统一把拦截器放在常用类模块,与上述非阻塞式不同,这里拦截器作为一个bean,供其他模块调用,因此我们要记住把bean注册在resources\META-INF\spring.facotories中。每当我们启动Context时,都会先扫描starter中的工厂,再通过这个工厂去管理非本模块中的bean。工厂创建如下例:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.hmall.common.config.JsonConfig

拦截器只需为请求头添加一个用户信息,即无论如何都会放行。在结束后及时销毁以保证信息安全

public class UserInfoInterceptors implements HandlerInterceptor{
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String userInfo = request.getHeader("user-info");
        if(StrUtil.isNotBlank(userInfo))
            UserContext.setUser(Long.valueOf(userInfo));
        return true;
    }
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        UserContext.removeUser();
    }
}

然后,我们在实现WebMvcConfig的接口中添加拦截器(这里为什么添加了条件注解:在网关模块中无法提供user-info请求头,因为网关模块压根没有引入springWEB,而DispatcherServlet.class作为springMVC的重要组件,个人理解DispatcherServlet约等于struct)

@Configuration
@ConditionalOnClass(DispatcherServlet.class)
public class MvcConfig implements WebMvcConfigurer {
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new UserInfoInterceptors());
    }
}

完成上述操作后,我们已经可以在网关---微服务之间搭起用户信息传递的桥梁

3.openFeign:我们还有微服务---微服务之间的通信问题没有解决

微服务=》微服务是基于openFeign发送,他明显不携带我们创建的请求头。基于此,我们可以在openFeign中创建一个拦截器,效仿网关=》微服务,添加一个bean

public class DefaultFeignConfig {
    @Bean
    public RequestInterceptor requestInterceptor(){
        return new RequestInterceptor(){

            @Override
            public void apply(RequestTemplate template) {
                Long id = UserContext.getUser();
                if(id!=null)
                    // 添加公共参数
                    template.header("user-info", id.toString());
            }
        };
    }
}

 

那么这两个拦截器有什么不同?

common中的拦截器我们称为C,而openFeign中的拦截器我们称为o,

首先C是springMVC的拦截器;而O是feign中的拦截器。

c的拦截器是被其他模块调用,需要在其他模块启动的时候自动加载和应用,因此他需要装入spring.factroies中。当我们实现WebMvcConfigurer时,并不适合@bean来注册,springMVC会自动完成,简而言之:c是由springMVC管理生命周期;而o的拦截器是feign中的拦截器,并不同于spring,不需要添加到mvc即可生效

c的拦截器是用于处理请求;而openFeign是一个声明式的web服务客户端,o的拦截器效果仅用于在发送请求之前修改请求模板

总的来说拦截器是由mvc注册的,一般不需要bean管理。openFeign中的拦截器做了简化,可以用bean管理

posted on 2024-04-24 21:37  天启A  阅读(30)  评论(0编辑  收藏  举报

导航