SaToken认证与授权框架

一、SaToken简介

Sa-Token是一个轻量级Java权限认证框架,主要解决:登录认证、权限认证、单点登录、OAuth2.0、分布式Session会话、微服务网关鉴权 等一系列权限相关问题。SaToken官方文档非常详尽,大家可以按照手册的指引可以很轻松的把SaToken整合到SpringBoot项目中。

二、认证与授权

颁发令牌(Token)

SaToken框架只会在两种业务场景中用到:登陆与访问。先请大家来看一下登录场景是怎么使用SaToken框架的。当用户登陆的时候,经过Java项目对登陆用户名和密码的核对,允许用户登陆系统。这时候需要我们调用SaToken的工具类(StpUtil)创建令牌字符串(Token)。SaToken生成的令牌字符串会被缓存到Redis中,接下来Web方法会把这个令牌字符串写到Http响应返回给客户端。

Token是一个加密的字符串,里面包含了userId、过期时间和随机数等等,它是用户登陆成功的凭证。如果客户端的令牌被别人盗取了,那么其他人就可以冒充该用户了。为了避免令牌在网络传输过程中被盗取,我们可以采用HTTPS协议,这样别人就无法窃取数据了。后续我们会为Java项目配置HTTPS,大家尽可放心。

在SaToken框架中,颁发令牌非常简单,只需要调用StpUtil工具类的方法即可。我们需要向SaToken会话对象提供当前用户的userId,然后SaToken才可以生成Token令牌。换而言之,如果将来我们拿到用户的令牌,SaToken可以反向解析出用户的userId,我们就能知道是哪个用户访问的Web方法。

//向当前SaToken会话对象传递用户ID,只有提供了用户ID才能生成令牌。
StpUtil.login(userId);
//生成令牌字符串
String token = StpUtil.getTokenValue();

验证令牌

用户成功登陆系统之后,客户端每次访问Web方法的时候,必须要上传Token令牌。如果不上传令牌,Java项目可以认定用户没有登陆系统,所以拒绝客户端访问Web方法。即便客户端提交了令牌,SaToken还是需要认真检查令牌的真伪。如果无法从令牌中反向解析出用户ID和令牌过期时间,那么就可以认定令牌是伪造的,SaToken则拒绝客户端访问Web方法。

假如遇上厉害的黑客破解了SaToken生成令牌的算法,他用这套算法自己生成了一个令牌,是不是能骗过SaToken系统呢?也不行。因为SaToken生成的每个令牌在Redis中都有存档,所以黑客手上的令牌,SaToken是不认的。这也就是为什么上图中第三个步骤需要调用Redis的原因。

其实也不是所有的Web方法或者HTML页面都需要用户登陆之后才能访问,比如登陆页面和对应的后端Web方法。但是有些Web方法必须用户登陆之后才能访问,我们可以给Web方法添加@SaCheckLogin注解。这个注解会拦截Web方法的请求,让SaToken验证客户端提交的Token令牌。如果令牌合法就允许调用Web方法,反之就拒绝HTTP请求,返回401状态码。

@RestController
@RequestMapping("/mis/user")
public class UserController {
    ……
    
    @GetMapping("/searchUserSummary")
    @SaCheckLogin
    public R searchUserSummary() {
        ……
    }
}

 权限验证

对于业务端来说,所有登陆的用户都是客户角色,身份上完全一致,所以业务端对应的Web方法只需要验证用户是否登陆即可,不需要做权限判定。然而使用登录的用户,角色上并不统一,有的是超级管理员,有的是部门经理,甚至还有普通员工。登录的WEB方法既要验证用户是否登陆,还要判定是否拥有特定的权限。如果不具备相关的权限,SaToken就拒绝客户端访问Web方法。

三、编写鉴权类

鉴权类是需要我们自己实现的,必须要扩展StpInterface接口才可以。在这里博主做个演示,创建StpInterfaceImpl.java类。

package com.example.his.api.config.sa_token;

import cn.dev33.satoken.stp.StpInterface;
import com.example.his.api.db.dao.UserDao;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

@Component
public class StpInterfaceImpl implements StpInterface {
    @Resource
    private UserDao userDao;

    /**
     * 返回一个用户所拥有的权限集合
     */
    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        List<String> list = new ArrayList<>();
        int userId = Integer.parseInt(loginId.toString());
        Set<String> set = userDao.searchUserPermissions(userId);
        list.addAll(set);
        return list;
    }


    /**
     * 返回一个用户所拥有的角色标识集合
     */
    @Override
    public List<String> getRoleList(Object loginId, String loginKey) {
        ArrayList<String> list = new ArrayList();
        return list;

    }
}

四、为什么不通过角色鉴权? 

由于登录系统中的权限是固定的,而角色是可以动态增减的。如果我们用@SaCheckRole注解,规定访问Web方法的用户必须具备什么角色。如果将来该角色被删除掉,难道我们还要修改大量的Web方法注解吗?就像下面的Web方法,如果角色1被删除,难不成还要让程序员重新修改代码,重新打包程序,重新发布项目吗?

@RestController("MisAppointmentController")
@RequestMapping("/mis/appointment")
public class AppointmentController {
    @Resource
    private AppointmentService appointmentService;

    @PostMapping("/searchByPage")
    @SaCheckRole(value = {"超级管理员", "部门经理", "角色1"}, mode = SaMode.OR)
    public R searchByPage(@RequestBody @Valid SearchAppointmentByPageForm form) {
        ……
    }
}

这么看下来,鉴权类中的getRoleList()函数返回空的ArrayList对象就能说的通了。我们根本不需要验证用户的角色,干嘛要去查询用户的角色呢?

