SpringBoot怎么使用Sa-Token实现登录认证

一、设计思路

对于一些登录之后才能访问的接口(例如:查询我的账号资料),我们通常的做法是增加一层接口校验:

  • 如果校验通过,则:正常返回数据。

  • 如果校验未通过,则:抛出异常,告知其需要先进行登录。

SpringBoot与Sa-Token:如何实现登录认证

那么,判断会话是否登录的依据是什么?我们先来简单分析一下登录访问流程:

  • 用户提交 name + password 参数,调用登录接口。

  • 登录成功,返回这个用户的 Token 会话凭证。

  • 用户后续的每次请求,都携带上这个 Token。

  • 服务器根据 Token 判断此会话是否登录成功。

所谓登录认证,指的就是服务器校验账号密码,为用户颁发 Token 会话凭证的过程,这个 Token 也是我们后续判断会话是否登录的关键所在。

动态图演示:

接下来,我们将介绍在 SpringBoot 中如何使用 Sa-Token 完成登录认证操作。

Sa-Token 是一个 java 权限认证框架,主要解决登录认证、权限认证、单点登录、OAuth3、微服务网关鉴权 等一系列权限相关问题。Gitee 开源地址:https://gitee.com/dromara/sa-token

首先在项目中引入 Sa-Token 依赖:

<
!-- Sa-Token 权限认证 -->

<
dependency>

<
groupId>
cn.dev33<
/groupId>

<
artifactId>
sa-token-spring-boot-starter<
/artifactId>

<
version>
1.34.0<
/version>

<
/dependency>

注:如果你使用的是 SpringBoot 3.x,只需要将 sa-token-spring-boot-starter 修改为 sa-token-spring-boot3-starter 即可。

二、登录与注销

根据以上思路,我们需要一个会话登录的函数:

// 会话登录:参数填写要登录的账号id,建议的数据类型:long | int | String, 不可以传入复杂类型,如:User、Admin 等等
StpUtil.login(Object id);

只此一句代码,便可以使会话登录成功,实际上,Sa-Token 在背后做了大量的工作,包括但不限于:

  • 检查此账号是否之前已有登录

  • 为账号生成 Token 凭证与 Session 会话

  • 通知全局侦听器,xx 账号登录成功

  • 将 Token 注入到请求上下文

  • 等等其它工作&
    hellip;
    &
    hellip;

你暂时不需要完整的了解整个登录过程,你只需要记住关键一点:Sa-Token 为这个账号创建了一个Token凭证,且通过 Cookie 上下文返回给了前端。

所以一般情况下,我们的登录接口代码,会大致类似如下:

// 会话登录接口
@RequestMapping("
doLogin"
)
public SaResult doLogin(String name, String pwd) {
// 第一步:比对前端提交的账号名称、密码
if("
zhang"
.equals(name) &
&
"
123456"
.equals(pwd)) {
// 第二步:根据账号id,进行登录
StpUtil.login(10001);

return SaResult.ok("
登录成功"
);

}
return SaResult.error("
登录失败"
);

}

如果你对以上代码阅读没有压力,你可能会注意到略显奇怪的一点:此处仅仅做了会话登录,但并没有主动向前端返回 Token 信息。是因为不需要吗?严格来讲是需要的,只不过 StpUtil.login(id) 方法利用了 Cookie 自动注入的特性,省略了你手写返回 Token 的代码。

如果你对 Cookie 功能还不太了解,也不用担心,我们会在之后的 [ 前后端分离 ] 章节中详细的阐述 Cookie 功能,现在你只需要了解最基本的两点:

  • Cookie 可以从后端控制往浏览器中写入 Token 值。

  • Cookie 会在前端每次发起请求时自动提交 Token 值。

因此,在 Cookie 功能的加持下,我们可以仅靠 StpUtil.login(id) 一句代码就完成登录认证。

除了登录方法,我们还需要:

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


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


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

异常 NotLoginException 代表当前会话暂未登录,可能的原因有很多:前端没有提交 Token、前端提交的 Token 是无效的、前端提交的 Token 已经过期 &
hellip;
&
hellip;
等等。

Sa-Token 未登录场景值参照表:

场景值对应常量含义说明-1NotLoginException.NOT_TOKEN未能从请求中读取到 Token-2NotLoginException.INVALID_TOKEN已读取到 Token,但是 Token无效-3NotLoginException.TOKEN_TIMEOUT已读取到 Token,但是 Token已经过期-4NotLoginException.BE_REPLACED已读取到 Token,但是 Token 已被顶下线-5NotLoginException.KICK_OUT已读取到 Token,但是 Token 已被踢下线

