引言 Hello大家好,这里是Anyin。 在我之前的文章中,不知道大家有没有发现我的代码都是放在AnyinCloud〔1〕这个项目的(欢迎大家点个星星)。这个项目我积累了一些我自己平时在工作当中的小工具和最佳实践,随着时间的推进这个项目已经慢慢成为一个我个人搭建项目的脚手架,能够快速搭建起来一个完善的基于SpringCloud技术栈的微服务基础架构。 之前在掘金上看到一个权限认证的框架SaToken,简单的了解下,发现确实容易上手,而且功能丰富。今天就让我们来把它集成到AnyinCloud项目吧。需求梳理 在把SaToken集成到我们的项目之前,我们需要先梳理下需求,不能为了集成而集成。 1。AnyinCloud项目需要一个认证鉴权的框架,经过选型,确定使用SaToken。 2。AnyinCloud项目是一个微服务项目,所以我们统一的认证需要放在认证服务Auth,而统一的鉴权是放在网关Gateway。 3。Auth和Gateway都是高频访问的服务,需要足够轻量,所以我们设计这两个服务都不依赖数据库,并且不会过多依赖其他服务,Auth服务和Gateway服务的数据通信通过Redis。 4。Gateway服务需要把当前登录用户的标识传递到下游。SaToken集成集成认证Auth服务 首先,我们先来处理Auth服务。 添加pom。xml依赖,因为我们需要通过Redis来进行数据通讯,所以需要依赖对应的Redis组件。dependencygroupIdcn。dev33groupIdsatokenspringbootstarterartifactIddependencydependencygroupIdcn。dev33groupIdsatokendaoredisjacksonartifactIddependency 在Auth服务编写Login方法。按我们之前的需求,Auth需要足够轻量,所以它不会去依赖数据库,在登录的时候需要用户信息,通过用户服务upms远程调用从而获取用户信息。ComponentSlf4jpublicclassUsernameLoginHandlerimplementsLoginHandler{AutowiredprivateSysUserFeignApisysUserFeignAOverridepublicLoginTypeEnumextension(){returnLoginTypeEnum。USERNAME;}OverridepublicLoginUserDTOlogin(String。。。content){TODOparamcheckStringusernamecontent〔0〕;Stringpasswordcontent〔1〕;SysUserRespsysUsersysUserFeignApi。infoByUsername(username)。getData();if(sysUsernull){throwAuthExCodeEnum。USERNOTREGISTER。getException();}TODOaddsaltif(!sysUser。getPassword()。equals(SecureUtil。md5(password))){throwAuthExCodeEnum。USERPASSWORDERROR。getException();}if(UserStatusEnum。DISABLE。getCode()。equals(sysUser。getStatus())){throwAuthExCodeEnum。USERISDISABLE。getException();}sysUser。setPassword(null);StpUtil。login(sysUser。getId());SaTokenInfotokenStpUtil。getTokenInfo();LoginUserDTOusernewLoginUserDTO();user。setSysUser(sysUser);user。setToken(token);}} 根据SaToken建议的使用方式,在我们对用户密码进行校验正确之后,通过StpUtil。login来进行框架内部的登录操作,这个操作其实是在Redis上记录对应的信息。在Redis上会记录2个信息: 用户ID,satoken:login:session:开头,它的值还会包含一些其他的信息 登录的Token,satoken:login:token:开头,它的值就是用户ID 在登录之后,我们还需要获取token返回给前端,所以这里使用StpUtil。getTokenInfo()获取token信息,最后组装用户信息和token信息返回给前端。 代码编写好,我们需要对登录接口做下测试。 好了,登录地认证我们已经处理好了,简不简单?香不香?集成鉴权Gateway服务 我们接着处理鉴权Gateway服务。老规矩,先添加依赖。 这里要特别注意了,因为我们的网关是SpringCloudGateway,底层是WebFlux实现,它是基于Reactor模型编程的;而之前的Auth服务是正常的SpringMVC服务,基于Servlet模型编程的。所以我们这里引入的是satokenreactorspringbootstarter cn。dev33 satokenreactorspringbootstarterartifactId cn。dev33 satokendaoredisjacksonartifactId 接着,我们继续编写Gateway服务的过滤器,在过滤器中我们主要做以下3个事情: 1。判断哪些路由需要进行鉴权,哪些不需要。 2。如果需要鉴权,则判断是否登录,未登录则直接返回异常信息。 3。如果登录,则透传用户ID到下游服务 对于判断哪些路由需要鉴权,我们可以在动态路由中配置路由的元数据,从而判断当前路由是否鉴权。代码如下:Routeroute(Route)exchange。getAttributes()。get(ServerWebExchangeUtils。GATEWAYROUTEATTR);IntegerneedAuth(Integer)route。getMetadata()。get(GatewayConstants。SYSROUTEAUTHKEY);无需认证的路由if(!NEEDAUTH。equals(needAuth)){returnchain。filter(exchange);} 当路由需要进行鉴权的时候,我们再使用SaToken提供的isLogin方法进行判断,如果未登录则响应异常信息。如下:判断是否登录if(!StpUtil。isLogin()){returnthis。response(exchange,CommonExCodeEnum。USERNOTLOGIN。getException());} 如果当前请求用户已经登录,则使用StpUtil。getLoginId方法获取当前用户ID,然后透传到下游服务,如下:privateServerWebExchangesetHeaderLoginId(ServerWebExchangeexchange,StringloginId){ServerHttpRequestrequestexchange。getRequest()。mutate()。header(CommonConstants。USERID,loginId)。build();returnexchange。mutate()。request(request)。build();} 最后,很关键的一步,我们还需要注册全局的过滤器,除了我们自己编写的过滤器,还有SaToken的过滤器。如果细心的同学可以发现StpUtil工具类提供的方法都是很简单,得益于它需要注册一个全局的过滤器SaReactorFilter,通过该过滤器它把大量的上下文信息和对应的逻辑都在内部处理掉。所以,我们需要注册2个过滤器,如下:BeanpublicGatewayAuthFiltergatewayAuthFilter(){returnnewGatewayAuthFilter();}BeanpublicSaReactorFiltersaReactorFilter(){returnnewSaReactorFilter();} 其实SaReactorFilter过滤器提供了很多的方法和参数,用来处理各种业务场景,但是因为我这边可能需要对过滤器进行更加定制化的逻辑处理,所以未使用它内部的一些方法。测试 完成了以上2部分代码,我们需要对其进行测试下,看看框架是否好使。 首先,测试下不传递token的场景,是否会报未登录的异常。 传递token的场景下,能够正常返回信息 最后 感谢SaToken作者提供了这么一个牛皮的框架。其文档地址SaToken〔2〕。 以上,就是SpringCloudGateway集成SaToken的步骤,如果有什么问题,欢迎指正。 如果您觉得文章对您有帮助,帮忙点个关注哈。 相关源码:AnyinCloud〔3〕References 〔1〕AnyinCloud:https:gitee。comanyinanyincloud 〔2〕SaToken:https:satoken。dev33。cn 〔3〕AnyinCloud:https:gitee。comanyinanyincloud