登录认证模块和权限控制模块打算选择shiro来做。
系统目前没有操作日志,有时候定位问题的时候难以定位,不知道是谁误操作了。
系统敏感信息【例如密码】安全加密传输,杜绝用户越权访问的操作【包括操作权限和数据权限】,能够跟踪用户的操作轨迹,记录菜单功能使用情况,后端方法耗时。
登录认证:包括web端的登录和小程序端的登录,大体思路都是检查用户名密码决定是否登录成功
session处理:web端交由shiro控制,小程序端无session处理,自己处理用户过期问题【小程序没有Cookie,对session不好处理】
权限控制:包括前端权限和后端权限,前端权限在登陆成功之后缓存起来,后端权限用redis做缓存。
操作日志:用户页面上点击菜单或者按钮,记录用户的基本信息以及IP等,保存在数据库
微信端登陆认证:
转存失败重新上传取消转存失败重新上传取消
转存失败重新上传取消
①在web端的index页面,从后台获取RSA加密需要的publicKey【加密密码用】,输入用户名密码点击登陆,将用户名和密码【密文传输】传到后端。
注意:RSA加密的时候是在加载index页面的时候获取的publicKey,所有的用户对应的密钥对都是不一样的
如果输错密码三次的话,带出验证码校验,如果输错密码5次的话,账号锁定半小时
②后端拿到用户名和密文密码,通过RSA的privateKey解密密码,再MD5加密和我们数据库中的对比【因为我们数据库中的密码是MD5加密后的字符串】,对比成功则允许登陆。
③登陆成功后将session存入redis,web端的session过期时间设置为24小时,后面的业务请求将会读取redis里的session,判断是否过期。
④用户注销后,删除redis里面的session
我们系统的session是通过shiro管理的,存储在redis中,shiro为我们提供了redis管理session的接口,所以我们只需要重写这个接口就行了,这个接口里面主要包括一些创建session,读取session,更新session,删除session的操作。
shiro默认的登录认证是没办法读取我们数据库来验证的,但是他提供了HashedCredentialsMatcher接口,这个接口主要做验证用户密码的操作,所以我们需要重写这个接口里面的doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info)方法,
所需的参数实在执行subject.executeLogin()方法的时候放进去的,相当于request里面的用户名和密码,所以我们只要在doCredentialsMatch方法里面写我们的用户验证逻辑就可以了。
①在刚打开微信小程序的时候,会调用一个getToken()的方法,请求参数里面带上微信登陆的code
②后端根据前端传过来的code通过微信提供的API获取当前用户的微信id,通过这个微信id去匹配我们用户表的weChatOpenId字段,如果匹配上了,则证明这个用户之前就有登陆过了,再验证这个用户session有没有过期【90天过期】,过期了也当作没有登陆过来处理。如果当前微信用户没有登陆过,则跳转到登陆页面,如果登陆过了,我们会返回一个token到前端页面,token为通过JWT生成的一个加密字符串,里面包含微信id。
③登陆操作:同web端,只是session的存储不一样,微信端的session自己通过redis处理。并且微信端登陆成功之后,也会返回一个token到前端
④登陆成功后跳转到小程序的首页,以后的所有业务请求都需要传登陆返回的token,后端通过这个token做用户验证。
转存失败重新上传取消转存失败重新上传取消转存失败重新上传取消
user:基本的用户表
password:因为不是用域账号登陆的,所以密码加密后保存在本地数据库。
weChatOpenId:将要开通微信小程序,所以用户表里面存一个微信用户的唯一标识,将来做登陆认证用
customerId:用户都是挂在客户下面的
role:基本的角色表
customerId:角色都是挂在客户下面的
privilege:基本的权限表
type:表示是菜单还是按钮
link:如果是菜单的话,菜单链接,如果是按钮则为空
parentId:通过父节点Id组成一个权限树
display:同一个父亲节点下的排列顺序
weChatFlag:确定是微信的权限还是web端的权限
user_role:用户角色的中间关联表
role_privilege:角色权限的中间关联表
采用标准的权限控制设计模型【用户关联角色,角色决定权限】,前后端都做权限控制,后端通过shiro提供的controller层注解控制,前端自己写一个权限控制组件
①后端部分:
将用户拥有的角色,权限存入shiro,通过shiro来管理权限。
重写org.apache.shiro.realm.AuthorizingRealm的doGetAuthorizationInfo()方法, 在本地数据库中通过三张表的关联关系找出当前登陆的用户拥有的所有角色以及所有权限,存入AuthorizationInfo这个对象【之后验证权限的时候判断这个对象中有没有对应权限】
用户在访问controller的时候,需要判断是否有对应权限【shiro会帮我们验证】,如果有,则允许访问,如果没有,默认是返回一个没有权限的html,但是用户体验不好,所以打算让前端弹框提示即可,不需要页面跳转了,在这里就需要重写shiro提供的
PermissionsAuthorizationFilter类里面的 onAccessDenied() 方法,这个方法是在拒绝访问的时候调用的,这个方法shiro默认是重定向到错误页面的,我们重写的时候将错误信息通过response回显到页面即可。
开启权限控制的注解【在spring中配置】
②前端部分:
在登陆成功之后,我们就调用后端方法获取当前用户的所有角色和权限,缓存起来,再写一个hasPermession()方法,判断是否有相应权限,没有的话就不会展示这个页面或者按钮。
前端权限树的获取:
前端采用wmstool提供的树形控件,后端转成需要的json格式传过来即可。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
以上权限控制,是菜单按钮级别的权限,接下来的是数据权限
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
数据权限主要有客户权限和项目权限。
客户权限:因为所有用户都是挂在customer下面的,同一个custmer下的客户在某些场景下的数据权限是一样的【例如:查询商品信息,门店信息,常用地址,库存等等】
项目权限:用户还会关联OMS和T8的项目,在某些场景下是需要更细粒度的控制在项目上的【例如,下单的时候,查询订单的时候】
数据权限决定通过sql控制,在baseMapper中加一个权限控制的代码块,类似于经管系统转存失败重新上传取消转存失败重新上传取消转存失败重新上传取消
主要字段:
段名称 |
字段类型 |
备注 |
---|---|---|
createTime |
DateTime |
操作时间 |
operatorId |
Long |
操作人ID |
menuName |
String |
操作菜单名称 |
buttonName |
String |
操作按钮名称 |
url |
String |
操作的URL |
IP |
String |
用户的IP |
costTime | Long | 方法执行耗时【后端处理时间】 |
通过spring切面来做操作日志,首先需要写一个拦截器,拦截所有的后端controller层的请求,将这个拦截器配置进spring
具体的拦截器设计:
在拦截器的preHandle() 方法中,我们可以通过request拿到用户的IP地址,URL请求地址
通过我们系统的 SecurityContextHolder.getContext().getUser() 可以拿到用户的信息
在权限设计模块中,我们是通过shiro提供的@RequirePermission注解来控制权限的,@RequirePermission(value)中的value是具体的权限,我们可以根据这个value关联到权限表中的按钮名称以及菜单名称