那么,如何获取场景值呢?废话少说直接上代码:

// 全局异常拦截(拦截项目中的NotLoginException异常)
@ExceptionHandler(NotLoginException.class)
public SaResult handlerNotLoginException(NotLoginException nle)
throws Exception {

// 打印堆栈,以供调试
nle.printStackTrace();


// 判断场景值,定制化异常信息
String message = "
"
;

if(nle.getType().equals(NotLoginException.NOT_TOKEN)) {
message = "
未提供token"
;

}
else if(nle.getType().equals(NotLoginException.INVALID_TOKEN)) {
message = "
token无效"
;

}
else if(nle.getType().equals(NotLoginException.TOKEN_TIMEOUT)) {
message = "
token已过期"
;

}
else if(nle.getType().equals(NotLoginException.BE_REPLACED)) {
message = "
token已被顶下线"
;

}
else if(nle.getType().equals(NotLoginException.KICK_OUT)) {
message = "
token已被踢下线"
;

}
else {
message = "
当前会话未登录"
;

}

// 返回给前端
return SaResult.error(message);

}

注意:以上代码并非处理逻辑的最佳方式,只为以最简单的代码演示出场景值的获取与应用,大家可以根据自己的项目需求来定制化处理

三、会话查询// 获取当前会话账号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 查询// 获取当前会话的token值
StpUtil.getTokenValue();


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


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


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


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

TokenInfo 是 Token 信息 Model,用来描述一个 Token 的常用参数:

{
"
tokenName"
: "
satoken"
, // token名称
"
tokenValue"
: "
e67b99f1-3d7a-4a8d-bb2f-e888a0805633"
, // token值
"
isLogin"
: true, // 此token是否已经登录
"
loginId"
: "
10001"
, // 此token对应的LoginId,未登录时为null
"
loginType"
: "
login"
, // 账号类型标识
"
tokenTimeout"
: 2591977, // token剩余有效期 (单位: 秒)
"
sessionTimeout"
: 2591977, // User-Session剩余有效时间 (单位: 秒)
"
tokenSessionTimeout"
: -2, // Token-Session剩余有效时间 (单位: 秒) (-2表示系统中不存在这个缓存)
"
tokenActivityTimeout"
: -1, // token剩余无操作有效时间 (单位: 秒)
"
loginDevice"
: "
default-device"
// 登录设备类型
} 五、来个小测试,加深一下理解

新建 LoginAuthController,复制以下代码

package com.pj.cases.use;


import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;


import cn.dev33.satoken.stp.SaTokenInfo;

import cn.dev33.satoken.stp.StpUtil;

import cn.dev33.satoken.util.SaResult;


