微服务架构下用户登录问题

微服务架构下用户登录问题

在传统的SpringBoot单体应用中,只有一台服务器,直接使用session就可以保存用户登录状态。但是session无法解决不同服务器之间的用户登录状态共享的问题。所以微服务的用户登录问题需要从以下两个方面思考:1.使用什么技术保存用户登录状态?2.微服务之间怎样共享用户信息?

在分享解决办法之前,先说明一下系统架构,看图:

请求通过Gateway进行路由分发,再由A或B服务进行处理。那Gateway和SpringCloud A 和SpringCloud B 三者的信息怎么共享呢?往下看:

①在网关服务中定义过滤器GoalFilter,这是gateway组件中的接口,校验用户是否登录,并将用户信息保存到请求头中

package com.gateway.filter;

import com.gateway.config.AuthProperties;
import com.gateway.utils.JwtTool;
import lombok.RequiredArgsConstructor;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
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.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.List;

/**
 * @author pj
 * @date 2024/8/6 11:19
 **/
@Component
@RequiredArgsConstructor
public class LoggingFilter implements GlobalFilter, Ordered {

    private final JwtTool jwtTool;

    private final AuthProperties authProperties;

    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 排除的路径放行
        ServerHttpRequest request = exchange.getRequest();
        if (isExcluded(request.getPath().toString())) {
            return chain.filter(exchange);
        }
        // 获取token
        HttpHeaders headers = request.getHeaders();
        List<String> authorization = headers.get("authorization");
        if (CollectionUtils.isEmpty(authorization)) {
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }
        // 解析用户信息
        String token = authorization.get(0);
        Long userId;
        try {
            userId = jwtTool.parseToken(token);
        } catch (Exception e) {
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }
        // TODO 传递用户信息给微服务
        ServerWebExchange swe = exchange.mutate()
                .request(builder -> builder.header(("user-info"), userId.toString()))
                .build();
        return chain.filter(swe);
    }

    private boolean isExcluded(String path) {
        for (String excludePath : authProperties.getExcludePaths()) {
            if (antPathMatcher.match(excludePath, path)) {
                return true;
            }
        }
        return false;
    }

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

②在业务处理服务中定义SpringMVC提供的拦截器 HandlerInterceptor,接收网关或其他微服务传递过来的用户信息。如果有多个微服务,A和B服务想共用一个拦截器,可以新建一个通用模块,在这个模块里面定义拦截器,A和B再同时依赖于这个模块,使用SpringBoot自动装配,就可以实现拦截器共用了。

package com.hmall.common.interceptor;
​
import cn.hutool.core.util.StrUtil;
import com.hmall.common.utils.UserContext;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
​
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
​
/**
 * @author pj
 * @date 2024/8/6 12:40
 **/
public class LoggingInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String userInfo = request.getHeader("user-info");
        if (StrUtil.isNotEmpty(userInfo)) {
            UserContext.setUser(Long.valueOf(userInfo));
        }
        return true;
    }
​
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }
​
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserContext.removeUser();
    }
}

// 在配置类中注册拦截器
package com.hmall.common.config;
​
import com.hmall.common.interceptor.LoggingInterceptor;
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;
​
/**
 * @author pj
 * @date 2024/8/6 12:51
 **/
@Configuration
@ConditionalOnClass(DispatcherServlet.class)
public class MvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 默认拦截所有路径
        registry.addInterceptor(new LoggingInterceptor());
    }
}
​
​

​

③远程调用接口时实现OpenFeign提供的RequestInterceptor接口,在发送调用请求之前拦截下来,将用户信息保存到请求头中,实现微服务之间的信息共享

package com.hmall.api.interceptor;
​
import com.hmall.common.utils.UserContext;
import feign.RequestInterceptor;
import feign.RequestTemplate;
​
/**
 * @author pj
 * @date 2024/8/6 13:19
 **/
public class MyRequestInceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        template.header("user-info", UserContext.getUser().toString() );
    }
}
​
// 在配置类中注册拦截器
    @Bean
    public RequestInterceptor myRequestInterceptor() {
        return template -> {
            if (UserContext.getUser() != null) {
                String userInfo = UserContext.getUser().toString();
                template.header("user-info", userInfo);
            }
        };
    }
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值