江西网站建设网络公司,建立网站对吗,网站tkd优化,排行前十装修公司在微服务架构中#xff0c;用户登录和身份校验的处理方式确实与单体应用有所不同。在单体架构中#xff0c;一旦用户通过身份验证#xff0c;其会话信息可以在整个应用范围内共享#xff0c;所有模块都能访问到用户信息。然而#xff0c;在微服务架构下#xff0c;每个服… 在微服务架构中用户登录和身份校验的处理方式确实与单体应用有所不同。在单体架构中一旦用户通过身份验证其会话信息可以在整个应用范围内共享所有模块都能访问到用户信息。然而在微服务架构下每个服务独立部署且通常运行在不同的进程中因此需要一种机制来确保用户的身份信息能够在各个微服务之间安全、高效地传递和验证。 目录 网关实现路由
网关登录鉴权
鉴权思路
登录校验流程图
网关过滤器详解
实现网关过滤器
拦截器流程图
服务信息鉴权 网关实现路由
问题每个微服务都有不同的地址或端口入口不同请求不同数据时要访问不同的入口需要维护多个入口地址前端无法调用nacos无法实时更新服务列表。
解决方案采用微服务网关数据在网络间传输从一个网络传输到另一网络时就需要经过网关来做数据的路由和转发。
在微服务中新建一个网关模块作为网关微服务
引入依赖需要加入nacos依赖让nacos管理 dependencies!--common--dependencygroupIdcom.heima/groupIdartifactIdhm-common/artifactIdversion1.0.0/version/dependency!--网关--dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-gateway/artifactId/dependency!--nacos discovery--dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId/dependency!--负载均衡--dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-loadbalancer/artifactId/dependency/dependencies
在application.yaml文件配置路由 gateway:routes:- id: item # 路由规则id自定义唯一uri: lb://item-service # 路由的目标服务lb代表负载均衡会从注册中心拉取服务列表predicates: # 路由断言判断当前请求是否符合当前规则符合则路由到目标服务- Path/items/**,/search/** # 这里是以请求路径作为判断规则
经测试成功
网关登录鉴权
问题单体架构时我们只需要完成一次用户登录、身份校验就可以在所有业务中获取到用户信息。但是在微服务中每个微服务都独立部署一般只有用户微服务能校验登录信息事实上这是不安全的。
解决方案既然网关是所有微服务的入口一切请求都需要先经过网关。我们完全可以把登录校验的工作放到网关去做。
鉴权思路
登录是基于JWT来实现的校验JWT的算法复杂而且需要用到秘钥。我们不可能让个微服务都需要知道JWT的秘钥不安全。也不可能每个微服务重复编写登录校验代码、权限校验代码麻烦。
所以我们在网关和用户服务保存秘钥开发登录校验功能。
登录校验流程图 我们可以看到前端——网关——后端服务
我们在网关层去实现过滤请求。
网关过滤器详解
Gateway内部工作的基本原理 如图所示 客户端请求进入网关后由HandlerMapping对请求做判断找到与当前请求匹配的路由规则Route然后将请求交给WebHandler去处理。 WebHandler则会加载当前路由下需要执行的过滤器链Filter chain然后按照顺序逐一执行过滤器后面称为Filter。 图中Filter被虚线分为左右两部分是因为Filter内部的逻辑分为pre和post两部分分别会在请求路由到微服务之前和之后被执行。 只有所有Filter的pre逻辑都依次顺序执行通过后请求才会被路由到微服务。 微服务返回结果后再倒序执行Filter的post逻辑。 最终把响应结果返回。
反正就是我们需要在NettyRoutingFilter过滤器之前在发起Request时即pre时定义一个过滤器进行网关登录校验。
实现网关过滤器
我们采用全局过滤器作用范围是所有路由即GlobalFilter。
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.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;RequiredArgsConstructor
Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {private final AuthProperties authProperties;private final JwtTool jwtTool;private final AntPathMatcher antPathMatcher new AntPathMatcher();/*** 过滤器* param exchange* param chain* return*/Overridepublic MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 1.获取请求对象ServerHttpRequest request exchange.getRequest();// 2.判断是否需要拦截if (isExclude(request.getPath().toString())) {// 放行return chain.filter(exchange);}// 3.获取tokenString token null;ListString headers request.getHeaders().get(authorization);if (headers ! null !headers.isEmpty()) {token headers.get(0);}// 4.解析tokenLong userId null;try {userId jwtTool.parseToken(token);} catch (UnauthorizedException e) {// 如果无效拦截ServerHttpResponse response exchange.getResponse();response.setStatusCode(HttpStatus.UNAUTHORIZED);return response.setComplete();}// 5.获取用户信息String userInfo userId.toString();ServerWebExchange swe exchange.mutate().request(builder - builder.header(user-info,userInfo )).build();// 6.放行return chain.filter(swe);}/*** 判断路径是否需要拦截* param antPath* return*/private boolean isExclude(String antPath) {for (String pathPattern : authProperties.getExcludePaths()) {if(antPathMatcher.match(pathPattern, antPath)){return true;}}return false;}// 优先级Overridepublic int getOrder() {return 0;}
}因为我采用了JWT令牌进行校验所以引入了JWT工具类进行令牌的获取与解析。
至于AuthProperties是配置了不需要鉴权就能访问的路径。
经测试网关已经可以完成登录校验并获取登录用户身份信息。 问题当网关将请求转发到微服务时微服务如何获取用户身份我们不可能每个微服务都写一个拦截器去得到用户身份信息。
解决方案将用户信息以请求头的方式传递到下游微服务。然后微服务可以从请求头中获取登录用户信息上述代码第五步已经完成了。考虑到微服务内部可能很多地方都需要用到登录用户信息因此我们可以利用SpringMVC的拦截器来实现登录用户信息获取并存入ThreadLocal。
拦截器流程图 提供一个用于保存登录用户的ThreadLocal工具UserContext
public class UserContext {private static final ThreadLocalLong tl new ThreadLocal();/*** 保存当前登录用户信息到ThreadLocal* param userId 用户id*/public static void setUser(Long userId) {tl.set(userId);}/*** 获取当前登录用户信息* return 用户id*/public static Long getUser() {return tl.get();}/*** 移除当前登录用户信息*/public static void removeUser(){tl.remove();}
}
在公用模块下定义一个拦截器
public class UserInfoInterceptor implements HandlerInterceptor {/*** 请求拦截器* param request* param response* param handler* return* throws Exception*/Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.获取请求头中的 用户信息String userInfo request.getHeader(user-info);// 2.判断是否为空if (StringUtils.isNotBlank(userInfo)) {UserContext.setUser(Long.valueOf(userInfo));}// 3.放行return true;}/*** 响应拦截器* param request* param response* param handler* param ex* throws Exception*/Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserContext.removeUser();}
}编写SpringMVC的配置类配置登录拦截器
Configuration
ConditionalOnClass(DispatcherServlet.class)
public class MvcConfig implements WebMvcConfigurer {public void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new UserInfoInterceptor());}
}
注意这个配置类默认是不会生效的基于SpringBoot的自动装配原理我们要将其添加到resources目录下的META-INF/spring.factories文件中
org.springframework.boot.autoconfigure.EnableAutoConfiguration\com.hmall.common.config.MyBatisConfig,\com.hmall.common.config.MvcConfig
经测试服务得到网关传递的用户身份信息。
服务信息鉴权
问题因为之前编写的过滤器和拦截器功能微服务可以轻松获取登录用户信息。但是有时候请求到达微服务后还需要调用其它多个微服务。我们没有实现服务之间的用户身份信息的传递。
解决方案由于微服务获取用户信息是通过拦截器在请求头中读取因此要想实现微服务之间的用户信息传递就必须在微服务发起调用时把用户信息存入请求头。
因为我们之前微服务之间调用是基于OpenFeign来实现的并不是我们自己发送的请求。所以我们可以采用Feign中提供的一个拦截器接口feign.RequestInterceptor。
在公用模块下定义一个userInfoRequestInterceptor /*** feign请求拦截器 微服务之间的远程调用时将当前登录用户的userId传递给目标服务* return*/Beanpublic RequestInterceptor userInfoRequestInterceptor() {return new RequestInterceptor() {public void apply(RequestTemplate requestTemplate) {Long userId UserContext.getUser();if (userId ! null) {requestTemplate.header(user-info, userId.toString());}}};}
总结
为了实现网关处简便的登录校验我们采用了GlobalFilter为了实现网关传递用户信息到多个微服务我们采用了UserInfoInterceptor 为了实现微服务之间用户身份信息传递我们采用了userInfoRequestInterceptor。