/**
* Sa-Token 登录认证示例
*
* @author kong
* @since 2022-10-13
*/
@RestController
@RequestMapping("
/acc/"
)
public class LoginAuthController {

// 会话登录接口 ---- http://localhost:8081/acc/doLogin?name=zhang&
pwd=123456
@RequestMapping("
doLogin"
)
public SaResult doLogin(String name, String pwd) {

// 第一步:比对前端提交的 账号名称 &
密码 是否正确,比对成功后开始登录
// 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对
if("
zhang"
.equals(name) &
&
"
123456"
.equals(pwd)) {

// 第二步:根据账号id,进行登录
// 此处填入的参数应该保持用户表唯一,比如用户id,不可以直接填入整个 User 对象
StpUtil.login(10001);


// SaResult 是 Sa-Token 中对返回结果的简单封装,下面的示例将不再赘述
return SaResult.ok("
登录成功"
);

}

return SaResult.error("
登录失败"
);

}

// 查询当前登录状态 ---- http://localhost:8081/acc/isLogin
@RequestMapping("
isLogin"
)
public SaResult isLogin() {
// StpUtil.isLogin() 查询当前客户端是否登录,返回 true 或 false
boolean isLogin = StpUtil.isLogin();

return SaResult.ok("
当前客户端是否登录:"
+ isLogin);

}

// 校验当前登录状态 ---- http://localhost:8081/acc/checkLogin
@RequestMapping("
checkLogin"
)
public SaResult checkLogin() {
// 检验当前会话是否已经登录, 如果未登录,则抛出异常:`NotLoginException`
StpUtil.checkLogin();


// 抛出异常后,代码将走入全局异常处理(GlobalException.java),如果没有抛出异常,则代表通过了登录校验,返回下面信息
return SaResult.ok("
校验登录成功,这行字符串是只有登录后才会返回的信息"
);

}

// 获取当前登录的账号是谁 ---- http://localhost:8081/acc/getLoginId
@RequestMapping("
getLoginId"
)
public SaResult getLoginId() {
// 需要注意的是,StpUtil.getLoginId() 自带登录校验效果
// 也就是说如果在未登录的情况下调用这句代码,框架就会抛出 `NotLoginException` 异常,效果和 StpUtil.checkLogin() 是一样的
Object userId = StpUtil.getLoginId();

System.out.println("
当前登录的账号id是:"
+ userId);


// 如果不希望 StpUtil.getLoginId() 触发登录校验效果,可以填入一个默认值
// 如果会话未登录,则返回这个默认值,如果会话已登录,将正常返回登录的账号id
Object userId2 = StpUtil.getLoginId(0);

System.out.println("
当前登录的账号id是:"
+ userId2);


// 或者使其在未登录的时候返回 null
Object userId3 = StpUtil.getLoginIdDefaultNull();

System.out.println("
当前登录的账号id是:"
+ userId3);


// 类型转换:
// StpUtil.getLoginId() 返回的是 Object 类型,你可以使用以下方法指定其返回的类型
int userId4 = StpUtil.getLoginIdAsInt();
// 将返回值转换为 int 类型
long userId5 = StpUtil.getLoginIdAsLong();
// 将返回值转换为 long 类型
String userId6 = StpUtil.getLoginIdAsString();
// 将返回值转换为 String 类型

// 疑问:数据基本类型不是有八个吗,为什么只封装以上三种类型的转换?
// 因为大多数项目都是拿 int、long 或 String 声明 UserId 的类型的,实在没见过哪个项目用 double、float、boolean 之类来声明 UserId
System.out.println("
当前登录的账号id是:"
+ userId4 + "
--- "
+ userId5 + "
--- "
+ userId6);


// 返回给前端
return SaResult.ok("
当前客户端登录的账号id是:"
+ userId);

}

// 查询 Token 信息 ---- http://localhost:8081/acc/tokenInfo
@RequestMapping("
tokenInfo"
)
public SaResult tokenInfo() {
// TokenName 是 Token 名称的意思,此值也决定了前端提交 Token 时应该使用的参数名称
String tokenName = StpUtil.getTokenName();

System.out.println("
前端提交 Token 时应该使用的参数名称:"
+ tokenName);


// 使用 StpUtil.getTokenValue() 获取前端提交的 Token 值
// 框架默认前端可以从以下三个途径中提交 Token:
// Cookie (浏览器自动提交)
// Header头 (代码手动提交)
// Query 参数 (代码手动提交) 例如: /user/getInfo?satoken=xxxx-xxxx-xxxx-xxxx
// 读取顺序为: Query 参数 -->
Header头 -- >
Cookie
// 以上三个地方都读取不到 Token 信息的话,则视为前端没有提交 Token
String tokenValue = StpUtil.getTokenValue();

System.out.println("
前端提交的Token值为:"
+ tokenValue);


// TokenInfo 包含了此 Token 的大多数信息
SaTokenInfo info = StpUtil.getTokenInfo();

System.out.println("
Token 名称:"
+ info.getTokenName());

System.out.println("
Token 值:"
+ info.getTokenValue());

System.out.println("
当前是否登录:"
+ info.getIsLogin());

System.out.println("
当前登录的账号id:"
+ info.getLoginId());

System.out.println("
当前登录账号的类型:"
+ info.getLoginType());

System.out.println("
当前登录客户端的设备类型:"
+ info.getLoginDevice());

System.out.println("
当前 Token 的剩余有效期:"
+ info.getTokenTimeout());
// 单位:秒,-1代表永久有效,-2代表值不存在
System.out.println("
当前 Token 的剩余临时有效期:"
+ info.getTokenActivityTimeout());
// 单位:秒,-1代表永久有效,-2代表值不存在
System.out.println("
当前 User-Session 的剩余有效期"
+ info.getSessionTimeout());
// 单位:秒,-1代表永久有效,-2代表值不存在
System.out.println("
当前 Token-Session 的剩余有效期"
+ info.getTokenSessionTimeout());
// 单位:秒,-1代表永久有效,-2代表值不存在

// 返回给前端
return SaResult.data(StpUtil.getTokenInfo());

}

// 会话注销 ---- http://localhost:8081/acc/logout
@RequestMapping("
logout"
)
public SaResult logout() {
// 退出登录会清除三个地方的数据:
// 1、Redis中保存的 Token 信息
// 2、当前请求上下文中保存的 Token 信息
// 3、Cookie 中保存的 Token 信息(如果未使用Cookie模式则不会清除)
StpUtil.logout();


// StpUtil.logout() 在未登录时也是可以调用成功的,
// 也就是说,无论客户端有没有登录,执行完 StpUtil.logout() 后,都会处于未登录状态
System.out.println("
当前是否处于登录状态:"
+ StpUtil.isLogin());


// 返回给前端
return SaResult.ok("
退出登录成功"
);

}

}

