一、概述 java应用系统设计过程中,用户认证、用户授权、鉴权是绕不过去的话题。 如果这个权限管理的设计,没有做到与业务系统的隔离,拓展性不够强,很容易就会拖后腿。 这个问题应该做过开发的同学都会有所体会。 现在网络上的各种关于权限管理的框架比较主流的有ApacheShiro,SpringSecurity,SaToken(新兴起的一个优秀框架)。 这里会有同学说,既然已经有这么多的成熟优秀的权限管理框架,为什么还有再给大家介绍这种实现思路。 在本人工作和学习的过程中,经常会使用这些优秀的权限管理框架。 但是,一旦是这些三方框架出现的异常和问题,想要排查,就比较麻烦。要么就是靠着百度大家的经验。要么就是猛扒代码,一点点去排查。 三方框架对于我们使用者来说,就像是一个黑盒。这一点一直让我觉得有点不顺畅。 同学们,谁不想要一个自己知根知底的的权限管理框架呢。 喜欢的朋友可以关注一下 二、框架使用体验 2。1项目初始化配置 Springboot老三样。添加pom依赖修改配置文件编写组件代码 引入pom依赖:!引入鉴权框架客户端dependencygroupIdcom。lhit。securitygroupIdsecurityclientartifactIdversion0。0。1SNAPSHOTversiondependency!引入鉴权框架服务端dependencygroupIdcom。lhit。securitygroupIdsecurityserverartifactIdversion0。0。1SNAPSHOTversiondependency 修改配置文件:lhit:security:serverurlofcheckurl:securitycheckurl不配置默认就是这个路径serverurlofcheckpermscode:securitycheckpermscode不配置默认就是这个路径serverurlofcheckstaticrespath:securitycheckstaticrespath不配置默认就是这个路径serverurloftokentoauthority:securitytokentoauthority不配置默认就是这个路径useCloud:false不配置默认为true回去注册中心查找授权中心服务tokenkey:LHTOKEN请求头中token的keyexpire:9999999 2。2用户登录 自定义一个凭证类GetterSetterNoArgsConstructorAllArgsConstructorpublicclassUsernamePasswordVerificationimplementsSecurityVoucher{用户名NotBlank(message未上传username)ApiModelProperty(valueusername)privateStringusername;NotBlank(message密码不能为空)ApiModelProperty(value密码)privateStringpassword;} 自定义一个凭证类认证器: 这个认证器很简单就是默认admin密码123456然后给与了固定的角色和全部的资源。实际应用中应该从数据库中获取到用户的权限并组织返回的securityAuthority。ComponentpublicclassDefaultUsernamePasswordVoucherVoucherVerificationimplementsSecurityVoucherVerificationUsernamePasswordVerification{OverridepublicSecurityAuthorityverification(UsernamePasswordVerificationusernamePasswordVoucher)throwsException{if(usernamePasswordVoucher。getUsername()。equals(admin)usernamePasswordVoucher。getPassword()。equals(123456)){SecurityAuthoritysecurityAuthoritynewSecurityAuthority();securityAuthority。setSecurityUser(newSecurityUser(1,admin));securityAuthority。setSecurityRoleList(Lists。newArrayList(newSecurityRole(0L,roleNo,管理员)));securityAuthority。setSecurityResList(Lists。newArrayList(SecurityRes。allCodeRes(),SecurityRes。allUrlRes()));returnsecurityAuthority;}throwCommonException。create(ServerResponse。createByError(用户名或密码错误,默认admin123456));}} 开放认证接口:Slf4jApi(tags认证接口)RestControllerRequestMapping(security)publicclassAuthorizeController{AutowiredprivateSecurityServersecurityServer;PostMapping(loginusernamepassword)ApiOperation(系统用户登录)ApiImplicitParams({ApiImplicitParam(paramTypebody,dataTypeUsernamePasswordVerification,dataTypeClassUsernamePasswordVerification。class,nameparam,value参数)})publicServerResponsesysUserLoginByUsernamePassword(ValidatedRequestBodyUsernamePasswordVerificationparam)throwsException{log。info();log。info(进入系统用户登录接口:LoginAuthenticationControllersysUserLoginByUsernamePassword);AuthenticationVoauthorizesecurityServer。authorize(param);returnServerResponse。createBySuccess(登录成功,authorize);}GetMapping(logout)ApiOperation(用户退出)ApiImplicitParams({ApiImplicitParam(paramTypeheader,dataTypestring,nameLHTOKEN,value用户token),})publicServerResponselogout(RequestHeader(valueLHTOKEN,defaultValue)Stringtoken)throwsException{log。info();log。info(进入获取当前用户信息接口:SysUserControllerlogout);securityServer。tokenDestroy(newTokenParam(token));returnServerResponse。createBySuccess(退出成功);}获取当前用户信息GetMapping(currentuserprems)ApiOperation(获取当前用户权限信息)ApiImplicitParams({ApiImplicitParam(paramTypeheader,dataTypestring,nameLHTOKEN,value用户token),})TokenToAuthority这个注解将请求同中的token信息转换为securityAuthority参数,到当前方法中。publicServerResponseSecurityAuthoritygetUesrPremsInfo(ApiIgnoreSecurityAuthoritysecurityAuthority)throwsException{log。info();log。info(进入获取当前用户信息接口:SysUserCurrentUserControllergetUesrInfo);returnServerResponse。createBySuccess(获取成功,securityAuthority);}} 2。3权限验证 路由级别鉴权: 不用做其他额外的配置只需要打上HasUrl就会获取到Controller层的当前url地址,并校验用户是否有访问该url的权限。 并将解析后的用户信息放到方法的SecurityAuthority参数中 在第一步用户登录时,默认给了SecurityRes。allUrlRes(),则配置了的url访问权限。删除用户DeleteMapping(delete{userId})ApiOperation(删除用户)ApiImplicitParams({ApiImplicitParam(paramTypeheader,dataTypestring,nameLHTOKEN,value用户token),ApiImplicitParam(paramTypepath,dataTypeLong,dataTypeClassLong。class,nameuserId,value用户id)})HasUrlpublicServerResponsedelUser(PathVariable(valueuserId)LonguserId,ApiIgnoreSecurityAuthoritysecurityAuthority)throwsException{log。info();log。info(进入删除用户接口:SysUserAdminControllerdelUser);sysUserService。delUser(userId,getCurrentSysUser(securityAuthority));returnServerResponse。createBySuccess(删除成功);} 方法级别鉴权Slf4jService(sysUserService)publicclassSysUserServiceImplimplementsSysUserService{删除用户OverrideTransactional(rollbackForException。class)HasPermsCode(permsCodeuser:delete)该数据会校验springMVC上下文中token是否有访问该资源的权限publicvoiddelUser(LonguserId,SysUsersysUser)throwsException{log。info(开始删除用户);SysUsercheckUsersysUserDao。getById(userId);if(checkUsernull){throwCommonException。create(ServerResponse。createByError(用户信息不存在));}try{删除用户checkUser。setDelFlag(true);checkUser。setUpdateBy(sysUser。getId());checkUser。setUpdateTime(newDate());sysUserDao。updateById(checkUser);log。info(完成删除用户);}catch(Exceptione){throwCommonException。create(e,ServerResponse。createByError(删除用户失败,请联系管理员));}}} 验证用户是否登录Slf4jApi(tags认证接口)RestControllerRequestMapping(security)publicclassAuthorizeController{获取当前用户信息GetMapping(currentuserprems)ApiOperation(获取当前用户权限信息)ApiImplicitParams({ApiImplicitParam(paramTypeheader,dataTypestring,nameLHTOKEN,value用户token),})TokenToAuthority这个注解将请求同中的token信息转换为securityAuthority参数,到当前方法中。如果转换失败抛出401异常publicServerResponseSecurityAuthoritygetUesrPremsInfo(ApiIgnoreSecurityAuthoritysecurityAuthority)throwsException{log。info();log。info(进入获取当前用户信息接口:SysUserCurrentUserControllergetUesrInfo);returnServerResponse。createBySuccess(获取成功,securityAuthority);}} 三、时间地点人物 想要描述一个事情,都是将时间地点人物介绍完,才能吧事情描述清楚。 介绍这个设计思路也需要介绍前提:什么时候用这个框架框架能提供哪些能力框架应该有哪些抽象组件 3。1什么时候用这个框架 显然,如果系统需要提供用户认证、用户授权、用户鉴权的时候,就需要有一个权限管理的模块。 整个流程应该是: 用户认证颁发token(用户授权)用户鉴权token回收 3。2框架要提供哪些能力首先可以对系统用户进行认证。可以将生成用户口令给客户端,并可以管理该口令。用户携带口令访问资源是可以判断用户是否有权限来访问这个资源。 以上能力老生常谈就是最基础的权限管理。 3。3框架应该有哪些抽象组件 这个问题是面向对象开发的java程序员必须要好好思考的问题,就是当你接到一个需求时,如何以面向对象的思维来分析和设计程序来完成需求。 3。3。1用户认证 用户认证,最最常见的场景就是用户名密码登录。 在这个场景中可能存在: 用户名密码、用户名密码验证码、手机号验证码、邮箱验证码。。。。。。这么多的登录方式。 而通常来验证这些登录信息是否合法,一般都是要去数据库中读取用户的注册信息来完成认证。 这个场景下可以抽象出来的类有: 1。凭证类:用户名密码、用户名密码验证码、手机号验证码、邮箱验证码。。。。。。 2。凭证类验证器:用来验证用户上传的凭证是否是合法的。 3。3。2用户授权 当用户完成认证凭证验证后,服务器应该返回一个用户的口令(token),给用户使用。 并且用户的token应该可以关联并携带出用户绑定的所有资源权限,和角色、部门、岗位等等信息。 用户的资源又分为: 静态资源: 菜单、按钮等静态资源 文档、图片等静态资源 动态资源: 对某种资源的CURD权限:如是否可以对sysuser表数据进行CURD。 这个场景下可以抽象出来的类:Token生成器:用来生成用户token。用户权限描述类:存放用户基本信息、用户拥有的资源权限、用户的角色信息、用户的其他信息(例如:岗位、部门等)角色:典型的RBAC设计,角色代表权限的集合。岗位:从另外一种维度给用户打标签,来区分用户的权限。部门:从另外一种维度给用户打标签,来区分用户的权限。 其中的岗位和部门,有些权限管理框架中没有,有的或许有一个,这里不纠结这个问题,无论是部门还是岗位,其实都是提供了一种权限判断的维度,类型给用户打上一种标签。 3。3。3token管理 生成用户token后,所有的token需要管理起来。可以用来统计和维护。 所以需要将上一步获取到的用户权限描述类的信息与token建立一种映射关系。从而可以通过token获取到用户的各种信息。 这个场景可以抽象出来的类: Token管理类:用来管理所有生成的token。并建立用户信息与token的关联关系。 3。3。4用户鉴权 当用户通过用户认证和用户授权后,就获取到了他的token口令。 每次用户来访问服务资源时,都需要携带token,当服务器收到请求后,需要通过token获取到用户的所有的权限信息,来判断用户是否可以访问当前资源。 这个场景似乎没有可以抽离出来的类,而是我们要找到一种用户鉴权的方案。 这里,根据以往的经验,基于Spring的AOP切面编程应该是对使用者最友好的方式 所以这里总结下我们需要鉴权的类型:用户身份验证:token是否对应着一个有效的用户身份。就是用户是不是已经登录。URL基本的鉴权:对于java开发来说就是Controller层开放的接口释放可访问。方法级别鉴权:对于java开发者来说,就对应着某个类的某个方法该用户是否可以访问,这里可以参考shiro的授权码user:delete表示是否可以删除用户。静态资源鉴权:用户是否可以访问系统中的静态资源。比如一张图片的下载地址。 喜欢的朋友可以关注一下 四、小结 上面铺垫了那么些,其实只是想让大家能跟笔者有一个相同的认知。用户认证:就是用户登录。用户授权:就是为用户颁发一个可以表达他拥有的角色和自有的口令。用户鉴权:就是判断用户有没有权限来访问当前的资源。 先梳理下上面总结出来的类。 凭证类、凭证类验证器、token生成器、token管理器。 以及,基于AOP实现的用户鉴权方案。 大致思路: 未完待续 敬请关注下一篇