使用内置 TokenManager 实现登录
在使用 Tio-boot 框架开发 Web 应用程序时,安全性始终是我们必须重点关注的部分。本文将详细介绍如何使用 Tio-boot 框架实现基于 JWT 的 Token 认证。我们将探讨如何配置拦截器、生成和验证 JWT Token,并管理用户的登录状态。本文依托 Tio-boot 工具类库 Tio-utils 内置的 JwtUtils
和 TokenManager
两个核心组件,完整展示登录、注销、验证登录状态的实现方法,并对每部分代码做详细解析。
1. JWT 工具类 JwtUtils
JwtUtils
类主要用于生成和验证 JWT Token,同时能够从 Token 中提取用户信息。其核心功能包括:
创建 JWT Token
通过用户 ID 和密钥生成 Token,并指定有效期。验证 JWT Token
检查 Token 的合法性以及是否过期。解析用户 ID
从 Token 的 payload 部分解析出用户 ID。
方法说明
createToken(String key, Map<String, Object> payloadMap)
参数:key
:用于签名 JWT 的密钥。payloadMap
:包含用户信息和过期时间等数据的 Map。
返回值:生成的 JWT 字符串。
verify(String key, String token)
参数:key
:用于验证 JWT 的密钥。token
:待验证的 JWT 字符串。
返回值:如果验证成功且 Token 未过期,返回true
,否则返回false
。
parseUserIdLong(String token)
参数:token
:JWT 字符串。
返回值:解析出的用户 ID。
2. Token 管理器 TokenManager
TokenManager
类负责管理用户的登录状态。其内部通过实现 ITokenStorage
接口存储 Token,提供了以下主要功能:
登录操作
方法login(Object userId, String tokenValue)
接收用户 ID 和生成的 JWT Token,然后将二者存储到ITokenStorage
中。登录状态验证
方法isLogin(String key, String token)
验证给定的 Token 是否有效,若用户已登录且 Token 有效则返回true
。注销登录
方法logout(Object userId)
用于移除指定用户的登录状态。
3. 登录与注销接口的实现
在下面的代码中,我们实现了登录和注销的 API 接口。用户登录成功后,系统会生成 JWT Token 并通过 TokenManager
存储下来。
package com.litongjava.tio.boot.admin.handler;
import java.util.HashMap;
import java.util.Map;
import com.jfinal.kit.Kv;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.model.body.RespBodyVo;
import com.litongjava.model.token.AuthToken;
import com.litongjava.tio.boot.admin.costants.AppConstant;
import com.litongjava.tio.boot.admin.services.LoginService;
import com.litongjava.tio.boot.admin.vo.LoginAccountVo;
import com.litongjava.tio.boot.http.TioRequestContext;
import com.litongjava.tio.http.common.HttpRequest;
import com.litongjava.tio.http.common.HttpResponse;
import com.litongjava.tio.http.server.model.HttpCors;
import com.litongjava.tio.http.server.util.CORSUtils;
import com.litongjava.tio.http.server.util.Resps;
import com.litongjava.tio.utils.environment.EnvUtils;
import com.litongjava.tio.utils.json.Json;
import com.litongjava.tio.utils.jwt.JwtUtils;
import com.litongjava.tio.utils.token.TokenManager;
public class ApiLoginHandler {
public HttpResponse account(HttpRequest request) {
HttpResponse httpResponse = TioRequestContext.getResponse();
CORSUtils.enableCORS(httpResponse, new HttpCors());
String bodyString = request.getBodyString();
LoginAccountVo loginAccountVo = Json.getJson().parse(bodyString, LoginAccountVo.class);
RespBodyVo respVo;
// 1.登录
LoginService loginService = Aop.get(LoginService.class);
Long userId = loginService.getUserIdByUsernameAndPassword(loginAccountVo);
if (userId != null) {
// 2. 设置过期时间payload 7天
long tokenTimeout = (System.currentTimeMillis() + 3600000 * 24 * 7) / 1000;
// 3.创建token
String keyValue = EnvUtils.getStr(AppConstant.ADMIN_SECRET_KEY);
AuthToken authToken = JwtUtils.createToken(keyValue, new AuthToken(userId, tokenTimeout));
TokenManager.login(userId, authToken.getToken());
Kv kv = new Kv();
kv.set("userId", userId);
kv.set("token", authToken.getToken());
kv.set("tokenTimeout", tokenTimeout);
kv.set("type", loginAccountVo.getType());
kv.set("status", "ok");
respVo = RespBodyVo.ok(kv);
} else {
Map<String, String> data = new HashMap<>(1);
data.put("status", "false");
respVo = RespBodyVo.fail().data(data);
}
return Resps.json(httpResponse, respVo);
}
public HttpResponse outLogin(HttpRequest request) {
HttpResponse httpResponse = TioRequestContext.getResponse();
CORSUtils.enableCORS(httpResponse, new HttpCors());
Long userIdLong = TioRequestContext.getUserIdLong();
//remove
TokenManager.logout(userIdLong);
return Resps.json(httpResponse, RespBodyVo.ok());
}
/**
* 因为拦截器已经经过了验证,判断token是否存在即可
* @param request
* @return
*/
public HttpResponse validateLogin(HttpRequest request) {
HttpResponse httpResponse = TioRequestContext.getResponse();
CORSUtils.enableCORS(httpResponse, new HttpCors());
Long userIdLong = TioRequestContext.getUserIdLong();
boolean login = TokenManager.isLogin(userIdLong);
return Resps.json(httpResponse, RespBodyVo.ok(login));
}
}
代码说明
登录接口
account
- 通过
LoginService
校验用户的用户名和密码; - 设置 Token 有效时间为 7 天(通过计算当前时间加上 7 天的毫秒数后转换为秒);
- 使用
JwtUtils.createToken
根据用户 ID 及 Token 有效期生成 JWT Token; - 调用
TokenManager.login
方法存储 Token 与用户对应关系; - 返回包含用户 ID、Token、过期时间等信息的 JSON 响应。
- 通过
注销接口
outLogin
从上下文中获取用户 ID,并调用TokenManager.logout
移除该用户的登录状态,最终返回注销成功的响应。登录验证接口
validateLogin
直接从请求上下文中获取用户 ID,调用TokenManager.isLogin
检查该用户是否已登录并返回相应的结果。
4. 登录的业务逻辑
登录业务逻辑主要在 LoginService
中实现,该类负责校验用户名和密码是否匹配,并返回用户的 ID。
package com.litongjava.tio.boot.admin.services;
import org.apache.commons.codec.digest.DigestUtils;
import com.litongjava.db.activerecord.Db;
import com.litongjava.tio.boot.admin.costants.TioBootAdminTableNames;
import com.litongjava.tio.boot.admin.vo.LoginAccountVo;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class LoginService {
public Long getUserIdByUsernameAndPassword(LoginAccountVo loginAccountVo) {
// digest
String password = DigestUtils.sha256Hex(loginAccountVo.getPassword());
log.info("password:{}", password);
String sql = "select id from %s where username=? and password=?";
sql = String.format(sql, TioBootAdminTableNames.tio_boot_admin_system_users);
return Db.queryLong(sql, loginAccountVo.getUsername(), password);
}
}
代码说明
- 使用
DigestUtils.sha256Hex
对用户提交的密码进行 SHA-256 加密,确保数据库中的密码与前端提交密码加密后能正确匹配。 - 通过 SQL 查询语句,从用户表
tio_boot_admin_system_users
中查找对应的用户 ID。 - 如果查询到用户,则返回其 ID;否则返回
null
。
5. 配置登录处理器
在登录处理器配置中,我们将定义路由并将其与 ApiLoginHandler
中的方法绑定。这样可以使系统正确路由用户的登录、注销及登录状态验证请求。
package com.litongjava.tio.boot.admin.config;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.tio.boot.admin.handler.ApiLoginHandler;
import com.litongjava.tio.boot.admin.handler.FakeAnalysisChartDataHandler;
import com.litongjava.tio.boot.admin.handler.GeographicHandler;
import com.litongjava.tio.boot.admin.handler.StableDiffusionHandler;
import com.litongjava.tio.boot.admin.handler.SystemFileFirebaseHandler;
import com.litongjava.tio.boot.admin.handler.SystemFileAwsS3Handler;
import com.litongjava.tio.boot.admin.handler.SystemHandler;
import com.litongjava.tio.boot.admin.handler.UserEventHandler;
import com.litongjava.tio.boot.admin.handler.UserHandler;
import com.litongjava.tio.boot.server.TioBootServer;
import com.litongjava.tio.http.server.router.HttpRequestRouter;
public class TioAdminHandlerConfiguration {
public void config() {
HttpRequestRouter r = TioBootServer.me().getRequestRouter();
if (r == null) {
return;
}
// 创建controller
ApiLoginHandler apiLoginHandler = Aop.get(ApiLoginHandler.class);
// 添加action
r.add("/api/login/account", apiLoginHandler::account);
r.add("/api/login/outLogin", apiLoginHandler::outLogin);
r.add("/api/login/validateLogin", apiLoginHandler::validateLogin);
}
}
代码说明
- 使用
Aop.get
获取ApiLoginHandler
的实例。 - 通过
HttpRequestRouter
将登录、注销以及登录状态验证接口与对应的 URL 进行绑定,实现路由转发。
6. 验证 Token 的实现
为了实现 Token 的验证,我们定义了 TokenPredicate
类,用于判断 Token 是否有效,并提取 Token 中的用户 ID。
package com.litongjava.tio.boot.admin.services;
import java.util.function.Predicate;
import com.litongjava.tio.boot.admin.costants.AppConstant;
import com.litongjava.tio.boot.http.TioRequestContext;
import com.litongjava.tio.utils.environment.EnvUtils;
import com.litongjava.tio.utils.jwt.JwtUtils;
public class TokenPredicate implements Predicate<String> {
@Override
public boolean test(String token) {
String adminToken = EnvUtils.get(AppConstant.ADMIN_TOKEN);
//admin token
boolean equals = adminToken.equals(token);
if (equals) {
TioRequestContext.setUserId(0L);
return true;
}
// user token
String key = EnvUtils.getStr(AppConstant.ADMIN_SECRET_KEY);
boolean verify = JwtUtils.verify(key, token);
if (verify) {
Long userId = JwtUtils.parseUserIdLong(token);
TioRequestContext.setUserId(userId);
return true;
}
return false;
}
public Long parseUserIdLong(String token) {
String key = EnvUtils.getStr(AppConstant.ADMIN_SECRET_KEY);
boolean verify = JwtUtils.verify(key, token);
if (verify) {
return JwtUtils.parseUserIdLong(token);
}
return null;
}
}
代码说明
该类实现了 Java 的
Predicate
接口,通过test
方法对传入的 Token 进行验证:- 首先判断 Token 是否与管理员 Token 相同,若相同则设置用户 ID 为 0 表示管理员。
- 否则,利用
JwtUtils.verify
验证 Token 的合法性,若验证成功,再从 Token 中解析出用户 ID,并将其存入上下文中供后续使用。
另外还提供了
parseUserIdLong
方法,用于直接返回解析出的用户 ID。
7. 身份验证拦截器
为了确保每个请求的合法性,我们需要在请求到达控制器之前对 JWT Token 进行验证。下面的代码展示了身份验证拦截器 AuthTokenInterceptor
的实现。
package com.litongjava.tio.boot.token;
import java.util.function.Predicate;
import com.litongjava.tio.boot.http.TioRequestContext;
import com.litongjava.tio.http.common.HttpRequest;
import com.litongjava.tio.http.common.HttpResponse;
import com.litongjava.tio.http.common.HttpResponseStatus;
import com.litongjava.tio.http.common.RequestLine;
import com.litongjava.tio.http.server.intf.HttpRequestInterceptor;
public class AuthTokenInterceptor implements HttpRequestInterceptor {
private Object body = null;
private Predicate<String> validateTokenLogic;
public AuthTokenInterceptor() {
}
public AuthTokenInterceptor(Object body) {
this.body = body;
}
public AuthTokenInterceptor(Predicate<String> validateTokenLogic) {
this.validateTokenLogic = validateTokenLogic;
}
public AuthTokenInterceptor(Object body, Predicate<String> validateTokenLogic) {
this.body = body;
this.validateTokenLogic = validateTokenLogic;
}
@Override
public HttpResponse doBeforeHandler(HttpRequest request, RequestLine requestLine, HttpResponse responseFromCache) {
if (validateTokenLogic != null) {
String token = request.getHeader("token");
if (token != null) {
if (validateTokenLogic.test(token)) {
return null;
}
}
String authorization = request.getHeader("authorization");
if (authorization != null) {
String[] split = authorization.split(" ");
if (split.length > 1) {
if (validateTokenLogic.test(split[1])) {
return null;
}
} else {
if (validateTokenLogic.test(split[0])) {
return null;
}
}
}
}
HttpResponse response = TioRequestContext.getResponse();
response.setStatus(HttpResponseStatus.C401);
if (body != null) {
response.setJson(body);
}
return response;
}
@Override
public void doAfterHandler(HttpRequest request, RequestLine requestLine, HttpResponse response, long cost) throws Exception {
}
}
代码说明
Token 验证逻辑
在doBeforeHandler
方法中,拦截器首先尝试从请求头中获取token
或authorization
字段。- 如果存在,则调用传入的
validateTokenLogic
(通常是TokenPredicate
实例)进行验证; - 验证通过后,允许请求继续执行(返回
null
); - 若验证失败,则返回状态码 401(未授权)的响应,并可附带自定义响应体。
- 如果存在,则调用传入的
后置处理
doAfterHandler
方法在当前示例中没有具体实现,但可用于记录日志或处理后续逻辑。
8. 拦截器配置
在拦截器配置中,我们将拦截所有需要保护的路由,并允许部分路由绕过 Token 验证。如下代码展示了拦截器的配置过程。
package com.litongjava.tio.boot.admin.config;
import java.util.function.Predicate;
import com.litongjava.tio.boot.admin.services.TokenPredicate;
import com.litongjava.tio.boot.http.interceptor.HttpInteceptorConfigure;
import com.litongjava.tio.boot.http.interceptor.HttpInterceptorModel;
import com.litongjava.tio.boot.server.TioBootServer;
import com.litongjava.tio.boot.token.AuthTokenInterceptor;
public class TioAdminInterceptorConfiguration {
private String[] permitUrls;
private boolean alloweStaticFile;
private Predicate<String> validateTokenLogic;
public TioAdminInterceptorConfiguration() {
}
public TioAdminInterceptorConfiguration(String[] permitUrls) {
this.permitUrls = permitUrls;
}
public TioAdminInterceptorConfiguration(String[] permitUrls, Predicate<String> validateTokenLogic) {
this.permitUrls = permitUrls;
this.validateTokenLogic = validateTokenLogic;
}
public TioAdminInterceptorConfiguration(String[] permitUrls, boolean b) {
this.permitUrls = permitUrls;
this.alloweStaticFile = b;
}
public void config() {
// 创建 SaToken 拦截器实例
if (validateTokenLogic == null) {
validateTokenLogic = new TokenPredicate();
}
AuthTokenInterceptor authTokenInterceptor = new AuthTokenInterceptor(validateTokenLogic);
HttpInterceptorModel model = new HttpInterceptorModel();
model.setInterceptor(authTokenInterceptor);
model.addBlockUrl("/**"); // 拦截所有路由
// index
model.addAllowUrls("", "/");
//user
model.addAllowUrls("/register/*", "/api/login/account", "/api/login/outLogin"); // 设置例外路由
model.addAllowUrls("/api/event/add");
String[] previewUrls = { "/table/json/tio_boot_admin_system_article/get/*",
//
"/table/json/tio_boot_admin_system_docx/get/*", "/table/json/tio_boot_admin_system_pdf/get/*" };
model.addAllowUrls(previewUrls);
if (permitUrls != null) {
model.addAllowUrls(permitUrls);
}
model.setAlloweStaticFile(alloweStaticFile);
HttpInteceptorConfigure inteceptorConfigure = new HttpInteceptorConfigure();
inteceptorConfigure.add(model);
// 将拦截器配置添加到 Tio 服务器
TioBootServer.me().setHttpInteceptorConfigure(inteceptorConfigure);
}
}
代码说明
拦截规则配置
- 通过
model.addBlockUrl("/**")
将所有路由设置为需要拦截; - 通过
model.addAllowUrls
添加不需要拦截的例外路由,如登录、注册及部分预览接口。
- 通过
Token 验证逻辑
如果外部没有传入自定义的validateTokenLogic
,则默认使用TokenPredicate
作为验证逻辑。静态文件处理
通过model.setAlloweStaticFile
参数可配置是否允许访问静态文件。注入到服务器
最后,将构建好的拦截器配置对象注入到 TioBoot 服务器中,确保所有 HTTP 请求经过拦截器检查。
9. 登录请求示例
使用下面的 curl 命令即可测试登录、验证登录状态和注销接口。
登录接口
curl 'http://localhost:8100/api/login/account' \
-H 'Accept: application/json, text/plain, */*' \
--data-raw '{"username":"admin","password":"admin","autoLogin":true,"type":"account"}'
响应示例:
{
"data": {
"tokenTimeout": "1741564881",
"type": "account",
"userId": "1",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NDE1NjQ4ODEsInVzZXJJZCI6MX0=.2P_dua9NdhaHUdb_yJxZWUDSk4gI8XB0lc5LYqFfEBs",
"status": "ok"
},
"error": null,
"ok": true,
"msg": null,
"code": 1
}
验证登录状态
curl 'http://localhost:8100/api/validateLogin' \
-H 'Accept: application/json, text/plain, */*' \
-H 'Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NDE1NjQ4ODEsInVzZXJJZCI6MX0=.2P_dua9NdhaHUdb_yJxZWUDSk4gI8XB0lc5LYqFfEBs'
注销接口
curl 'http://localhost:8100/sejie-download-admin/api/login/outLogin' \
-X 'POST' \
-H 'Accept: application/json, text/plain, */*' \
-H 'Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NDE1NjQ4ODEsInVzZXJJZCI6MX0=.2P_dua9NdhaHUdb_yJxZWUDSk4gI8XB0lc5LYqFfEBs'
10. 结论
通过以上步骤,我们成功实现了在 Tio-boot 框架中基于 JWT 的用户认证。整个过程主要包括以下几个关键步骤:
- 使用
JwtUtils
类生成和验证 JWT Token。 - 利用
TokenManager
管理用户的登录状态。 - 通过
TokenPredicate
实现 Token 验证逻辑,并在拦截器中进行检查。 - 配置 HTTP 请求拦截器,确保每个受保护的请求均经过身份认证检查。
这种实现方式不仅提高了系统的安全性,而且由于模块化设计,便于后续扩展和维护。所有原始代码均在文档中完整展示,开发者可以直接参考或根据实际需求进行调整。
以上便是基于 Tio-boot 框架实现 JWT Token 认证的完整实现文档及代码解释。