Springboot+vue 实战 white-jotter 第二部分

一、用户角色权限管理模块设计

“用户-角色-权限”管理是 “访问控制” 的一种实现方式

RBAC

Shiro框架,安全框架

1、用户密码加密

加盐hash

加盐加密与验证的逻辑:

  • 用户注册时,输入用户名密码(明文),向后台发送请求
  • 后台将密码加上随机生成的盐并 hash,再将 hash 后的值作为密码存入数据库,盐也作为单独的字段存起来
  • 用户登录时,输入用户名密码(明文),向后台发送请求
  • 后台根据用户名查询出盐,和密码组合并 hash,将得到的值与数据库中存储的密码比对,若一致则通过验证

 

改了一下result,整合了返回响应码,建了一个枚举类 ResultCode,建了一个ResultFactory 通过静态方法 来调用返回 new Result(resultCode);

(Result里 这两个值 我不知道干嘛用的,删了逻辑正常  // private String message; //响应提示信息 private Object object; //响应结果对象) 以后需要可以再加上

 

注册中的加密:

 @PostMapping("api/register")
    @ResponseBody
    public Result register(@RequestBody User user){

    String salt = new SecureRandomNumberGenerator().nextBytes().toString();
        // 设置 hash 算法迭代次数
    int times = 2;
        // 得到 hash 后的密码
    String encodedPassword = new SimpleHash("md5", password, salt, times).toString();
  
    userService.add(user);     //user.set方法设置属性;service写入数据库

}

 生成盐,结合输入的密码 hash后加密存起来

2、Shiro 认证登录

Shiro,需要理解三个核心概念:Subject、SecurityManager 和 Realms。

 

Subject:  穿了马甲的用户类,负责存储与修改当前用户的信息和状态;;

SecurityManager:    Subject 背后的女人,安全相关的操作实际上是由她管理的。只用在项目中配置一次,就可以忘掉她了;

Realms:    Shiro 和安全相关数据(比如用户信息)的桥梁,也就是说,Realm 负责从数据源中获取数据并加工后传给 SecurityManager。

 

可以通过配置使用特定的 Realm 替代 DAO,和 JPA 类似,Realm 获取数据的方法被封装了起来,但是数据库中的表名、字段等需要与源码预定义的查询保持一致,

所以在我们的项目中获取数据的功能仍旧可以交给 JPA 完成,Realm 只负责加工并传递这些数据。

 

Shiro 配置

1)首先 添加 Shiro的  maven 依赖;

2)创建 Realm 并重写 获取 认证 与 授权 信息的方法

3)创建配置类,包括创建并配置 SecurityManager 等

 

第二步中

新建 WJRealm类,继承 AuthorizingRealm,重写 doGetAuthorizationInfo、doGetAuthenticationInfo 方法

返回值两个类 AuthorizationInfo 授权、AuthenticationInfo 认证

获取认证信息,即根据 token 中的用户名从数据库中获取密码、盐等并返回

public class WJRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    // 简单重写获取授权信息方法
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo s = new SimpleAuthorizationInfo();
        return s;
    }

    // 获取认证信息,即根据 token 中的用户名从数据库中获取密码、盐等并返回
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String userName = token.getPrincipal().toString();
        User user = userService.getByUserName(userName);
        String passwordInDB = user.getPassword();
        String salt = user.getSalt();
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userName, passwordInDB, ByteSource.Util.bytes(salt), getName());
        return authenticationInfo;
    }
}

token.getPrincipal().toString(); 获取token里的用户名

因为SimpleAuthenticationInfo() 要求 salt为byte[]类型,所以从String转了回去;

 

第三步

新建 ShiroConfiguration配置类

 

Controller中 使用Shiro 验证 密码:

 UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, requestUser.getPassword());


subject.login(usernamePasswordToken);

就这一句 就可以执行验证,

这里面 大概经过七八层调用,Shiro 通过 Realm 里我们重写的 doGetAuthenticationInfo 方法获取到了验证信息,再根据我们在配置类里定义的 CredentialsMatcher(HashedCredentialsMatcher),执行 doCredentialsMatch方法(shiro自己写的)判断是否匹配,

贴出来方便理解:

    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        Object tokenHashedCredentials = this.hashProvidedCredentials(token, info);
        Object accountCredentials = this.getCredentials(info);
        return this.equals(tokenHashedCredentials, accountCredentials);
    }

