当前位置:   article > 正文

SaToken技术分享文档

satoken

SaToken技术分享

1.什么是SaToken

Sa-Token 是一个轻量级 Java 权限认证框架,主要解决:登录认证权限认证Session会话单点登录OAuth2.0微服务网关鉴权 等一系列权限相关问题,简化了相对Shiro或者SpringSecurity的复杂的前置配置,比如自定义的Realm或者全局过滤器这些。

SaToekn与Shiro以及SpringSecurity对比

相同点:都具备认证授权加密会话缓存rememerMe等功能,都是一种安全认证框架。

SpringSecurity优点:

  • SpringSecurity是基于Spring进行开发的 ,需要依赖Spring Shiro是需要和Spring进行整合开发
  • SpringSecurity功能相对比Shiro更加丰富以及社区资源相对Shiro更多

Shiro优点:

  • Shiro的配置和使用比较简单而SpringSecurity相对上手比较复杂
  • Shiro是一种相对独立的容器,依赖性比较低,可以独立运行但是 Spring Security 依赖Spring容器;

saToken的优点

  • 功能相对Shiro和SpringSecurity更加全面和完整
  • 更加独立,整合项目过程中只需要引入依赖添加配置即可简化了部署的操作
  • 入门以及操作更加简单简化了类似Shiro这种需要自定义Relam的操作,官方文档是中文版的,更好理解

2.常用功能介绍

  • 登录认证 —— 单端登录、多端登录、同端互斥登录、七天内免登录
  • 权限认证 —— 权限认证、角色认证、会话二级认证
  • 踢人下线 —— 根据账号id踢人下线、根据Token值踢人下线
  • 账号封禁 —— 指定天数封禁、永久封禁、设定解封时间
  • 持久层扩展 —— 可集成Redis等专业缓存中间件,重启数据不丢失
  • 分布式会话 —— 提供jwt集成、共享数据中心两种分布式会话方案
  • 独立Redis —— 将权限缓存与业务缓存分离
  • 同端互斥登录 —— 像QQ一样手机电脑同时在线,但是两个手机上互斥登录
  • 多账号认证体系 —— 比如一个商城项目的user表和admin表分开鉴权
  • 花式token生成 —— 内置六种Token风格,还可:自定义Token生成策略、自定义Token前缀
  • 注解式鉴权 —— 优雅的将鉴权与业务代码分离
  • 路由拦截式鉴权 —— 根据路由拦截鉴权,可适配restful模式
  • 密码加密 —— 提供密码加密模块,可快速MD5、SHA1、SHA256、AES、RSA加密
  • 全局侦听器 —— 在用户登陆、注销、被踢下线等关键性操作时进行一些AOP操作
StpUtil.login(10001);    // 标记当前会话登录的账号id
StpUtil.getLoginId();    // 获取当前会话登录的账号id
StpUtil.isLogin();    // 获取当前会话是否已经登录, 返回true或false
StpUtil.logout();    // 当前会话注销登录
StpUtil.kickout(10001);    // 将账号为10001的会话踢下线
StpUtil.hasRole("super-admin");    // 查询当前账号是否含有指定角色标识, 返回true或false
StpUtil.hasPermission("user:add");    // 查询当前账号是否含有指定权限, 返回true或false
StpUtil.getTokenValueByLoginId(10001);    // 获取账号id为10001的token令牌值
StpUtil.login(10001, "PC");    // 指定设备标识登录,常用于“同端互斥登录”
StpUtil.kickout(10001, "PC");    // 指定账号指定设备标识踢下线 (不同端不受影响)
StpUtil.openSafe(120);    // 在当前会话开启二级认证,有效期为120秒 
StpUtil.checkSafe();    // 校验当前会话是否处于二级认证有效期内,校验失败会抛出异常 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

3.使用操作流程

1.创建SpringBoot项目

2.添加依赖

<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-spring-boot-starter</artifactId>
    <version>1.29.0</version>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

3.编写配置文件

server:
    # 端口
    port: 8081
# Sa-Token配置
sa-token: 
    # token名称 
    token-name: token
    # token有效期,单位s 默认30天, -1代表永不过期 
    timeout: 2592000
    # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) 
    is-concurrent: false
    # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) 
    is-share: false
    # token风格
    token-style: uuid
    # 是否输出操作日志 
    is-log: true
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