五、为何要对令牌续期? 

只要用户成功登陆系统就会拿到Token令牌,但是令牌并不是永久有效的,它存在过期时间。假设令牌的过期时间是30天,用户在这30天内,都可以免于登陆直接使用登录系统。不管用户在有效期内使用登录系统多少次,只要过了30天,用户就必须要登陆一次,拿到新的令牌才可以继续使用登录系统。例如163邮箱和慕课网都是这种令牌过期时间的策略,过期就必须重新登陆。

从用户的角度来看,我一直使用登录系统,令牌就应该一直续期,除非我长期不用,令牌才可以过期。为了满足用户的需求,提升网站的满意度,我们可以给令牌自动续期,恰好SaToken也具备这个功能。

六、令牌如何续期? 

SaToken令牌续期的过程比较复杂,所以我使用推演的方式,咱们逐步理解令牌续期的完整过程。

有效期和临时有效期

介绍令牌续期原理之前,我们先确定两个时间概念:有效期(timeout)和临时有效期(activity-timeout)。临时有效期很容易解释,只要用户访问登录系统的间隔时间不超过临时有效期,令牌就一直有效;有效期指的是即便用户不经常访问登录系统,只要在有效期以内,令牌就依然有效。

使用令牌的间隔时间

还记得SaToken生成的Token会缓存到Redis里面一份吗?令牌续期跟这个Redis缓存有很大的关系。缓存技术用的实在太巧妙了。执行@SaCheckLogin@SaCheckPermission注解都会向Redis中记录使用的时刻。在向Redis中保存当前时刻之前,SaToken框架会用当前时刻减去Redis中保存的时刻,这就是该Token令牌被使用的间隔时间。大家记住这个间隔时间,非常关键。

何时做令牌续期?

如果当前时刻与有效期之间的差距小于临时有效期,SaToken就会查看Token令牌前后间隔时间是不是小于临时有效期。如果是,那就对令牌续期。如果不是,就不对令牌续期。上面这段话不太容易理解,我举个例子。比如说令牌有效期是30天,临时有效期是3天。从用户登陆成功开始算起,现在是第25天,用户访问了登录系统,我们完全不用考虑续期的问题。因为临时有效期是3天,在第27天之前,即便SaToken给令牌续期,过期时间依然也没有超过第30天,你觉得有必要续期吗?当然没必要。在第27天~30天之间,如果用户访问了登录系统,SaToken就必须考虑令牌续期的问题了。检查一下Token使用的间隔时间,在超过了临时有效期之内,就可以给令牌续期3天;反之就不续期。

续期产生新令牌吗?

Token续期既可以生成新令牌,也可以不生成新令牌,这个要看认证与授权框架是怎么设计的。比如SaToken的自动续期是不会产生新令牌的。也许有的同学还记得我说过令牌字符串中包含了加密过的过期时间。如果SaToken对令牌续期,应该生成新的令牌字符串,才能体现新的过期时间,原有的令牌怎么能体现是续过期的呢?答案很简单,SaToken对Redis缓存做了手脚:延长了Redis销毁该令牌的过期时间。

按照上图的流程,即便SaToken发现令牌中的过期时间早就到了,但是有可能这个令牌是续过期的。于是SaToken必须到Redis中检查该令牌是否存在缓存记录。如果还存在,说明该令牌是续过期的,而且还没有超期,令牌是有效的。

应该续期多少天

刚才我举例子的时候说到了,SaToken对令牌续期3天,而不是30天。那么为什么不续期30天呢?其实很简单,用户长期持有固定的令牌不安全。如果续期令牌,还是续期3天为妙,你觉得呢?

七、配置YML文件

application.yml文件中,填入SaToken的配置信息,如下:

sa-token:
  #HTTP请求头中哪个属性用来上传令牌
  token-name: token
  #过期时间(秒),设置为30天
  timeout: 2592000
  #临时有效期,设置为3天
  activity-timeout: 259200
  #不允许相同账号同时在线,新登陆的账号会挤掉原来登陆的账号
  allow-concurrent-login: false
  #在多人登陆相同账号的时候,是否使用相同的Token
  is-share: false
  token-style: uuid
  #是否读取Cookie中的令牌
  isReadCookie: false
  #同端互斥
  isConcurrent: false
  #SaToken缓存令牌用其他的逻辑库,避免业务数据和令牌数据共用相同的Redis逻辑库
  alone-redis:
    database: 1
    host: localhost
    port: 6379
    password: abc123456
    timeout: 10s
    lettuce:
      pool:
        # 连接池最大连接数
        max-active: 200
        # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: 10s
        # 连接池中的最大空闲连接
        max-idle: 16
        # 连接池中的最小空闲连接
        min-idle: 8

八、创建自签名数字证书 

也许大家觉得前后端项目都部署在同一台服务器主机上面,前后端项目之间不需要使用HTTPS协议,其实不然。如果前后端项目都部署在同一台主机上面,该主机的负载会偏大,所以通常的做法是把前后端项目部署在不同主机上面。这种情况下,局域网之内的数据传输也应该加密,所以给后端项目配置HTTPS协议是必要的。

开通HTTPS协议需要使用数字证书,然而申请数字证书需要有公网域名。目前对于一些处在开发阶段的项目,我们可以自己创建一个数字证书,开通HTTPS协议。将来我们的项目上线的时候,再去申请正式的数字证书就行了。

命令就不在这里演示啦,大家想要学习的话,就发私信联系博主!博主带领大家一步一步在开发项目中实现https数字证书来保证安全性问题。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值