accountCredentials 获取到了我们存在数据库中的 hash 后的密码, 而 tokenHashedCredentials 则调用 hash 算法根据 salt 和客户端传入的 password 算出了 hash 值,他俩是byte[] 类型

再分析 equals 方法,发现最终调用的是 java.security.MessageDigest 包中的 isEqual() 方法,讲道理这个方法才是逻辑的核心(比较两个 hash 值  是否相等)

 

3、登出功能

使用shiro 实现用户登出。

Subject subject = SecurityUtils.getSubject();
subject.logout();

默认 Subject 接口是由 DelegatingSubject 类实现,调用其 logout 方法,

this.session = null;  this.principals = null;  this.authenticated = false; 

 

引入vuex

1)  cnpm install vuex -S

2) 在src目录下,创建store模块,与router同级,创建 index.js, state.js, 还有其他三个 actions/mutations/getters.js

3) 除index.js 之外,引入 export default{   };在index.js中, 新建vuex的store实例

4)main.js中 导入 store 实例

 

4、完善的访问拦截

1.认证方案(session 与 token)

session和token 可以 在某种程度上 解决 :

以往的方式 :  前 端在每次请求时都加上用户名和密码,交由后端验证。

  • 一,需要频繁查询数据库,导致服务器压力较大
  • 二,安全性,如果信息被截取,攻击者就可以 一直 利用用户名密码登录(注意不是因为明文不安全,是由于无法控制时效性)

- session

管理用户状态, 比如控制会话存在时间,在会话中保存属性

 

通常的作用方式:

  • 后端 (服务器 )接收到第一个请求时,生成 session 对象,并通过 响应头 告诉 前端(客户端/ 浏览器) 在 cookie 中放入 sessionId
  • 前端之后发送请求时,会带上包含 sessionId 的 cookie
  • 服务器通过 sessionId 获取 session ,进而得到当前用户的状态(是否登录)等信息

即,前端 只需要在 登录 的时候发送一次 用户名密码,此后只需要 在发送请求 时带上 sessionId,  后端服务器 就可以验证用户是否登录了

 

- token

cookie是一个 存放 seeionId的东西;session存放在后端服务器内存中;

token 的优势是无需服务器存储!!!令牌

 token 解决方案是 JWT

作用流程:

  • 用户使用用户名密码登录,服务器验证通过后,根据用户名(或用户 id 等),按照预先设置的算法生成 token,其中也可以封装其它信息,并将 token 返回给客户端(可以设置到客户端的 cookie 中,也可以作为 response body)
  • 客户端接收到 token,并在之后发送请求时带上它(利用 cookie、作为请求头或作为参数均可)
  • 服务器对 token 进行解密、验证

签名算法、用 base64 编个码,方便传输

 

2.客户端存储方案 (cookie、localStorage、sessionStorage)

明文 用户名密码,还是 sessionId 和 token,都可以用这三种方式存储

 

cookie 可以作为传递的参数,并可通过后端进行控制,

local/session Storage 则主要用于在客户端中保存数据,其传输需要借助 cookie 或其它方式完成。

 

通常来说,在可以使用 cookie 的场景下,作为验证用途进行传输的用户名密码、sessionId、token 直接放在 cookie 里即可。

而后端传来的其它信息则可以根据需要放在 local/session Storage 中,作为全局变量之类进行处理。

 

3.后端登录拦截

shiro 的安全管理,实际上是基于会话实现的,使用session方案。

 

subject.login()  该过程会产生 session,并自动把 sessionId 设置到 cookie。

由pastman工具可见 , sessionId 在 tomcat 中的叫 JSESSIONID

 

完善的访问拦截 就是:

前端带上 sesisonId 发送请求交由后端 认证,坑爹之处主要在于 前后端分离的情况下 需要额外的配置解决  跨域问题。

默认的情况下,跨域的 cookie 是被禁止的,后端不能设置,前端也不能发送,所以两边都要设置。

 

1、后端设置 LoginInterceptor()类 preHandle 方法,   放行 options 请求

小知识:OPTIONS请求即 预检请求,可用于检测服务器允许的http方法。当发起跨域请求时,由于安全原因,触发一定条件时浏览器会在正式请求之前自动先发起OPTIONS请求,即CORS预检请求,服务器若接受该跨域请求,浏览器才继续发起正式请求。

 

 

