Use sa-token

Introduction

A lightweight Java authority authentication framework, I personally think it is easier to use and more convenient to configure than Spring Security and shrio!

This article only introduces the functions for simple use. For detailed use, please visit the official development document

Official development document address: https://sa-token.cc

use

SpringBoot integrated Sa-Token example

  1. add dependencies

<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-spring-boot-starter</artifactId>
    <version>1.34.0</version>
</dependency>

Note: If you are using SpringBoot 3.x, just change sa-token-spring-boot-starter to sa-token-spring-boot3-starter.

  1. Configuration file to add usage requirements

server:
    # 端口
    port: 8081

############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
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: true
    # token风格
    token-style: uuid
    # 是否输出操作日志 
    is-log: false

The above two steps have completed the basic configuration

business use

login authentication

The following functions are directly available in code

// 登录功能(账号密码校验成功后使用):参数填写要登录的账号id,建议的数据类型:long | int | String, 不可以传入复杂类型,如:User、Admin 等等
StpUtil.login(Object id);  

// 当前会话注销登录
StpUtil.logout();

// 获取当前会话是否已经登录,返回true=已登录,false=未登录
StpUtil.isLogin();

// 检验当前会话是否已经登录, 如果未登录,则抛出异常:`NotLoginException`
StpUtil.checkLogin();

// 获取当前会话账号id, 如果未登录,则抛出异常:`NotLoginException`
StpUtil.getLoginId();

// 类似查询API还有:
StpUtil.getLoginIdAsString();    // 获取当前会话账号id, 并转化为`String`类型
StpUtil.getLoginIdAsInt();       // 获取当前会话账号id, 并转化为`int`类型
StpUtil.getLoginIdAsLong();      // 获取当前会话账号id, 并转化为`long`类型

// ---------- 指定未登录情形下返回的默认值 ----------

// 获取当前会话账号id, 如果未登录,则返回null 
StpUtil.getLoginIdDefaultNull();

// 获取当前会话账号id, 如果未登录,则返回默认值 (`defaultValue`可以为任意类型)
StpUtil.getLoginId(T defaultValue);

// 获取当前会话的token值
StpUtil.getTokenValue();

// 获取当前`StpLogic`的token名称
StpUtil.getTokenName();

// 获取指定token对应的账号id,如果未登录,则返回 null
StpUtil.getLoginIdByToken(String tokenValue);

// 获取当前会话剩余有效期(单位:s,返回-1代表永久有效)
StpUtil.getTokenTimeout();

// 获取当前会话的token信息参数
StpUtil.getTokenInfo();

After logging in, it is related to the permission function

Authentication

The so-called authority authentication, the core logic is to judge whether an account has the specified authority:

  • Yes, let you pass.

  • No? Then no access!

Going deep into the underlying data, each account will have a set of permission codes, and the framework will check whether the set contains the specified permission codes.

For example: the current account has a permission code set ["user-add", "user-delete", "user-get"], at this time I will verify the permission "user-update", the result is: verification failed, forbidden visit .

Get the current account permission code set

Because the requirements of each project are different, its permission design is also ever-changing, so the operation of [get the current account permission code set] cannot be built into the framework, so Sa-Token exposes this operation to you in the form of an interface for your convenience You rewrite according to your own business logic.

All you need to do is to create a new class and implement the StpInterface interface, such as the following code:

/**
 * 自定义权限验证接口扩展
 */
@Component    // 保证此类被SpringBoot扫描,完成Sa-Token的自定义权限验证扩展 
public class StpInterfaceImpl implements StpInterface {

    /**
     * 返回一个账号所拥有的权限码集合 
     */
    @Override
    public List<String> getPermissionList(Object loginId, String loginType) {
        // 本list仅做模拟,实际项目中要根据具体业务逻辑来查询权限
        List<String> list = new ArrayList<String>();    
        list.add("101");
        list.add("user.add");
        list.add("user.update");
        list.add("user.get");
        // list.add("user.delete");
        list.add("art.*");
        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;
    }

}

Then you can use the following api to authenticate (annotation authentication is recommended)

// 获取:当前账号所拥有的权限集合
StpUtil.getPermissionList();

// 判断:当前账号是否含有指定权限, 返回 true 或 false
StpUtil.hasPermission("user.add");        

// 校验:当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException 
StpUtil.checkPermission("user.add");        

// 校验:当前账号是否含有指定权限 [指定多个,必须全部验证通过]
StpUtil.checkPermissionAnd("user.add", "user.delete", "user.get");        

// 校验:当前账号是否含有指定权限 [指定多个,只要其一验证通过即可]
StpUtil.checkPermissionOr("user.add", "user.delete", "user.get");    

// 获取:当前账号所拥有的角色集合
StpUtil.getRoleList();

// 判断:当前账号是否拥有指定角色, 返回 true 或 false
StpUtil.hasRole("super-admin");        

// 校验:当前账号是否含有指定角色标识, 如果验证未通过,则抛出异常: NotRoleException
StpUtil.checkRole("super-admin");        

// 校验:当前账号是否含有指定角色标识 [指定多个,必须全部验证通过]
StpUtil.checkRoleAnd("super-admin", "shop-admin");        

// 校验:当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可] 
StpUtil.checkRoleOr("super-admin", "shop-admin");        

Annotation implementation

1. Register interceptor (add annotation authentication (authentication logic and business logic are separated), or not)

