“程”风破浪的开发者|satoken 实现优雅鉴权
作者:codingyt
- 2022-10-27 山东
本文字数:6631 字
阅读完需:约 22 分钟
https://sa-token.dev33.cn/doc/index-backup.html#/satoken 官方网址(很好用的鉴权)
依赖引入
<!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.30.0</version>
</dependency>
复制代码
yml 文件配置(中文版 进行理解的)
server:
# 端口
port: 8081
# Sa-Token配置
sa-token:
# token 名称 (同时也是cookie名称)
token-name: satoken
# token 有效期,单位s 默认30天, -1代表永不过期
timeout: 2592000
# token 临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
activity-timeout: -1
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: false
# token风格
token-style: uuid
# 是否输出操作日志
is-log: false
复制代码
yml 文件配置(英文版本 直接放到项目里面的)
# Sa-Token setting
sa-token:
token-name: satoken
timeout: 2592000
activity-timeout: -1
is-concurrent: true
is-share: false
token-style: uuid
is-log: false
复制代码
启动类加上日志打印
System.out.println("启动成功:Sa-Token配置如下:" + SaManager.getConfig());
复制代码
常用的几个内置函数
StpUtil.login(10001); #登陆成功之后使用 通常我们登陆成功之后放一个用户唯一的标识进去即可
StpUtil.isLogin(); 检测用户是否登陆
SaResult.data(StpUtil.getTokenInfo()); 获取用户的token信息的
StpUtil.logout(); 注销我们的账户的 可以随时退出
复制代码
权限获取类(用于获取我们的用户的标识)
package com.itheima.utils;
import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* 自定义权限验证接口扩展
*/
@Component // 保证此类被SpringBoot扫描,完成Sa-Token的自定义权限验证扩展
public class StpInterfaceImpl implements StpInterface {
/**
* 返回一个账号所拥有的权限码集合
*/
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询权限
//loginId:账号id,即你在调用 StpUtil.login(id) 时写入的标识值。
//loginType:账号体系标识,此处可以暂时忽略,在 [ 多账户认证 ] 章节下会对这个概念做详细的解释。
StpUtil.getLoginId();//我们可以根据这个来进行我们的角色的信息认证
//通过调用我们的
QueryWrapper<Object> wrapper = new QueryWrapper<>();
//查询到我们的用户信息
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) {
// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询角色
List<String> list = new ArrayList<String>();
list.add("admin");
list.add("super-admin");
return list;
}
}
复制代码
开启注解鉴权
package com.itheima.config;
import cn.dev33.satoken.interceptor.SaAnnotationInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
//配置我们的注解拦截器 这里我们的springboot的版本值如果太高的话 我们就不能直接这样做 具体情况具体分析
@EnableWebMvc//高版本就加 如果不是就可以不用加
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
// 注册Sa-Token的注解拦截器,打开注解式鉴权功能
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册注解拦截器,并排除不需要注解鉴权的接口地址 (与登录拦截器无关)
registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**");
}
}
复制代码
注意:如果在高版本 SpringBoot (≥2.6.x) 下注册拦截器生效,则需要额外添加 @EnableWebMvc 注解才可以使用。
注解鉴权实际案列演示
加密模块
自定义加密的密文(目前在使用的)
package com.sdjz.xgk.utils;
import cn.dev33.satoken.secure.SaSecureUtil;
/**
* @author yt1105
* @version 1.0
*/
public class secre {
public static final String key = "jkadjkajksdjkad";//自己定义属于自己秘钥进行加密处理
//生成秘钥(存到数据库里面的)
public static String generatePassword(String password) {
String ciphertext = SaSecureUtil.aesEncrypt(key, password);
return ciphertext;
}
//这里传递的是我们的加密后的数据(这个是加密之后的数据 才能调用这个接口才行)
public static String getDecrypt(String password) {
String text2 = SaSecureUtil.aesDecrypt(key, password);
return text2;
}
}
复制代码
// 定义秘钥和明文
String key = "123456";
String text = "Sa-Token 一个轻量级java权限认证框架";
// 加密
String ciphertext = SaSecureUtil.aesEncrypt(key, text);
System.out.println("AES加密后:" + ciphertext);
// 解密
String text2 = SaSecureUtil.aesDecrypt(key, ciphertext);
System.out.println("AES解密后:" + text2);
复制代码
非对称加密
RSA 加密(这个使用的话很可能会导致我们的代码出现一定的问题 字符长度过长可能会导致没法存到数据库中去)
// 定义私钥和公钥
String privateKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAO+wmt01pwm9lHMdq7A8gkEigk0XKMfjv+4IjAFhWCSiTeP7dtlnceFJbkWxvbc7Qo3fCOpwmfcskwUc3VSgyiJkNJDs9ivPbvlt8IU2bZ+PBDxYxSCJFrgouVOpAr8ar/b6gNuYTi1vt3FkGtSjACFb002/68RKUTye8/tdcVilAgMBAAECgYA1COmrSqTUJeuD8Su9ChZ0HROhxR8T45PjMmbwIz7ilDsR1+E7R4VOKPZKW4Kz2VvnklMhtJqMs4MwXWunvxAaUFzQTTg2Fu/WU8Y9ha14OaWZABfChMZlpkmpJW9arKmI22ZuxCEsFGxghTiJQ3tK8npj5IZq5vk+6mFHQ6aJAQJBAPghz91Dpuj+0bOUfOUmzi22obWCBncAD/0CqCLnJlpfOoa9bOcXSusGuSPuKy5KiGyblHMgKI6bq7gcM2DWrGUCQQD3SkOcmia2s/6i7DUEzMKaB0bkkX4Ela/xrfV+A3GzTPv9bIBamu0VIHznuiZbeNeyw7sVo4/GTItq/zn2QJdBAkEA8xHsVoyXTVeShaDIWJKTFyT5dJ1TR++/udKIcuiNIap34tZdgGPI+EM1yoTduBM7YWlnGwA9urW0mj7F9e9WIQJAFjxqSfmeg40512KP/ed/lCQVXtYqU7U2BfBTg8pBfhLtEcOg4wTNTroGITwe2NjL5HovJ2n2sqkNXEio6Ji0QQJAFLW1Kt80qypMqot+mHhS+0KfdOpaKeMWMSR4Ij5VfE63WzETEeWAMQESxzhavN1WOTb3/p6icgcVbgPQBaWhGg==";
String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDvsJrdNacJvZRzHauwPIJBIoJNFyjH47/uCIwBYVgkok3j+3bZZ3HhSW5Fsb23O0KN3wjqcJn3LJMFHN1UoMoiZDSQ7PYrz275bfCFNm2fjwQ8WMUgiRa4KLlTqQK/Gq/2+oDbmE4tb7dxZBrUowAhW9NNv+vESlE8nvP7XXFYpQIDAQAB";
// 文本
String text = "123123123";
// 使用公钥加密
String ciphertext = SaSecureUtil.rsaEncryptByPublic(publicKey, text);
System.out.println("公钥加密后:" + ciphertext);
// 使用私钥解密
String text2 = SaSecureUtil.rsaDecryptByPrivate(privateKey, ciphertext);
System.out.println("私钥解密后:" + text2);
复制代码
// 生成一对公钥和私钥,其中Map对象 (private=私钥, public=公钥)
System.out.println(SaSecureUtil.rsaGenerateKeyPair());
复制代码
登陆模块
package com.itheima.controller;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author yt1105
* @version 1.0
*/
//注解可以加上之后进行全局的拦截处理
@RestController
@RequestMapping("/user/")
public class loginController {
@RequestMapping("doLogin")
public SaResult doLogin(String username, String password) {
// 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对
// todo 前台传递给我们的 账号密码 我们获取一个id 将我们的数据库的数据进行比对
// 然后将我们的用户的id进行比对
if ("zhang".equals(username) && "123456".equals(password)) {
StpUtil.login(10001);
return SaResult.ok("登录成功");
}
return SaResult.error("登录失败");
}
// 查询登录状态,浏览器访问: http://localhost:8081/user/isLogin
@RequestMapping("isLogin")
public String isLogin() {
return "当前会话是否登录:" + StpUtil.isLogin();
}
//检测我们的用户的token信息
@RequestMapping("tokenInfo")
public SaResult tokenInfo() {
return SaResult.data(StpUtil.getTokenInfo());
}
// todo 到时候 我们的前提只要一调用这个接口的话 我们就直接注销这个用户的信息
// 测试注销 ---- http://localhost:8081/acc/logout
@RequestMapping("logout")
public SaResult logout() {
StpUtil.logout();
return SaResult.ok();
}
}
复制代码
获取权限信息
package com.sdjz.xgk.security.roles;
import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.sdjz.xgk.entity.SysUser;
import com.sdjz.xgk.mapper.SysUserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* 自定义权限验证接口扩展
*/
@Component // 保证此类被SpringBoot扫描,完成Sa-Token的自定义权限验证扩展
public class StpInterfaceImpl implements StpInterface {
@Autowired
SysUserMapper sysUserMapper;
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
List<String> list = new ArrayList<String>();
String loginId1 = (String) StpUtil.getLoginId();
//通过调用我们的
QueryWrapper<SysUser> wrapper = new QueryWrapper<>();
wrapper.like("phone", loginId1);
SysUser sysUser = sysUserMapper.selectOne(wrapper);
Integer role = sysUser.getRole();
//查询到我们的用户信息
if (role.equals(0)) {//书写自己逻辑
//普通用户
list.add("user-add");
list.add("user-delete");
list.add("user-update");
}
if (role.equals(1)) {
//普通用户
list.add("user-add");
list.add("user-delete");
list.add("user-update");
}
if (role.equals(2)) {
//普通用户
list.add("user-add");
list.add("user-delete");
list.add("user-update");
}
return list;
}
/**
* 返回一个账号所拥有的角色标识集合 (权限与角色可分开校验)
*/
@Override
public List<String> getRoleList(Object loginId, String loginType) {
List<String> list = new ArrayList<String>();
String loginId1 = (String) StpUtil.getLoginId();
//通过调用我们的
QueryWrapper<SysUser> wrapper = new QueryWrapper<>();
wrapper.like("phone", loginId1);
SysUser sysUser = sysUserMapper.selectOne(wrapper);
Integer role = sysUser.getRole();
//查询到我们的用户信息
if (role.equals(0)) {
//普通用户
list.add("admin");
}
if (role.equals(1)) {
//专家
list.add("admin");
}
if (role.equals(2)) {
//admin
list.add("admin");
list.add("user");
}
return list;
}
}
复制代码
退出登陆模块
// 测试注销 ---- http://localhost:8081/acc/logout
@RequestMapping("logout")
public SaResult logout() {
StpUtil.logout();
return SaResult.ok();
}
复制代码
前后端分离(微信小程序)
1、后端将 token 返回到前端
首先调用 StpUtil.login(id) 进行登录。
调用 StpUtil.getTokenInfo() 返回当前会话的 token 详细参数。
此方法返回一个对象,其有两个关键属性:tokenName 和 tokenValue(token 的名称和 token 的值)。
将此对象传递到前台,让前端人员将这两个值保存到本地。
代码示例:
// 登录接口
@RequestMapping("doLogin")
public SaResult doLogin() {
// 第1步,先登录上
StpUtil.login(10001);
// 第2步,获取 Token 相关参数
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
// 第3步,返回给前端
return SaResult.data(tokenInfo);
}
复制代码
2、前端将 token 提交到后端
无论是 app 还是小程序,其传递方式都大同小异。
那就是,将 token 塞到请求 header 里 ,格式为:{tokenName: tokenValue}。
以经典跨端框架 uni-app 为例:
方式 1 简单粗暴(官网获取)
// 1、首先在登录时,将 tokenValue 存储在本地,例如:
uni.setStorageSync('tokenValue', tokenValue);
// 2、在发起ajax请求的地方,获取这个值,并塞到header里
uni.request({
url: 'https://www.example.com/request', // 仅为示例,并非真实接口地址。
header: {
"content-type": "application/x-www-form-urlencoded",
"satoken": uni.getStorageSync('tokenValue') // 关键代码, 注意参数名字是 satoken
},
success: (res) => {
console.log(res.data);
}
});
复制代码
剪贴板错误
**方式2更加灵活(官网获取)**
```java
// 1、首先在登录时,将tokenName和tokenValue一起存储在本地,例如:
uni.setStorageSync('tokenName', tokenName);
uni.setStorageSync('tokenValue', tokenValue);
// 2、在发起ajax的地方,获取这两个值, 并组织到head里
var tokenName = uni.getStorageSync('tokenName'); // 从本地缓存读取tokenName值
var tokenValue = uni.getStorageSync('tokenValue'); // 从本地缓存读取tokenValue值
var header = {
"content-type": "application/x-www-form-urlencoded"
};
if (tokenName != undefined && tokenName != '') {
header[tokenName] = tokenValue;
}
// 3、后续在发起请求时将 header 对象塞到请求头部
uni.request({
url: 'https://www.example.com/request', // 仅为示例,并非真实接口地址。
header: header,
success: (res) => {
console.log(res.data);
}
});
```
1. 只要按照如此方法将token值传递到后端,Sa-Token 就能像传统PC端一样自动读取到 token 值,进行鉴权。
### 前后端分离(web端 以vue为例)
复制代码
划线
评论
复制
发布于: 刚刚阅读数: 3
版权声明: 本文为 InfoQ 作者【codingyt】的原创文章。
原文链接:【http://xie.infoq.cn/article/ae8214a062fcfeed1a36b2124】。文章转载请联系作者。
codingyt
关注
还未添加个人签名 2022-10-25 加入
还未添加个人简介
评论