內置 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 字符串。
- 返回值:
true如果驗證成功且未過期,否則返回false。
-
-
parseUserIdLong(String token):-
參數:
token: JWT 字符串。
- 返回值: 返回解析出的用户 ID。
-
2. Token 管理器 TokenManager
TokenManager 類負責管理用户的登錄狀態。它使用 ITokenStorage 接口存儲 Token,並提供登錄、註銷、驗證登錄狀態等功能。
方法説明
-
login(Object userId, String tokenValue):-
參數:
userId: 用户 ID。tokenValue: 生成的 JWT Token。
- 返回值: 無返回值,但會將
userId和tokenValue存儲在ITokenStorage中。
-
-
isLogin(String key, String token):-
參數:
key: 密鑰,用於驗證 JWT。token: JWT 字符串。
- 返回值:
true如果用户已登錄且 Token 有效,否則返回false。
-
-
logout(Object userId):-
參數:
userId: 用户 ID。
- 返回值: 無返回值,但會將該
userId的登錄狀態移除。
-
使用 Tio-boot 框架實現基於 JWT 的 Token 認證
1. 前置準備
確保您的項目中已經引入了 Tio-boot 框架,並且數據庫中有一個用户表,例如 tio_boot_admin_system_users,該表存儲了用户的基本信息,如用户名、密碼等。
CREATE TABLE tio_boot_admin_system_users (
id BIGINT NOT NULL,
username VARCHAR(30) NOT NULL,
password VARCHAR(100) NOT NULL DEFAULT '',
nickname VARCHAR(30) NOT NULL,
signature VARCHAR(200),
title VARCHAR(50),
group_name VARCHAR(50),
tags JSON,
notify_count INT DEFAULT 0,
unread_count INT DEFAULT 0,
country VARCHAR(50),
access VARCHAR(20),
geographic JSON,
address VARCHAR(200),
remark VARCHAR(500),
dept_id BIGINT,
post_ids VARCHAR(255),
email VARCHAR(50) DEFAULT '',
phone VARCHAR(11) DEFAULT '',
sex SMALLINT DEFAULT 0,
avatar VARCHAR(512) DEFAULT '',
status SMALLINT NOT NULL DEFAULT 0,
login_ip VARCHAR(50) DEFAULT '',
login_date TIMESTAMP WITHOUT TIME ZONE,
creator VARCHAR(64) DEFAULT '',
create_time TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
updater VARCHAR(64) DEFAULT '',
update_time TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted SMALLINT NOT NULL DEFAULT 0,
tenant_id BIGINT NOT NULL DEFAULT 0,
PRIMARY KEY (id),
UNIQUE (username)
);
2. 登錄與註銷接口
接下來,我們實現登錄和註銷的 API 接口。用户登錄成功後,生成 JWT Token 並存儲在 TokenManager 中。
package com.admin.handler;
import java.util.HashMap;
import java.util.Map;
import com.jfinal.kit.Kv;
import com.litongjava.jfinal.aop.Aop;
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.HttpServerResponseUtils;
import com.litongjava.tio.http.server.util.Resps;
import com.litongjava.tio.utils.json.Json;
import com.litongjava.tio.utils.jwt.JwtUtils;
import com.litongjava.tio.utils.resp.RespVo;
import com.litongjava.tio.utils.token.AuthToken;
import com.litongjava.tio.utils.token.TokenManager;
import com.admin.constants.AppConstant;
import com.admin.services.LoginService;
import com.admin.vo.LoginAccountVo;
public class ApiLoginHandler {
public HttpResponse account(HttpRequest request) {
HttpResponse httpResponse = TioRequestContext.getResponse();
HttpServerResponseUtils.enableCORS(httpResponse, new HttpCors());
String bodyString = request.getBodyString();
LoginAccountVo loginAccountVo = Json.getJson().parse(bodyString, LoginAccountVo.class);
RespVo respVo;
LoginService loginService = Aop.get(LoginService.class);
Long userId = loginService.getUserIdByUsernameAndPassword(loginAccountVo);
if (userId != null) {
long tokenTimeout = (System.currentTimeMillis() + 3600000) / 1000;
AuthToken authToken = JwtUtils.createToken(AppConstant.SECRET_KEY, new AuthToken(userId, tokenTimeout));
TokenManager.login(userId, authToken.getToken());
Kv kv = new Kv();
kv.set("token", authToken.getToken());
kv.set("tokenTimeout", tokenTimeout);
kv.set("type", loginAccountVo.getType());
kv.set("status", "ok");
respVo = RespVo.ok(kv);
} else {
Map<String, String> data = new HashMap<>(1);
data.put("status", "false");
respVo = RespVo.fail().data(data);
}
return Resps.json(httpResponse, respVo);
}
public HttpResponse outLogin(HttpRequest request) {
HttpResponse httpResponse = TioRequestContext.getResponse();
HttpServerResponseUtils.enableCORS(httpResponse, new HttpCors());
Long userIdLong = TioRequestContext.getUserIdLong();
TokenManager.logout(userIdLong);
return Resps.json(httpResponse, RespVo.ok());
}
public HttpResponse validateLogin(HttpRequest request) {
HttpResponse httpResponse = TioRequestContext.getResponse();
HttpServerResponseUtils.enableCORS(httpResponse, new HttpCors());
Long userIdLong = TioRequestContext.getUserIdLong();
boolean login = TokenManager.isLogin(userIdLong);
return Resps.json(httpResponse, RespVo.ok(login));
}
}
方法説明
-
account(HttpRequest request):-
參數:
request: 包含用户登錄信息的 HTTP 請求。
- 返回值: 返回登錄結果的
HttpResponse,包括生成的 JWT Token 和過期時間。
-
-
outLogin(HttpRequest request):-
參數:
request: 包含用户登出信息的 HTTP 請求。
- 返回值: 返回登出結果的
HttpResponse。
-
3. 登錄的業務邏輯
import org.apache.commons.codec.digest.DigestUtils;
import com.litongjava.db.activerecord.Db;
import com.admin.vo.LoginAccountVo;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class LoginService {
public Long getUserIdByUsernameAndPassword(LoginAccountVo loginAccountVo) {
String password = DigestUtils.sha256Hex(loginAccountVo.getPassword());
log.info("password:{}", password);
String sql = "select id from tio_boot_admin_system_users where username=? and password=?";
return Db.queryLong(sql, loginAccountVo.getUsername(), password);
}
}
4. 配置登錄處理器
package com.admin.config;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.jfinal.aop.annotation.AConfiguration;
import com.litongjava.jfinal.aop.annotation.AInitialization;
import com.litongjava.tio.boot.server.TioBootServer;
import com.litongjava.tio.http.server.router.RequestRoute;
import com.admin.handler.ApiLoginHandler;
import com.admin.handler.FakeAnalysisChartDataHandler;
import com.admin.handler.GeographicHandler;
import com.admin.handler.SystemHandler;
import com.admin.handler.UserHandler;
@AConfiguration
public class HttpRequestHandlerConfig {
@AInitialization
public void httpRoutes() {
RequestRoute r = TioBoot
Server.me().getRequestRoute();
UserHandler userHandler = Aop.get(UserHandler.class);
FakeAnalysisChartDataHandler fakeAnalysisChartDataHandler = Aop.get(FakeAnalysisChartDataHandler.class);
GeographicHandler geographicHandler = Aop.get(GeographicHandler.class);
SystemHandler systemHandler = Aop.get(SystemHandler.class);
ApiLoginHandler apiLoginHandler = Aop.get(ApiLoginHandler.class);
r.add("/api/login/account", apiLoginHandler::account);
r.add("/api/login/outLogin", apiLoginHandler::outLogin);
r.add("/api/login/validateLogin", apiLoginHandler::validateLogin);
}
}
5. 身份驗證攔截器
為了確保每個請求的合法性,我們需要實現一個身份驗證攔截器 AuthInterceptor。該攔截器會在請求到達控制器之前驗證 JWT Token。
package com.admin.inteceptor;
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;
import com.litongjava.tio.http.server.util.Resps;
import com.litongjava.tio.utils.hutool.StrUtil;
import com.litongjava.tio.utils.resp.RespVo;
import com.litongjava.tio.utils.token.TokenManager;
import com.admin.constants.AppConstant;
public class AuthInterceptor implements HttpRequestInterceptor {
private Object body = null;
public AuthInterceptor() {
}
public AuthInterceptor(Object body) {
this.body = body;
}
@Override
public HttpResponse doBeforeHandler(HttpRequest request, RequestLine requestLine, HttpResponse responseFromCache) {
String authorization = request.getHeader("authorization");
if (StrUtil.isBlank(authorization)) {
return fail("authorization can not be empty");
}
String[] split = authorization.split(" ");
Long userId = TokenManager.parseUserIdLong(AppConstant.SECRET_KEY, authorization);
if (userId != null) {
TioRequestContext.setUserId(userId);
return null;
} else {
if (split.length > 1) {
String idToken = split[1];
userId = TokenManager.parseUserIdLong(AppConstant.SECRET_KEY, idToken);
if (userId != null) {
TioRequestContext.setUserId(userId);
return null;
}
}
}
HttpResponse response = TioRequestContext.getResponse();
response.setStatus(HttpResponseStatus.C401);
if (body != null) {
Resps.json(response, body);
}
return response;
}
private HttpResponse fail(String msg) {
HttpResponse response = TioRequestContext.getResponse();
response.setStatus(HttpResponseStatus.C401);
if (body != null) {
RespVo respVo = RespVo.fail(msg);
respVo.setData(body);
response.setJson(respVo);
}
return response;
}
@Override
public void doAfterHandler(HttpRequest request, RequestLine requestLine, HttpResponse response, long cost) throws Exception {
}
}
方法説明
-
doBeforeHandler(HttpRequest request, RequestLine requestLine, HttpResponse responseFromCache):-
參數:
request: 當前的 HTTP 請求。requestLine: 請求行信息。responseFromCache: 緩存的響應信息。
- 返回值: 返回
HttpResponse,如果驗證通過,返回null,否則返回錯誤響應。
-
6. 攔截器配置
攔截器配置是關鍵步驟,它將攔截所有需要保護的路由,並允許特定的路由繞過驗證。
package com.admin.config;
import com.litongjava.jfinal.aop.annotation.AConfiguration;
import com.litongjava.jfinal.aop.annotation.AInitialization;
import com.litongjava.tio.boot.http.interceptor.HttpServerInterceptorModel;
import com.litongjava.tio.boot.http.interceptor.ServerInteceptorConfigure;
import com.litongjava.tio.boot.server.TioBootServer;
import com.admin.inteceptor.AuthInterceptor;
@AConfiguration
public class InterceptorConfiguration {
@AInitialization
public void config() {
AuthInterceptor authTokenInterceptor = new AuthInterceptor();
HttpServerInterceptorModel model = new HttpServerInterceptorModel();
model.setInterceptor(authTokenInterceptor);
model.addblockeUrl("/**");
model.addAlloweUrls("", "/");
model.addAlloweUrls("/register/*", "/api/login/account", "/api/login/outLogin");
ServerInteceptorConfigure serverInteceptorConfigure = new ServerInteceptorConfigure();
serverInteceptorConfigure.add(model);
TioBootServer.me().setServerInteceptorConfigure(serverInteceptorConfigure);
}
}
7. 登錄請求示例
curl 'http://localhost:10004/api/login/account' \
-H 'Accept: application/json, text/plain, */*' \
--data-raw '{"username":"admin","password":"admin","autoLogin":true,"type":"account"}'
curl 'http://localhost:10004/api/validateLogin' \
-H 'Accept: application/json, text/plain, */*' \
-H 'Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MjQ5OTQ0NDIsInVzZXJJZCI6MX0=.9We8596ZexVSH4Su8cx4zpgD9XzEQLKM42wpWrSr8j0'
curl 'http://localhost:10004/sejie-download-admin/api/login/outLogin' \
-X 'POST' \
-H 'Accept: application/json, text/plain, */*' \
-H 'Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MjQ5OTQ0NDIsInVzZXJJZCI6MX0=.9We8596ZexVSH4Su8cx4zpgD9XzEQLKM42wpWrSr8j0'
結論
通過以上步驟,我們成功在 Tio-boot 框架中實現了基於 JWT 的用户認證。使用 JwtUtils 類生成和驗證 Token,通過 TokenManager 管理用户的登錄狀態,並配置了攔截器來確保請求的安全性。這個方法能夠有效地提高系統的安全性,並且易於擴展和維護。