SpringBoot作为目前最受欢迎的Java Web开发框架之一,其强大的模块化设计与易用性备受开发者的青睐。而在开发基于SpringBoot的应用时,如何实现高效的用户登录认证是至关重要的一个环节。本文将介绍如何使用Sa-Token这一优秀的身份认证扩展库,来实现快速、高效、安全的登录认证。
1. Sa-Token 简介
Sa-Token是一个轻量级身份认证框架,可无缝集成到Java Web项目中,支持多种认证方式(如Session、Token、OAuth2、JWT等),提供了强大的身份管理、权限管理、会话管理等功能。其与常见开发框架(如SpringBoot、Spring MVC)集成非常友好。
2. 引入 Sa-Token 依赖
使用Sa-Token前,需要在Maven或Gradle中引入相关依赖。对于Maven,可以在pom.xml文件中添加以下代码:
```

cn.dev33.satoken
sa-token
1.20.1

```
3. Sa-Token 配置
在SpringBoot项目中,可通过编写配置类来完成Sa-Token的初始化。示例代码如下:
```
@Configuration
public class SaTokenConfig {

@Bean
public SaTokenConfigure saTokenConfigure(){
SaTokenConfigure config = new SaTokenConfigure();
// 配置密码管理器
config.setPasswordEncoder(new MyPasswordEncoder());
// 配置登录验证器
config.setLoginResolver(new MyLoginResolver());
// 其他配置项...
return config;
}
}
```
需要注意的是,在配置类中可以根据实际需求设置各种参数。例如,可以自定义密码管理器、登录验证器、权限认证器等,以实现更加个性化的认证机制。
4. 用户信息管理
在Sa-Token中,可以使用如下方式来管理已登录用户的信息:
```
// 在Controller或Service中获取当前登录用户的ID
Integer userId = SaHolder.getSaToken().getUserId();

// 在Controller或Service中获取指定token的用户ID
Integer userId2 = SaHolder.getSaToken(token).getUserId();

// 设置一些自定义的用户信息,如昵称、头像等
SaHolder.put(\"nickname\", \"张三\");
```
5. 登录认证
Sa-Token提供了多种不同的登录方式,以适应不同场景的需求。例如:
- 简单的账号密码登录:使用Sa-Token的“账号密码登录接口”即可。
- 手机号登录:通过自定义登录接口实现。
- 第三方登录:使用Sa-Token的OAuth2扩展库即可。
在登录成功后,可以在会话管理器中使用SaHolder.saveToken(token)方法来创建并保存登录态token。然后,将返回包含token的响应给客户端,以便客户端在需要访问需要登录权限的资源时,提交该token进行身份认证。
6. 权限管理
Sa-Token也提供了灵活易用的权限管理功能。例如:
```
// 判断是否具有某个权限
SaHolder.hasPermission(\"user:add\");

// 校验是否具有某个角色
SaHolder.checkRole(\"admin\");

// 根据路由地址判断是否无需认证
SaRouter.matchPath(\"/login/**\");
```
通过上述方式,可以简单而又高效地实现权限管理的功能。
7. 常见问题解答
在使用Sa-Token时可能会遇到一些常见问题,例如同一账号的多次登录会崩溃,登录态失效时间不好配置等。对此,建议开发者查看官方文档,了解更多Sa-Token的使用技巧和注意事项。
总结
本文简要介绍了使用Sa-Token在SpringBoot中实现快速、高效、安全的登录认证机制的方法。开发者可以根据自己的实际项目需求,调整和完善本示例代码,以实现更加个性化的认证机制。最后建议,开发者在使用Sa-Token时遵循最佳实践,保证应用程序的安全性与稳定性。