当前位置: 首页 > 后端技术 > Java

重新认识Java微服务架构-认证服务

时间:2023-04-02 00:40:45 Java

前言之前看spring-cloud+spring-security+oauth2认证服务和资源服务写过文章《Spring微服务实战》,写过spring-gateway做token验证但是在实战过程中发现了一些问题,无法通过与小伙伴的交流获得新的知识。之前的框架设计存在问题。我想通过这篇文章重新组织验证和认证过程。遇到的问题1.Feign调用问题:之前所有的微服务都做成了资源服务,所以feign调用的时候必须校验token,影响执行效率2.Gateway网关问题:spring-gateway校验token,把token是通过授权作为请求头发送给下游微服务,下游服务再次校验token,影响执行效率。3、全局信息问题:比如获取用户信息,微服务API接口通过OAuth2Authentication获取用户名,再通过UserService如何解决获取用户信息又降低执行效率的问题?基于以上三个问题,提出了相应的解决方案:1、微服务不需要做成资源服务(不需要验证授权),微服务的权限和统一处理都在网关完成。这样,feign调用的时候就不需要验证token了。2.上面说了,微服务已经不是资源服务了,不需要再去查token了。尽管如此,还是可以使用spring-gateway做统一授权,控制外部访问。3.spring-gateway校验token并将用户信息封装到请求头中,下游服务将请求头中的用户信息保存到Context中。注意:这里有个问题:A服务有个Controller方法叫saveUserEvent,feign通过/gateway-name/a/saveUserEvent路由调用(feign调用api接口没有token校验问题),但是没有token资源服务的限制。当然外部也可以通过网关调用这个接口,所以这里遇到的问题是:如何在不让外部请求调用的情况下保证feign的顺利调用?这个问题的答案是:利用网关的路由黑名单,将不想暴露在路由外部的api接口排除在外,这样就可以保证外部不再请求该接口,服务中feign调用的数据也能保证安全。运维针对以上三个问题,我们来重新架构一下我们的微服务。1、spring-gateway认证服务运行过程cookie和spring-gateway结合进行用户认证服务。这是通过cookie和set-cookie来实现token转账的效果,后面会单独写一篇文章来说明。2.将用户信息封装成Context注意:封装好的Context可以单独放在context包中,每个微服务都要添加。如何包装?其实代码我已经写好了,大家可以参考一下。UserContext.java@ComponentpublicclassUserContext{publicstaticfinalStringCORRELATION_ID="correlation-id";publicstaticfinalStringAUTH_TOKEN="authorization";publicstaticfinalStringUSER="user";privatestaticfinalThreadLocalcorrelationId=newThreadLocal();privatestaticfinalThreadLocalauthToken=newThreadLocal();privatestaticfinalThreadLocaluser=newThreadLocal<>();publicstaticStringgetCorrelationId(){返回correlationId.get();}publicstaticvoidsetCorrelationId(Stringcid){correlationId.set(cid);}publicstaticStringgetAuthToken(){returnauthToken.get();}publicstaticvoidsetAuthToken(Stringtoken){authToken.set(token);}publicstaticLoginUsergetUser(){returnuser.get();}公共静态voidsetUser(LoginUseru){user.set(u);}publicstaticHttpHeadersgetHttpHeaders(){HttpHeadershttpHeaders=newHttpHeaders();httpHeaders.set(CORRELATION_ID,getCorrelationId());返回httpHeaders;}}UserContextFilter.java@ComponentpublicclassUserContextFilterimplementsFilter{privatestaticfinalLoggerlogger=LoggerFactory.getLogger(UserContextFilter.class);@OverridepublicvoiddoFilter(ServletRequestservletRequest,ServletResponseservletResponse,FilterChainfilterChain)抛出IOException,ServletException{HttpServletRequesthttpServletRequest=(HttpServletRequest)servletRequest;ObjectMapper映射器=newObjectMapper();StringuserJson=httpServletRequest.getHeader(UserContext.USER);if(StringUtils.hasLength(userJson)){LoginUseruserMap=mapper.readValue(userJson,LoginUser.class);UserContextHolder.getContext().setUser(用户映射);}UserContextHolder.getContext().setCorrelationId(httpServletRequest.getHeader(UserContext.CORRELATION_ID));UserContextHolder.getContext().setAuthToken(httpServletRequest.getHeader(UserContext.AUTH_TOKEN));logger.debug("---IncomingCorrelationid:{}---",UserContextHolder.getContext().getCorrelationId());//logger.debug("---IncomingAuthorizationtoken:{}---",UserContextHolder.getContext().getAuthToken());filterChain.doFilter(httpServletRequest,servletResponse);}@Overridepublicvoidinit(FilterConfigfilterConfig)throwsServletException{}@Overridepublicvoiddestroy(){}}UserContextHolder.javapublicclassUserContextHolder{privatestaticfinalThreadLocaluserContext=newThreadLocal();publicstaticfinalUserContextgetContext(){UserContextcontext=userContext.get();如果(context==null){context=createEmptyContext();userContext.set(上下文);}返回userContext.get();}publicstaticfinalvoidsetContext(UserContextcontext){Assert.notNull(context,"Onlynon-nullUserContextinstancesarepermitted");userContext.set(上下文);}publicstaticfinalUserContextcreateEmptyContext(){returnnewUserContext();}}UserContextInterceptor.javapublicclassUserContextInterceptorimplementsClientHttpRequestInterceptor{privatestaticfinalLoggerlogger=LoggerFactory.getLogger(UserContextInterceptor.class);@OverridepublicClientHttpResponseintercept(HttpRequestrequest,byte[]body,ClientHttpRequestExecutionexecution)throwsIOException{HttpHeadersheaders=request.getHeaders();headers.add(UserContext.CORRELATION_ID,UserContextHolder.getContext().getCorrelationId());headers.add(UserContext.AUTH_TOKEN,UserContextHolder.getContext().getAuthToken());LoginUseruser=UserContextHolder.getContext().getUser();ObjectMapper映射器=newObjectMapper();StringuserInfo=mapper.writeValueAsString(user);headers.add(UserContext.USER,userInfo);返回执行。执行(请求,正文);}}这么多代码,我们看看接下来怎么用?XxxController.javapublicResponseEntityaddLikeUrl(){LoginUserloginUser=UserContext.getUser();if(loginUser==null){returnResponseEntity.ok(newResultInfo<>(ResultStatus.USER_NOT_FOUND));}}UserContext在这里。getUser方法可以获取全局登录用户。3、如果你是参考我上面两篇文章搭建鉴权和资源服务的,那你现在可以去掉之前的代码,否则跳过这个过程。3.1.删除@EnableResourceServer@SpringBootApplication//资源保护服务@EnableResourceServer//服务发现@EnableDiscoveryClient//启用feign@EnableFeignClients@RefreshScopepublicclassAccountServiceApplication{@BeanpublicBCryptPasswordEncoderpasswordEncoder(){public}coder((String[]args){SpringApplication.run(AccountServiceApplication.class,args);}}3.2.删除spring-cloud-security和spring-cloud-starter-oauth2org.springframework。云spring-cloud-security2.2.5.RELEASEorg.springframework.cloudspring-cloud-starter-oauth22.2.5.RELEASE3.删除安全包总结1.《Spring微服务实战》是一本好书,但是和hibernate一样,在国外很流行,到了中国,我们就会有自己的理解。2.《Spring微服务实战》发布第二版,有兴趣的可以看看。