项目准备
<h1>S+星级客户平台</h1>
<h2>用户端:</h2>
<h2>后管端:</h2>
<h3>优化1:Shiro+Session==>Spring Security+JWT Token</h3>
<p>优化前:</p>
<ul>
<li>管理器:
<ul>
<li>自定义Realm继承AuthorizingRealm,实现doGetAuthenticationInfo(根据token获取数据库中匹配到的认证信息)和doGetAuthorizationInfo(获取当前登录用户的授权信息:角色+资源权限)</li>
<li>会话管理器:验证session有效性,用于删除过期session</li>
<li>缓存管理器:登录记录缓存+系统活跃用户缓存+系统用户授权缓存+系统缓存+系统参数缓存+系统会话缓存</li>
<li>Cookie记住我管理器:设置固定秘钥可保证记住我功能有效</li>
</ul></li>
<li>过滤器:
<ul>
<li>退出登录过滤器(extends org.apache.shiro.web.filter.authc.LogoutFilter):指定登录页面url</li>
<li>在线用户处理过滤器(extends AccessControlFilter):根据session状态进行访问控制</li>
<li>在线用户同步过滤器(extends PathMatchingFilter):同步会话信息到数据库</li>
<li>验证码过滤器(extends AccessControlFilter):登录页面,注册页面:匿名+验证码过滤器</li>
<li>登录账号控制过滤器(KickoutSessionFilter extends AccessControlFilter):可以限制同一个用户登录设备数量,指定踢出前者或后者</li>
</ul></li>
<li>缺点:
<ul>
<li>需要在服务器保存session信息(上次登录时间,过期时间,用户ID,用户名等用户信息)</li>
<li>session依赖于Cookie,无法防止CSRF(跨站请求伪造)攻击</li>
</ul></li>
</ul>
<p>优化后:</p>
<ul>
<li>Spring Security:
<ul>
<li>自定义LoginUser继承UserDetails,重写getPassword(),getUsername()等方法,并补充登录用户的其他属性,如登录信息、权限信息等</li>
<li>实现UserDetailsService重写loadUserByUsername(String username)(获取数据库中username对应的用户信息,并封装成UserDetails类)</li>
</ul></li>
<li>JWT(JSON Web Token):
<ul>
<li>用户表信息+权限信息+登录信息保存在redis中,返回给用户jwt令牌即为缓存的key</li>
</ul></li>
<li>优势:
<ul>
<li>不需要在服务器保存session信息,减轻了服务端的压力</li>
<li>不依赖于Cookie,可防止CSRF(跨站请求伪造)攻击</li>
</ul></li>
<li>伴随的问题:
<ul>
<li>JWT无状态,客户端一经分配,即使用户状态改变(如:退出登录、修改密码、角色权限变化、账户封禁)也会一直有效。解决:每次请求验证token距离过期时间不足20分钟,就刷新token</li>
</ul></li>
</ul>
<h3>优化2:动态多鉴权</h3>
<p>权限管理:针对不同的岗位,不同的级别看到的数据是不一样的,操作数据的权限也是不一样的。</p>
<ul>
<li>
<p>RBAC(Role-Based Access Control)
<img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=c2a9be4282fa7d97e7065649b7233308&file=file.png" alt="" /></p>
</li>
<li>
<p>组织+RBAC
<img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=233c1e90907f7ae382be0645cfe5e1eb&file=file.png" alt="" />
组织:部门,支行,网点;用户可以在多个组织中,因为组织也有层级结构,一个组织里只可以有多个用户。</p>
</li>
<li>
<p>资源(菜单)-角色权限:访问某个菜单及其下面的功能需要用户拥有某些角色
优化前:访问某个菜单及其下面的功能所需要的权限标识是硬编码在代码里面,例@PreAuthorize("@ss.hasPermi('system:dept:list')"),增加一个功能还需要在接口上添加对应的注解代码。</p>
<p><img src="https://www.showdoc.com.cn/server/api/attachment/visitFile?sign=67cb90efa164bfc7f1504f688f50a482&file=file.png" alt="" /></p>
<p>优化:通过接口的url路径直接匹配到资源的角色权限</p>
<ul>
<li>menu_role关联menu表和role表,配置哪些角色可以访问哪些菜单</li>
<li>menu表中pattern字段配置该菜单的url访问规则(一般配置Controller层的url映射地址,例:/system/dept/**)</li>
<li>LoginUser类实现UserDetails中getAuthorities()方法,返回该用户所拥有的角色名权限集合</li>
<li>自定义CustomSecurityMetadataSource实现FilterInvocationSecurityMetadataSource的getAttributes(Object object),入参object中可以提取出当前请求的URL地址,返回值是访问受保护对象所需要的权限。拿该url地址去menu表中匹配pattern字段,若匹配上则返回该菜单对应的角色名权限集合</li>
<li>通过UrlAuthorizationConfigurer 来进行配置。在配置的过程中,通过 withObjectPostProcessor 方法调用 ObjectPostProcessor 对象后置处理器,在对象后置处理器中,将 FilterSecurityInterceptor 中的 SecurityMetadataSource 对象替换为我们自定义的 customSecurityMetadataSource 对象即可。</li>
</ul>
<p>对比:如果要将某个资源禁止所有角色访问,硬编码接口权限标识的方案需要去设置所有角色的资源,url路径匹配方案只需要设置该资源的pattern字段,配合AbstractSecurityInterceptor 对象中的 rejectPublicInvocations 属性。</p>
</li>
<li>数据-组织(部门)权限:根据当前登录用户的部门或职位,对查询的数据进行过滤。
优化前:后置的权限处理注解<code>PostAuthorize</code>和<code>@PostFilter</code>有一个共同劣势,就是得先去数据库中查询到数据,然后再根据当前用户权限进行过滤,如果数据量比较小,这样做倒也没有啥大问题,但是如果数据量比较大,这样做很明显效率会特别低。</li>
</ul>
<p>优化:<code>@DataScope</code>注解+AOP</p>
<ul>
<li>AOP的doBefore 方法是一个前置通知。由于 <code>@DataScope</code> 注解是加在 service 层的方法上,所以这里使用前置通知,为方法的执行补充 SQL 参数,具体思路是这样:加了数据权限注解的 service 层方法的参数必须是对象,并且这个对象必须继承自 BaseEntity,BaseEntity 中则有一个 Map 类型的 params 属性,我们如果需要为 service 层方法的执行补充一句 SQL,那么就把补充的内容放到这个 params 变量中,补充内容的 key 就是前面声明的 <code>dataScope</code>,value 则是一句 SQL。在 doBefore 方法中先执行 clearDataScope 去清除 params 变量中已有的内容,防止 SQL 注入(因为这个 params 的内容也可以从前端传来);然后执行 handleDataScope 方法进行数据权限的过滤。</li>
<li>
<p>handleDataScope 方法中,主要是查询到当前的用户,由于一个用户可能有多个角色,所以在 dataScopeFilter 方法中要先遍历角色,不同的角色有不同的数据权限,这些不同的数据权限之间通过 OR 相连,最终生成的补充 SQL 的格式类似这样 AND(xxx OR xxx OR xxx) 这样,每一个 xxx 代表一个角色生成的过滤条件。</p>
</li>
<li>接下来就是根据各种不同的角色的数据权限生成补充 SQL 了:如果数据权限为 1,则生成的 SQL 为空,即查询 SQL 不添加限制条件;如果数据权限为 2,表示自定义数据权限,此时根据用户的角色查询出用户的部门,生成查询限制的 SQL;如果数据权限为 3,表示用户的数据权限仅限于自己所在的部门,那么将用户所属的部门拎出来作为查询限制;如果数据权限为 4,表示用户的权限是自己的部门和他的子部门,那么就将用户所属的部门以及其子部门拎出来作为限制查询条件;如果数据权限为 5,表示用户的数据权限仅限于自己,即只能查看自己的数据,那么就用用户自身的 id 作为查询的限制条件。最后,再把生成的 SQL 稍微处理下,变成 <code>AND(xxx OR xxx OR xxx)</code> 格式,这个就比较简单了,就是字符串截取+字符串拼接。</li>
</ul>
<h1>权益中台</h1>
<p>负责:单点登录SSO服务,数据权限控制,分布式锁下单,库存
网关服务:拦截所有请求认证,通过后根据配置文件中url前缀匹配转发给同一注册中心下相应的服务
认证中心:调用sso服务,根据tenantCode, account, password调用SSO服务获取用户信息,生成JWT令牌(用户信息+角色信息)
SSO服务:</p>
<ul>
<li>获取用户信息(角色信息)</li>
<li>给用户设置角色</li>
<li>初始化密码</li>
<li>新增或修改用户</li>
</ul>
<h1>活动平台</h1>
<h1>社保卡裂变营销活动</h1>