微人事系统笔记

动态处理角色和资源的关系

vhr-web:

CustomFilterInvocationSecurityMetadataSource类(FilterInvocationSecurityMetadataSource接口):通过当前的请求地址,获取该地址需要的用户角色,其中getAttributes(Object o)方法获取该地址需要的用户角色(当前登录用户满足其中任意角色即可获取访问权限),该方法返回的集合最终会来到AccessDecisionManager类中,接下来我们再来看AccessDecisionManager类。

CustomUrlDecisionManager(实现AccessDecisionManager接口):该方法接收Authentication对象参数以及以及getAttributes传来的“当前请求需要的角色”参数。

  1. 如果当前需要的角色为“ROLE_LOGIN”,表示只要登录就能访问,因此只需判断是否登录(通过Authentication是否是AnonymousAuthenticationToken子类即可,因为AnonymousAuthenticationToken表示尚未登录,就会抛出AccessDeniedException运行时异常,前端获取该异常中包含的信息“未登录”,并进行展示)。
  2. 遍历getAttributes传来的集合(集合内包含表示角色需求的ConfigAttribute对象,包含 角色名 String对象),通过authentication.getAuthorities()(该方法获取的内容由 Hr 类中进行实现的getAuthorities方法进行注入)可以获取当前用户具有的权限集合,遍历权限集合判断是否有某权限和所需角色名字相同。

Hr类(实现UserDetails接口),实现了getAuthorities方法(会在CustomUrlDecisionManager中被调用),将 Hr 对象(当前登录对象)中的角色列表中的所有角色名整合成一个GrantedAuthority类型的集合,进行返回。

HrService类(实现UserDetailsService接口),主要实现该接口的loadUserByUsername方法(创建表示登录用户信息的对象:Hr 对象,并通过查询数据库 为该对象的角色属性赋值),在改方法中会查询数据库hr 表中的username 为当前登录 username 的行(获取该对象),(1)如果获取对象为空,表示“"用户名不存在!"”,会抛出UsernameNotFoundException异常,(2)如果能找到该对象,则再从hr_role 表中利用该用户 id(hr 对象)获取该用户具有的角色信息。

SecurityConfig类(继承WebSecurityConfigurerAdapter)中完成简单的配置

  1. 重写configure方法,将实现的 HrService 类与AuthenticationManagerBuilder对象绑定,表示在执行登录时,首先通过HrService类去查找用户并创建用户信息(Hr 类,后续在权限验证中会使用到 Hr 类)。
  2. configure方法中,通过http.authorizeRequests().withObjectPostProcessor将之前提到的FilterInvocationSecurityMetadataSource和AccessDecisionManager实现类注入进来,保证所有请求都会经过这两个过滤器进行处理(获取当前地址所需权限+验证用户是否具有权限)。
  3. loginFilter()方法中,loginFilter.setAuthenticationSuccessHandler配置登录成功后返回的信息(“登录成功+用户信息: hr 对象”);loginFilter.setAuthenticationFailureHandler根据登录失败抛出的异常返回相应的错误信息。

密码加密和加盐

为了避免数据库被攻击,需要对数据库中存储的密码进行加密(存储密码的md5摘要),但却存在着攻击者利用 md5摘要反向查询彩虹表获取未加密密码的问题,因此为了增大攻击者反向查询(建立彩虹表)的难度(即使相同明文,生成的加密字符串也是不相同的),应该在数据库中保存“原始密码+盐(一段不保存在数据库中的字符串)”的 md5摘要。

使用SpringSecurity提供的BCryptPasswordEncoder类对用户设定的密码进行加盐加密,且无需在系统内配置盐字符串内容。只需要创建BCryptPasswordEncoder对象,调用其encode 方法(以原密码为参数),即可得到加密后的字符串,将其保存在数据库中即可。

在进行 hr 注册以及 hr 修改密码时,需要使用BCryptPasswordEncoder提供的 encode方法对原密码进行加密,将该方法返回的加盐加密后字符串存储到数据库中。

在 SercurityConfig配置类中,在passwordEncoder方法(返回PasswordEncoder类型对象,BCryptPasswordEncoder implements PasswordEncoder)创建BCryptPasswordEncoder对象,并将该对象进行注入(用于登录时密码验证)。

 

服务端异常统一处理

在前后端不分离的项目中,如果出了异常直接跳转到错误页面即可。而本项目作为前后端分离的项目,除了异常之后,对异常的处理逻辑应该是向前端返回 JSON。对服务器端发生的异常可以进行统一处理。

只需要编写GlobalExceptionHandler类,在其中创建带有 ExceptionHandler 注释的方法用于处理全局异常,利用 instanceof判断当前接收到的异常是否为某类异常,对该类异常填写处理逻辑即可(生成带有异常描述信息的RespBean对象,并返回)

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(SQLException.class)
    public RespBean sqlException(SQLException e) {
        if (e instanceof SQLIntegrityConstraintViolationException) {
            return RespBean.error("该数据有关联数据,操作失败!");
        }
        return RespBean.error("数据库异常,操作失败!");
    }
}