Taking SpringBoot2.0 as an example, create a new configuration class SaTokenConfigure.java

@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
    // 注册 Sa-Token 拦截器,打开注解式鉴权功能 
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册 Sa-Token 拦截器,打开注解式鉴权功能 
        registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");    
    }
}

2. Use annotation authentication

Function:

@SaCheckLogin: 登录校验 —— 只有登录之后才能进入该方法。
@SaCheckRole("admin"): 角色校验 —— 必须具有指定角色标识才能进入该方法。
@SaCheckPermission("user:add"): 权限校验 —— 必须具有指定权限才能进入该方法。
@SaCheckSafe: 二级认证校验 —— 必须二级认证之后才能进入该方法。
@SaCheckBasic: HttpBasic校验 —— 只有通过 Basic 认证后才能进入该方法。
@SaIgnore:忽略校验 —— 表示被修饰的方法或类无需进行注解鉴权和路由拦截器鉴权。
@SaCheckDisable("comment"):账号服务封禁校验 —— 校验当前账号指定服务是否被封禁。

Example usage:

// 登录校验:只有登录之后才能进入该方法 
@SaCheckLogin                        
@RequestMapping("info")
public String info() {
    return "查询用户信息";
}

// 角色校验:必须具有指定角色才能进入该方法 
@SaCheckRole("super-admin")        
@RequestMapping("add")
public String add() {
    return "用户增加";
}

// 权限校验:必须具有指定权限才能进入该方法 
@SaCheckPermission("user-add")        
@RequestMapping("add")
public String add() {
    return "用户增加";
}

// 二级认证校验:必须二级认证之后才能进入该方法 
@SaCheckSafe()        
@RequestMapping("add")
public String add() {
    return "用户增加";
}

// Http Basic 校验:只有通过 Basic 认证后才能进入该方法 
@SaCheckBasic(account = "sa:123456")
@RequestMapping("add")
public String add() {
    return "用户增加";
}

// 校验当前账号是否被封禁 comment 服务,如果已被封禁会抛出异常,无法进入方法 
@SaCheckDisable("comment")                
@RequestMapping("send")
public String send() {
    return "查询用户信息";
}

3. Set the verification mode

The @SaCheckRole and @SaCheckPermission annotations can set the verification mode, for example:

// 注解式鉴权:只要具有其中一个权限即可通过校验 
@RequestMapping("atJurOr")
@SaCheckPermission(value = {"user-add", "user-all", "user-delete"}, mode = SaMode.OR)        
public SaResult atJurOr() {
    return SaResult.data("用户信息");
}

mode has two values:

  • SaMode.AND, mark a group of permissions, the session must have all of them to pass the verification.

  • SaMode.OR, mark a set of permissions, as long as the session has one of them, it can pass the verification.

4. Double "or verification" of role permissions

Assume the following business scenario: an interface can be invoked when it has the authority user.add or the role admin. how to write?

// 角色权限双重 “or校验”:具备指定权限或者指定角色即可通过校验
@RequestMapping("userAdd")
@SaCheckPermission(value = "user.add", orRole = "admin")        
public SaResult userAdd() {
    return SaResult.data("用户信息");
}

5. Ignore authentication

Use @SaIgnore to indicate that an interface ignores authentication:

@SaCheckLogin
@RestController
public class TestController {

    // ... 其它方法 

    // 此接口加上了 @SaIgnore 可以游客访问 
    @SaIgnore
    @RequestMapping("getList")
    public SaResult getList() {
        // ... 
        return SaResult.ok(); 
    }
}

The above code indicates that all methods in TestController can only be accessed after logging in, but the getList interface can be accessed by anonymous visitors.

  • When @SaIgnore modifies a method, it means that this method can be accessed by tourists, and when it modifies a class, it means that all interfaces in this class can be accessed by tourists.

  • @SaIgnore has the highest priority. When @SaIgnore appears together with other authentication annotations, other authentication annotations will be ignored.

  • @SaIgnore can also ignore the routing authentication in the Sa-Token interceptor, which we will talk about in the following [Routing Interception and Authentication] section.

Using the interceptor mode, annotation authentication can only be performed at the Controller layer. If you need to use annotation authentication at any level, visit the official document

Separate use of front and back (APP, applet, etc.)

1. The backend returns the token to the frontend

  1. First call StpUtil.login(id) to log in.

  1. Call StpUtil.getTokenInfo() to return the token detailed parameters of the current session.

  • This method returns an object with two key properties: tokenName and tokenValue (the name of the token and the value of the token).

  • Pass this object to the foreground and let the front-end staff save these two values ​​locally.

Code example:

// 登录接口
@RequestMapping("doLogin")
public SaResult doLogin() {
    // 第1步,先登录上 
    StpUtil.login(10001);
    // 第2步,获取 Token  相关参数 
    SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
    // 第3步,返回给前端 
    return SaResult.data(tokenInfo);
}

2. The front end submits the token to the back end

  1. Whether it is an app or a small program, the delivery methods are similar.

  1. That is, insert the token into the request header in the format: {tokenName: tokenValue}.

  1. Take the classic cross-end framework uni-app as an example:

Method 1, simple and rude

// 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);    
    }
});

Method 2, more flexible

// 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);    
    }
});

Guess you like

Origin blog.csdn.net/GuaGea/article/details/128674645