2、配置类 MyWebConfigurer 做一些修改,主要是 addCorsMappings 方法

允许跨域的 cookie       cookie就是Credentials证书吗???

 

4、前端拦截 

前端 在 main.js 中 , 通过 axios 主动开启 withCredentials ,为了让前端能够带上 cookie

这样,前端每次发送请求时就会带上 sessionId,shiro 就可以通过 sessionId 获取登录状态并执行是否登录的判断。

 

为实现 前端页面拦截:

-beforeEach的使用

使用场景: 一般用在跳转前需要做校验的地方,如:进入首页前做登录校验,如果已登录则跳转首页,否则跳转登录页。

使用的地方:

 如果是做跳转首页前做登录校验,需要写在main.js文件中,表示在所有路由被访问之前做校验;

结果:访问每个页面前都向后端发送一个请求,目的是经由拦截器验证服务器端的登录状态

 

论bug是怎么产生的???

bug一:

login转index转不了

直接登录index以及其他页面也进不了

 

靠前端实现的拦截

“全局前置守卫”(router.beforeEach)前端拦截登录

后端判断是否登录以及登录的是谁?

 

localStorage, 主要用于在前端中 保存数据,其传输需要借助 cookie 或其它方式完成; 仅在客户端(即浏览器)中保存,不参与和服务器的通信

跨源资源共享(CORS),允许您灵活地指定什么样的跨域请求被授权

 

 

我们理一下逻辑:

shiro 的安全管理,用户加密与验证 我们用到了subject

subject.login 会产生 session,并自动把 sessionId 设置到 cookie。

浏览器之后再发送请求 访问的时候,可以带上cookie 的这个sessionid;服务器 通过sessionid获得session,即用户的登录状态

 

 

目标:

1)、靠前端实现的拦截     :实现

2)、靠后端实现拦截       : LoginInterceptor  认证一下是否subject.isAuthenticated() 、 MyWebConfigurer 在遇到哪些请求 生成拦截器

3)、解决跨域      :开启 cors,允许跨域的cookie

 

我们做了啥

前端:

第一步,靠前端实现的拦截

2、main.js中,通过 axios 主动开启 withCredentials

 

3、修改 router.beforeEach 方法, 访问每个页面前发送'/authentication' 请求,

访问每个页面前都向后端发送一个请求,目的是经由拦截器验证服务器端的登录状态

 

后端:

1、LoginInterceptor, preHandle 方法 放行options请求 

2、MyWebConfigurer , addCorsMappings 方法   .allowCredentials(true)允许跨域的cookie, 配置了适用于特定的路径模式的CORS

 

3、后端 '/authentication'  这个接口 暂时写成空的

 

结果:

通过  后端12,前端2 可实现 目标2 3:

 前端每次发送请求时就会带上 sessionId,shiro 就可以通过 sessionId 获取登录状态并执行是否登录的判断。

 

通过 前后端的3 可实现:

实现 页面的拦截,不能访问本来需要登录才能访问的页面

 

目标 2 3 完成,做 目标 1:

实现前端拦截

1、引入vuex,写store index.js

在项目打开的时候会判断本地存储中是否有 user 这个对象存在,如果存在就取出来并获得 username 的值,否则则把 username 设置为空。

2、 router\index.js, 在需要拦截的路由中加 meta: { requireAuth: true }

3、使用钩子函数 

钩子函数是  在某些时机会被调用的函数。这里我们使用 router.beforeEach(),意思是在访问每一个路由前调用。

1)在src\main.js ,导入store;  // if (store.state.user.username)   不用username还报错

2)在Vue对象里 添加 store;3)写router.beforeEach ;4)改login.vue  

 

必须要用_this.$store.commit (‘SET_TOKEN’, tokenV)   调用mutations里的方法,才能在store存储成功

 

bug一 解决

 

bug二  这怎么remmberme全是false

他这俩全是fasle我怎么登录进去的啊????!!!

bug三  我图书馆里的书呢???

 

浏览器发送url请求,首先通过MyWebConfigurer 判断是否拦截,然后触发LoginInterceptor。

 

// 白卷时间到了,我得看 springbootplus+mybatisplus了

 


 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值