@RestControllerAdvice 注解是一个组合注解,由ControllerAdvice(继承了@Component)和ResponseBody组成,注解了RestControllerAdvice的类中的方法可以被 ExceptionHandler 注解注释,该 ExceptionHandler 注解用于指定异常处理方法,与 RestControllerAdive配合使用时,用于全局处理异常。

文章https://www.cnblogs.com/UncleWang001/p/10949318.html(非常好,不懂RestControllerAdvice就看这里)对 ControllerAdvice、ExceptionHandler、RestControllerAdive 解释的很好呀。本身ControllerAdvice+ ResponseBody+ExceptionHandler就可以进行全局异常统一处理,当全部的全局异常统一处理都返回 json,就可以使用RestControllerAdvice代替ControllerAdvice,这样在方法上也不用添加ResponseBody啦。

 

登录成功后前端动态加载组件

之前我们曾提到过后端动态处理角色和资源的关系(判断登录用户是否具有获取某种资源的权限),进行这种处理可以在用户在地址栏直接某资源路径时,不具有该资源权限的用户无法对该资源进行访问。但实际上,前端也应该对用户不具有的资源进行过滤,即在导航栏中只展示该用户能够访问的资源列表。

我们很容易能够想到可以在用户登录成功之后(进入 Home 页面之前),先向后端发起 get 请求,获取该用户有权限访问的资源列表(获取当前的菜单信息和组件信息),然后在 对后端返回的 JSON 数据进行解析(对JSON 数据中包含的每一条资源对象,加载相应的组件)。

但这里存在一个问题,如果用户是第一次登录,那么简单的get 菜单资源并加载当然是没有问题的,那么如果用户进入子页面后又返回 HOME 呢,如果在向后端 get 一次就太浪费了

  1. 因此很自然的会想到将菜单资源保存在 vue 提供的 store(无法被外界读取) (不能将敏感数据保存在 localStore)中。
  2. 但这里又存在一个问题,如果用户在处于 Home 页面或者任一子页面时,按 F5对页面进行了刷新,那么  就会造成store 中的数据清空了,因此只get一次菜单请求还是不够的,也许我们会想到在每个vue 页面的mounted方法中都发起 get菜单资源请求,但这样又太浪费了。因此,我们需要在进入 HOME 页面时,预先判断 store 中是否有数据,如果没有的话就需要向后端发起 get 菜单资源的请求了。

具体实现其实就是,如果要去的页面不是登录页面,就先从store 中读取当前登录状态(是否登录了),如果没登录就去登录页面(并将本身要去的页面的 path 作为参数传递给登录页面,保证登录成功之后能够跳转到原本访问页面),如果已经登录了,就判断 store 中的routes 数组(自定义的保存菜单路由资源的数组)是否为空,如果为空才需要使用getRequest("/config/sysmenu")获取菜单资源 JSON,并调用formatRoutes将 JSON中的 component 转换为相应组件,调用formatRoutes得到的fmtRoutes就可add 进router中啦(router.addRoutes(fmtRoutes))。

并将fmtRoutes添加进store中,这样只要用户不进行 F5刷新,都不需要再“get 菜单 JSON+JSON 转换为路由对象”了。

邮件收发功能

邮件收发功能引入 RabbitMQ,

聊天功能(模仿微信页面)

HTTP/1.1具有协议升级特性,协议升级特性指的是客户端可以在请求的请求头中包含 Connection 首部字段, 并将该字段值设置为 Upgrade,并在请求头中包含值为websocket 等(协议名)的 Upgrade 首部字段,表示想在后续使用 websocket 协议与服务器建立通信。如果服务器同意协议升级,就会返回101响应吗表示“服务器同意进行协议转换”,这之后两者之间就不再使用 HTTP 协议啦。

如果要实现聊天功能,要在客户端和服务器之间使用 websocket 全双工协议(文本消息和其他二进制消息都可以同时在两个方向上发送,而且客户端和服务器都可以主动向对方发送消息);

websocket 协议连接在80或者443端口,和 HTTP使用相同端口,也就是说,几乎所有的防火墙都不会阻塞 websocket连接。

远程系统性能和状态的实时监控也会用到 socket。

图片

 

Git版本回退

每一次的提交(commit)都会有一个版本号,我们可以为每次提交同时打上一个 tag,从 github 上克隆下来的项目,可以在本地使用 git tag 命令查看所有的 tag,通过 git show tag1,可以查看 tag1对应的 commit 对应的版本号 x1,然后如果想要回到 x1版本的话,就执行 git reset --hard x1 就行啦。

 

项目远程部署(Docker、Netty)

https://mp.weixin.qq.com/s?__biz=MzI1NDY0MTkzNQ==&mid=2247486342&idx=1&sn=b8adcef8237f3d222816c985ff58b654&scene=21#wechat_redirect

1、后端

  1. 远程服务器配置 Docker(修改docker 配置 文件:/lib/systemd/system/docker.service,开启docker允许远程访问)
  2. IDEA 下载 Dokcer 插件,在 IDEA 中打包后端项目,并使用远程服务器 Docker部署
  3.  

2、使用

 

 

 

 

 

 

 

 

 

 

 

 


 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值