3.启动SpringBoot项目 进行启动

4.编写测试

@Api(tags = "用户基础操作")
@RestController
@RequestMapping("/acc/")
public class LoginController {
    public static final String LJX = "ljx";
    public static final String PASSWORD = "123456";
    
    @GetMapping("doLogin")
    @ApiOperation("登录操作")
    public SaResult doLogin(String name, String pwd) {
        //todo 这个地方放置具体的数据库操作
        if (LJX.equals(name) && PASSWORD.equals(pwd)) {
            // 存放的是登录用户的账户account
            StpUtil.login(10001);
            String tokenName = StpUtil.getTokenName();
            System.out.println("token名称是"+tokenName);
            String tokenValue = StpUtil.getTokenValue();
            System.out.println("token的值"+tokenValue);
            SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
            System.out.println("token的信息参数"+tokenInfo);
            return SaResult.ok("登录成功");
        }
        return SaResult.error("登录失败");
    }
    
    @ApiOperation("判断当前的登录状态")
    @GetMapping("isLogin")
    public SaResult isLogin() {
        return SaResult.ok(StpUtil.isLogin() + "");
    }
    
    @RequestMapping("tokenInfo")
    @GetMapping("获取当前的token信息")
    public SaResult tokenInfo() {
        return SaResult.data(StpUtil.getTokenInfo());
    }
    
