基于SpringBoot實(shí)現(xiàn)單賬號登錄控制功能
在很多業(yè)務(wù)場景中,我們需要控制單個(gè)賬號的登錄數(shù)量。本文介紹一種輕量級實(shí)現(xiàn)方案,靈活支持單賬號單登錄和多登錄兩種模式。
付費(fèi)系統(tǒng)場景:某在線教育平臺(tái)銷售VIP會(huì)員賬號,如果不限制登錄數(shù)量,一個(gè)賬號可能被多個(gè)用戶共享,導(dǎo)致收入損失。平臺(tái)需要確保一個(gè)付費(fèi)賬號只能有一個(gè)用戶同時(shí)在線。
企業(yè)辦公系統(tǒng):為了保證數(shù)據(jù)安全,企業(yè)OA系統(tǒng)通常要求員工賬號只能在指定設(shè)備上登錄。當(dāng)員工在新設(shè)備登錄時(shí),需要自動(dòng)踢出其他設(shè)備上的會(huì)話,防止賬號被濫用。
一、傳統(tǒng)方案的痛點(diǎn)
傳統(tǒng)的賬號登錄控制方案存在各種問題:
Session方案的局限:使用HttpSession實(shí)現(xiàn)雖然簡單,但在分布式環(huán)境下會(huì)面臨Session共享的難題。引入Spring Session雖然可以解決問題,但增加了系統(tǒng)的復(fù)雜度和依賴,對于簡單的需求來說有些"殺雞用牛刀"。
Spring Security的復(fù)雜性:Spring Security提供了強(qiáng)大的安全框架,但對于僅僅需要登錄控制的需求來說,學(xué)習(xí)成本過高,配置復(fù)雜,而且引入了很多不必要的功能。
數(shù)據(jù)庫存儲(chǔ)的性能問題:將登錄狀態(tài)存儲(chǔ)在數(shù)據(jù)庫中會(huì)導(dǎo)致每次請求都需要查詢數(shù)據(jù)庫,在高并發(fā)場景下會(huì)成為性能瓶頸。雖然可以通過緩存優(yōu)化,但又增加了系統(tǒng)復(fù)雜度。
前端狀態(tài)管理的麻煩:傳統(tǒng)的頁面跳轉(zhuǎn)方式已經(jīng)不適應(yīng)現(xiàn)代前端框架的需求。前后端分離架構(gòu)需要統(tǒng)一的API接口和狀態(tài)管理機(jī)制。
二、方案概述
2.1 核心思路
本文提出的方案基于以下幾個(gè)核心理念:
Token認(rèn)證:使用自定義Token替代傳統(tǒng)的Session機(jī)制。Token是無狀態(tài)的,可以在分布式環(huán)境下輕松擴(kuò)展,同時(shí)避免了Session共享的復(fù)雜性問題。
攔截器驗(yàn)證:通過Spring MVC的攔截器機(jī)制統(tǒng)一處理登錄驗(yàn)證。這種方式的優(yōu)點(diǎn)是不需要修改業(yè)務(wù)代碼,通過配置即可實(shí)現(xiàn)對所有請求的攔截。
接口抽象:定義清晰的SessionManager接口,將存儲(chǔ)邏輯與業(yè)務(wù)邏輯分離。這樣既可以使用Map實(shí)現(xiàn),也可以輕松切換到Redis,為系統(tǒng)擴(kuò)展提供可能。
模式切換:通過簡單的配置項(xiàng)即可在"單登錄"和"多登錄"模式之間切換,滿足不同業(yè)務(wù)場景的需求。
2.2 技術(shù)選型
核心依賴
僅使用Spring Boot Web Starter,不引入任何額外的安全框架或中間件。
存儲(chǔ)方案
單機(jī)部署:使用ConcurrentHashMap 分布式部署:通過接口抽象無縫切換到Redis
三、核心實(shí)現(xiàn)
3.1 會(huì)話管理接口
SessionManager接口是整個(gè)方案的核心,它定義了會(huì)話管理的所有基本操作:
public interface SessionManager {
// 登錄:生成并返回Token
String login(String username, LoginInfo loginInfo);
// 登出:使指定Token失效
void logout(String token);
// 驗(yàn)證:檢查Token是否有效
TokenInfo validateToken(String token);
// 查詢:獲取用戶的所有活躍Token
List getUserTokens(String username);
// 管理:強(qiáng)制用戶下線
void kickoutUser(String username);
}
3.2 Map實(shí)現(xiàn)方案
MapSessionManager是基于ConcurrentHashMap的具體實(shí)現(xiàn),它使用兩個(gè)Map來管理會(huì)話狀態(tài):
tokenMap:存儲(chǔ)Token到TokenInfo的映射,用于快速驗(yàn)證Token的有效性。TokenInfo包含用戶名、登錄時(shí)間、過期時(shí)間等關(guān)鍵信息。
userTokenMap:存儲(chǔ)用戶名到Token集合的映射,用于管理用戶的所有會(huì)話。這樣設(shè)計(jì)的好處是,當(dāng)需要踢出用戶時(shí),可以快速找到該用戶的所有Token并使其失效。
@Component
public class MapSessionManager implements SessionManager {
// Token -> TokenInfo 的映射,用于驗(yàn)證
private final Map tokenMap = new ConcurrentHashMap<>();
// Username -> Token集合的映射,用于管理
private final Map> userTokenMap = new ConcurrentHashMap<>();
@Override
public String login(String username, LoginInfo loginInfo) {
String token = generateToken();
TokenInfo tokenInfo = new TokenInfo(token, username, loginInfo,
System.currentTimeMillis() + properties.getTokenExpireTime() * 1000);
// 單登錄模式:先踢出舊會(huì)話
if (properties.getMode() == LoginMode.SINGLE) {
kickoutUser(username);
}
// 存儲(chǔ)新Token
tokenMap.put(token, tokenInfo);
userTokenMap.computeIfAbsent(username, k -> ConcurrentHashMap.newKeySet())
.add(token);
return token;
}
}
3.3 攔截器設(shè)計(jì)
LoginInterceptor負(fù)責(zé)攔截所有需要認(rèn)證的請求,驗(yàn)證Token的有效性。它的設(shè)計(jì)要點(diǎn)包括:
路徑排除:靜態(tài)資源、登錄接口等不需要認(rèn)證的路徑需要被排除。
Token提取:從HTTP請求頭中提取Token,支持自定義Header名稱。
驗(yàn)證邏輯:調(diào)用SessionManager驗(yàn)證Token,將驗(yàn)證結(jié)果存入請求屬性。
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
private SessionManager sessionManager;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
String token = getTokenFromRequest(request);
if (token == null) {
return handleUnauthorized(response, "請先登錄");
}
TokenInfo tokenInfo = sessionManager.validateToken(token);
if (tokenInfo == null) {
return handleUnauthorized(response, "登錄已過期");
}
// 將用戶信息存入請求上下文
request.setAttribute("username", tokenInfo.getUsername());
return true;
}
}
3.4 配置管理
LoginProperties集中管理所有可配置的參數(shù),通過Spring Boot的配置機(jī)制,可以在application.yml中靈活調(diào)整:
app:
login:
# 登錄模式控制
mode: MULTIPLE # SINGLE-單登錄, MULTIPLE-多登錄
# Token相關(guān)配置
token-expire-time: 1800 # 有效期30分鐘
token-header: Authorization # 請求頭名稱
token-prefix: TOKEN_ # Token前綴
# 維護(hù)配置
enable-auto-clean: true # 啟用自動(dòng)清理
clean-interval: 5 # 清理間隔(分鐘)
四、API接口設(shè)計(jì)
4.1 接口定義
系統(tǒng)提供了一套RESTful API接口,支持完整的用戶會(huì)話管理功能:
| 接口路徑 | 請求方法 | 功能說明 | 請求參數(shù) | 響應(yīng)數(shù)據(jù) |
|---|---|---|---|---|
| /api/auth/login | POST | 用戶登錄 | username, password | Token信息 |
| /api/auth/logout | POST | 用戶退出 | 無 | 操作結(jié)果 |
| /api/auth/current | GET | 當(dāng)前用戶 | 無 | 用戶信息 |
| /api/auth/online | GET | 在線列表 | 無 | 用戶名列表 |
| /api/auth/tokens | GET | 用戶Tokens | username | Token列表 |
| /api/auth/kickout | POST | 踢出用戶 | username | 操作結(jié)果 |
4.2 統(tǒng)一響應(yīng)格式
所有API接口都遵循統(tǒng)一的響應(yīng)格式,便于前端處理:
{
"code": 200, // 狀態(tài)碼
"message": "成功", // 提示信息
"data": {} // 業(yè)務(wù)數(shù)據(jù)
}
狀態(tài)碼規(guī)范:
- 200:請求成功
- 401:未認(rèn)證或Token失效
- 403:權(quán)限不足
- 500:服務(wù)器內(nèi)部錯(cuò)誤
4.3 登錄認(rèn)證流程
完整的登錄流程包括以下幾個(gè)步驟:
1. 用戶提交憑據(jù)
POST /api/auth/login
Content-Type: application/json
{
"username": "admin",
"password": "admin123"
}
2. 服務(wù)端驗(yàn)證并生成Token 服務(wù)端驗(yàn)證用戶名密碼后,生成一個(gè)唯一的Token,包含:
- Token字符串(UUID)
- 用戶信息
- 登錄時(shí)間
- 過期時(shí)間
3. 返回Token給客戶端
{
"code": 200,
"message": "登錄成功",
"data": {
"token": "TOKEN_550e8400-e29b-41d4-a716-446655440000",
"username": "admin",
"expireTime": 1704067200000,
"loginMode": "MULTIPLE"
}
}
4. 客戶端存儲(chǔ)Token 根據(jù)用戶選擇,Token可以存儲(chǔ)在localStorage(長期有效)或sessionStorage(會(huì)話級別)。
5. 后續(xù)請求攜帶Token
GET /api/auth/current Authorization: TOKEN_550e8400-e29b-41d4-a716-446655440000
6. 服務(wù)端驗(yàn)證Token 攔截器自動(dòng)驗(yàn)證Token的有效性,包括:
- Token是否存在
- Token是否過期
- Token是否被踢出
4.4 錯(cuò)誤處理機(jī)制
當(dāng)認(rèn)證失敗時(shí),系統(tǒng)會(huì)返回明確的錯(cuò)誤信息:
{
"code": 401,
"message": "Token已過期,請重新登錄",
"data": null
}
前端收到401響應(yīng)后,會(huì)自動(dòng)清除本地存儲(chǔ)的Token,并跳轉(zhuǎn)到登錄頁面。
4.5 管理功能接口
管理員可以通過專門的接口管理系統(tǒng)用戶:
獲取在線用戶:返回當(dāng)前所有在線用戶的用戶名列表,可用于監(jiān)控和統(tǒng)計(jì)。
踢出指定用戶:強(qiáng)制指定用戶下線,清除其所有Token,常用于處理異常情況。
查看用戶會(huì)話:獲取指定用戶的所有活躍Token,了解用戶的登錄狀態(tài)。
五、前端實(shí)現(xiàn)
5.1 核心實(shí)現(xiàn)
api.js:負(fù)責(zé)所有API請求的封裝,包括Token管理、請求攔截、錯(cuò)誤處理等。
頁面模塊:login.html、index.html、admin.html各自負(fù)責(zé)獨(dú)立的功能,通過import導(dǎo)入api模塊。
狀態(tài)管理:使用localStorage和sessionStorage管理Token狀態(tài),支持"記住我"功能。
5.2 Token管理機(jī)制
Token管理是前端的核心功能,實(shí)現(xiàn)了完整的生命周期管理:
// Token獲取策略
export function getToken() {
// 優(yōu)先從localStorage獲?。ㄩL期有效)
return localStorage.getItem('token')
|| sessionStorage.getItem('token'); // 再從sessionStorage獲取
}
// Token存儲(chǔ)策略
export function setToken(token, remember) {
if (remember) {
localStorage.setItem('token', token); // 用戶選擇"記住我"
} else {
sessionStorage.setItem('token', token); // 僅本次會(huì)話有效
}
}
這種設(shè)計(jì)的優(yōu)勢是給了用戶選擇權(quán):隱私敏感的場景可以使用sessionStorage,確保關(guān)閉瀏覽器后自動(dòng)清除;個(gè)人設(shè)備上可以使用localStorage,提供更好的用戶體驗(yàn)。
5.3 請求攔截器
前端實(shí)現(xiàn)了一個(gè)輕量級的請求攔截器,自動(dòng)處理認(rèn)證相關(guān)的邏輯:
async function apiRequest(url, options = {}) {
// 自動(dòng)添加Token
const token = getToken();
if (token) {
options.headers = {
...options.headers,
'Authorization': token
};
}
const response = await fetch(url, options);
// 統(tǒng)一處理401響應(yīng)
if (response.status === 401) {
clearToken();
// 避免在登錄頁重復(fù)跳轉(zhuǎn)
if (location.pathname !== '/login.html') {
location.href = '/login.html';
}
}
return response.json();
}
這個(gè)攔截器實(shí)現(xiàn)了:
- 自動(dòng)攜帶Token,減少重復(fù)代碼
- 統(tǒng)一的401處理,確保用戶在Token失效時(shí)能夠及時(shí)重新登錄
六、總結(jié)
整個(gè)方案僅依賴Spring Boot Web,不需要引入任何額外的安全框架、緩存中間件或數(shù)據(jù)庫。這使得項(xiàng)目的依賴保持最小化。
通過SessionManager接口抽象,實(shí)現(xiàn)了業(yè)務(wù)邏輯與存儲(chǔ)邏輯的完全分離。當(dāng)需要從單機(jī)擴(kuò)展到分布式時(shí),只需要實(shí)現(xiàn)一個(gè)基于Redis的SessionManager即可,業(yè)務(wù)代碼無需任何修改。
到此這篇關(guān)于基于SpringBoot實(shí)現(xiàn)單賬號登錄控制功能的文章就介紹到這了,更多相關(guān)SpringBoot賬號登錄內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue+springboot前后端分離工程跨域問題解決方案解析
這篇文章主要介紹了vue+springboot前后端分離工程跨域問題解決方案解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03
如何使用SpEL表達(dá)式實(shí)現(xiàn)動(dòng)態(tài)分表查詢
這篇文章主要介紹了如何使用SpEL表達(dá)式實(shí)現(xiàn)動(dòng)態(tài)分表查詢,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12
Java 自定義Spring框架與Spring IoC相關(guān)接口分析
Spring框架是由于軟件開發(fā)的復(fù)雜性而創(chuàng)建的。Spring使用的是基本的JavaBean來完成以前只可能由EJB完成的事情。然而,Spring的用途不僅僅限于服務(wù)器端的開發(fā)2021-10-10
Java實(shí)現(xiàn)解析zip壓縮包并獲取文件內(nèi)容
這篇文章主要為大家詳細(xì)介紹了如何利用Java語言實(shí)現(xiàn)頁面上傳一個(gè)源碼壓縮包,后端將壓縮包解壓,并獲取每個(gè)文件中的內(nèi)容,感興趣的可以動(dòng)手嘗試一下2022-07-07
Java List簡介_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
Java中可變數(shù)組的原理就是不斷的創(chuàng)建新的數(shù)組,將原數(shù)組加到新的數(shù)組中,下文對Java List用法做了詳解。需要的朋友參考下吧2017-05-05
在Spring AI中實(shí)現(xiàn)函數(shù)調(diào)用的方法
在SpringAI中實(shí)現(xiàn)函數(shù)調(diào)用,通過反射機(jī)制與AOP技術(shù),靈活高效地處理業(yè)務(wù)邏輯,本文詳細(xì)介紹了基本原理、應(yīng)用場景及Java代碼示例,涵蓋直接調(diào)用、AOP增強(qiáng)和異步調(diào)用三種方式,需要的朋友可以參考下2026-05-05
java實(shí)現(xiàn)基于SMTP發(fā)送郵件的方法
這篇文章主要介紹了java實(shí)現(xiàn)基于SMTP發(fā)送郵件的方法,實(shí)例分析了java基于SMTP服務(wù)發(fā)送郵件的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07
Java?IO及BufferedReader.readline()出現(xiàn)的Bug
這篇文章主要介紹了Java?IO及BufferedReader.readline()出現(xiàn)的Bug,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12