    @ApiOperation("账号注销登录")
    @GetMapping("logout")
    public SaResult logout() {
        StpUtil.logout();
        return SaResult.ok();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

4.注解式鉴权操作流程

1.编写实现StpInterface接口的类 实现获取当前登录用户的权限集合以及角色集合的操作

@Component
public class StpInterfaceImpl implements StpInterface {

    /**
     * 返回一个账号所拥有的权限码集合
     */
    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        // todo 根据从数据库中进行查询
        List<String> list = new ArrayList<String>();
        list.add("user-add");
//        list.add("user-delete");
//        list.add("user-update");
        list.add("user-get");
        list.add("article-get");
        return list;
    }

    /**
     * 返回一个账号所拥有的角色标识集合
     */
    @Override
    public List<String> getRoleList(Object loginId, String loginType) {
        // todo 根据从数据库中进行查询
        List<String> list = new ArrayList<String>();
        list.add("admin");
        list.add("super-admin");
        return list;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

2.定义全局异常拦截

/**
 * 全局异常处理
 */
@ControllerAdvice
public class GlobalException {
    // 全局异常拦截(拦截项目中的所有异常)
    @ResponseBody
    @ExceptionHandler
    public AjaxJson handlerException(Exception e, HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        // 不同异常返回不同状态码
        AjaxJson aj;
        if (e instanceof NotLoginException) {
			// 如果是未登录异常
            NotLoginException ee = (NotLoginException) e;
            aj = AjaxJson.getNotLogin().setMsg(ee.getMessage());
        } else if (e instanceof NotRoleException) {
			// 如果是角色异常
            NotRoleException ee = (NotRoleException) e;
            aj = AjaxJson.getNotJur("无此角色:" + ee.getRole());
        } else if (e instanceof NotPermissionException) {
			// 如果是权限异常
            NotPermissionException ee = (NotPermissionException) e;
            aj = AjaxJson.getNotJur("无此权限:" + ee.getCode());
        } else if (e instanceof DisableLoginException) {
			// 如果是被封禁异常
            DisableLoginException ee = (DisableLoginException) e;
            aj = AjaxJson.getNotJur("账号被封禁:" + ee.getDisableTime() + "秒后解封");
        } else {
        	// 普通异常, 输出:500 + 异常信息
            aj = AjaxJson.getError(e.getMessage());
        }
        // 返回给前端
        return aj;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

3.使用鉴权式注解

@Api(tags = "注解鉴权测试")
@RestController
@RequestMapping("/at/")
public class AtController {

	@SaCheckLogin
	@ApiOperation("检验之后登录的用户才可以进行操作")
	@GetMapping("checkLogin")
	public SaResult checkLogin() {
		return SaResult.ok();
	}

	@ApiOperation("具有指定权限操作")
	@SaCheckPermission("user-add")
	@GetMapping("checkPermission")
	public SaResult checkPermission() {
		return SaResult.ok();
	}

	@ApiOperation("同时具有指定权限操作")
	@SaCheckPermission({"user-add", "user-delete", "user-update"})
	@GetMapping("checkPermissionAnd")
	public SaResult checkPermissionAnd() {
		return SaResult.ok();
	}

	@ApiOperation("具有多个指定权限中的一个操作")
	@SaCheckPermission(value = {"user-add", "user-delete", "user-update"}, mode = SaMode.OR)
	@GetMapping("checkPermissionOr")
	public SaResult checkPermissionOr() {
		return SaResult.ok();
	}

	@ApiOperation("具有指定角色才可以进行操作")
	@SaCheckRole("admin")
	@GetMapping("checkRole")
	public SaResult checkRole() {
		return SaResult.ok();
	}

	@ApiOperation("开启二级认证")
	@GetMapping("openSafe")
	public SaResult openSafe() {
		// todo 在这个地方添加校验是否开启二级认证
		// 打开二级认证,有效期为200秒
		StpUtil.openSafe(200);
		return SaResult.ok();
	}
	
	@ApiOperation("通过二级认证之后才可以进行操作")
	@SaCheckSafe
	@GetMapping("checkSafe")
	public SaResult checkSafe() {
		// todo 添加一些需要二级认证之后才可以进行的操作
		return SaResult.ok();
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

5.路由拦截鉴权

@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
	
	/**
	 * 注册Sa-Token 的拦截器,打开注解式鉴权功能 
	 */
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		// todo 注册注解拦截器 可以用来存放允许放行的路径
        // 除了/user/doLogin 可以放行  其余的操作都需要进行登录之后才可以进行操作
		registry.addInterceptor(new SaAnnotationInterceptor())
				.addPathPatterns("/**")
				.excludePathPatterns("/user/doLogin");
	}
    @Override
//	public void addInterceptors(InterceptorRegistry registry) {
//		// 注册路由拦截器,自定义认证规则 
//		registry.addInterceptor(new SaRouteInterceptor((req, res, handler)->{
//			// 根据路由划分模块,不同模块不同鉴权 
//			SaRouter.match("/user/**", r -> StpUtil.checkPermission("user"));
//			SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin"));
//			SaRouter.match("/goods/**", r -> StpUtil.checkPermission("goods"));
//			SaRouter.match("/orders/**", r -> StpUtil.checkPermission("orders"));
//			SaRouter.match("/notice/**", r -> StpUtil.checkPermission("notice"));
//			SaRouter.match("/comment/**", r -> StpUtil.checkPermission("comment"));
//		})).addPathPatterns("/**");
//	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

6.技术点

1.自定义token类型

token-style=simple-uuid

2.同端互斥登录 指定客户端登录与退出

StpUtil.login(2345, “PC”);

StpUtil.logout(2345, “PC”);

3.密码加密

SaSecureUtil.md5(“123456”);

SaSecureUtil.md5BySalt(“123456”, “salt”);

4.全局监听器

/**
 * 自定义侦听器的实现 
 */
@Component
public class MySaTokenListener implements SaTokenListener {

    /** 每次登录时触发 */
    @Override
    public void doLogin(String loginType, Object loginId, SaLoginModel loginModel) {
        // ... 
    }

    /** 每次注销时触发 */
    @Override
    public void doLogout(String loginType, Object loginId, String tokenValue) {
        // ... 
    }

    /** 每次被顶下线时触发 */
    @Override
    public void doReplaced(String loginType, Object loginId, String tokenValue) {
        // ... 
    }
	...

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

5.与jwt继承

引入依赖

<!-- Sa-Token 整合 jwt -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-jwt</artifactId>
    <version>1.29.0</version>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

配置

sa-token:
    # jwt秘钥 
    jwt-secret-key: 456789123asdfghjk
  • 1
  • 2
  • 3

注入指定Token方式

@Configuration
public class SaTokenConfigure {
    // Sa-Token 整合 jwt (Style模式)
    @Bean
    public StpLogic getStpLogicJwt() {
        return new StpLogicJwtForStyle();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
saToekn官网文档: https://sa-token.dev33.cn/doc/index.html#/
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/933763
推荐阅读
相关标签
  

闽ICP备14008679号