SAP Commerce(Hybris)開發實戰(二):登陸生成token問題

?問題簡述

最近處理Hybris框架標準的登陸功能,遇到一個問題:用兩個不同的瀏覽器,同時登陸一個賬號,會同時生成兩個不同的token和refreshToken。

問題原因

?解決了其實非常簡單,就是Hybris的Employee表中,有一個禁用登陸的字段logindisabled,被設置為禁止登陸了,正常情況應該是設置為false。

Hybris登陸原理

?在這里可以詳細的說一下問題以及造成的原因,Hybris的標準登陸功能是,同一個人的賬號,登陸以后會設置access_token,refresh_token,expired,分別為登陸token,刷新token和超時時間,這個三個字段都存在同一張表里,只要在登陸時間內,無論怎么登陸,都應該返回的是同一個token對象,也就是上面三個字段的值應該保持一致。

但是這次遇到的問題就是,用兩個不同瀏覽器,登陸同一個賬號,會產生不同的token。

這里再補充一下,問題背景,這里的登陸,是在完成了saml2.0的單點登陸的第二步:后端通過url跳轉,攜帶code直接從前端進入的Hybris后臺標準登陸方法。

之前一直在單純的排查登陸問題,實際登陸沒有任何問題,因為用了標準的源碼,問題出在單點登陸的第一步,也就是通過saml登陸請求到后端以后,在這里改變了Employee表的logindisabled字段狀態,從而導致存token對象的表中的指定數據被清空,從而導致第二步的標準登陸方法執行沒有獲取到用戶的token,而是直接生成了一個新的token。

那問題的重點就在,改變了Employee表的一個字段狀態,存儲在表中token對象就被清空了呢?

原因如下:

public class UserAuthenticationTokensRemovePrepareInterceptor implements PrepareInterceptor<UserModel> {private final TimeService timeService;public UserAuthenticationTokensRemovePrepareInterceptor(TimeService timeService) {this.timeService = timeService;}public void onPrepare(UserModel userModel, InterceptorContext ctx) throws InterceptorException {if (userModel.isLoginDisabled() || this.isUserDeactivated(userModel)) {Collection<OAuthAccessTokenModel> tokensToRemove = userModel.getTokens();if (tokensToRemove != null) {tokensToRemove.forEach((token) -> ctx.registerElementFor(token, PersistenceOperation.DELETE));}userModel.setTokens(Collections.emptyList());}}
......

UserMoldel是Employee的父類?

isLoginDisabled()方法就是判斷字段loginDisabled的值。如果為true,就把user里面的token對象設置為空。

也就是后來登陸查詢不到用戶token對象的原因。

獲取token對象

那Hybris是怎么獲取當前已經登陸過的用戶的token對象的呢?

主要是通過DefaultHybrisOpenIDTokenServices的createAccessToken()方法:

public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) {try {OAuth2AccessToken accessToken = super.createAccessToken(authentication);Set<String> scopes = OAuth2Utils.parseParameterList((String)authentication.getOAuth2Request().getRequestParameters().get("scope"));if (scopes.contains("openid")) {OAuthClientDetailsModel clientDetailsModel = this.getClientDetailsDao().findClientById(authentication.getOAuth2Request().getClientId());if (!(clientDetailsModel instanceof OpenIDClientDetailsModel)) {Logger var10000 = LOG;String var10001 = clientDetailsModel.getClientId();var10000.warn("OAuth2 error, wrong configuration - Client with ID " + var10001 + " is not instance of " + OpenIDClientDetailsModel.class.getName());throw new InvalidRequestException("Server error. Can't generate id_token.");} else {OpenIDClientDetailsModel openIDClientDetailsModel = (OpenIDClientDetailsModel)clientDetailsModel;List<String> externalScopes = null;if (openIDClientDetailsModel.getExternalScopeClaimName() != null) {externalScopes = this.externalScopesStrategy.getExternalScopes(clientDetailsModel, (String)authentication.getUserAuthentication().getPrincipal());LOG.debug("externalScopes: " + externalScopes);}IDTokenParameterData idtokenparam = this.initializeIdTokenParameters(openIDClientDetailsModel.getClientId());DefaultOAuth2AccessToken accessTokenIdToken = new DefaultOAuth2AccessToken(accessToken);String requestedScopes = (String)authentication.getOAuth2Request().getRequestParameters().get("scope");if (!StringUtils.isEmpty(requestedScopes) && requestedScopes.contains("openid")) {IdTokenHelper idTokenHelper = this.createIdTokenHelper(authentication, openIDClientDetailsModel, externalScopes, idtokenparam);Jwt jwt = idTokenHelper.encodeAndSign(this.getSigner(idtokenparam));Map<String, Object> map = new HashMap();map.put("id_token", jwt.getEncoded());accessTokenIdToken.setAdditionalInformation(map);return accessTokenIdToken;} else {LOG.warn("Missing openid scope");throw new InvalidRequestException("Missing openid scope");}}} else {return accessToken;}} catch (ModelSavingException e) {LOG.debug("HybrisOAuthTokenServices->createAccessToken : ModelSavingException", e);return super.createAccessToken(authentication);} catch (ModelRemovalException e) {LOG.debug("HybrisOAuthTokenServices->createAccessToken : ModelRemovalException", e);return super.createAccessToken(authentication);}}

可以看到其主要就是通過調用父類的?createAccessToken(authentication)來實現的。

通過調用鏈:

HybrisOAuthTokenServices.createAccessToken(authentication)————>>>

DefaultTokenServices.createAccessToken(authentication)

@Transactionalpublic OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {OAuth2AccessToken existingAccessToken = this.tokenStore.getAccessToken(authentication);OAuth2RefreshToken refreshToken = null;if (existingAccessToken != null) {if (!existingAccessToken.isExpired()) {this.tokenStore.storeAccessToken(existingAccessToken, authentication);return existingAccessToken;}if (existingAccessToken.getRefreshToken() != null) {refreshToken = existingAccessToken.getRefreshToken();this.tokenStore.removeRefreshToken(refreshToken);}this.tokenStore.removeAccessToken(existingAccessToken);}if (refreshToken == null) {refreshToken = this.createRefreshToken(authentication);} else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken)refreshToken;if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {refreshToken = this.createRefreshToken(authentication);}}OAuth2AccessToken accessToken = this.createAccessToken(authentication, refreshToken);this.tokenStore.storeAccessToken(accessToken, authentication);refreshToken = accessToken.getRefreshToken();if (refreshToken != null) {this.tokenStore.storeRefreshToken(refreshToken, authentication);}return accessToken;}

可以看到重點在這一句

OAuth2AccessToken existingAccessToken = this.tokenStore.getAccessToken(authentication);

?通過authentication來獲取token,也就是HybrisOAuthTokenStore的getAccessToken方法:

public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {OAuth2AccessToken accessToken = null;OAuthAccessTokenModel accessTokenModel = null;String authenticationId = this.authenticationKeyGenerator.extractKey(authentication);try {accessTokenModel = this.oauthTokenService.getAccessTokenForAuthentication(authenticationId);accessToken = this.deserializeAccessToken((byte[])accessTokenModel.getToken());} catch (ClassCastException | IllegalArgumentException var7) {LOG.warn("Could not extract access token for authentication " + authentication);this.oauthTokenService.removeAccessTokenForAuthentication(authenticationId);} catch (UnknownIdentifierException var8) {if (LOG.isInfoEnabled()) {LOG.debug("Failed to find access token for authentication " + authentication);}}try {if (accessToken != null && accessTokenModel != null && !StringUtils.equals(authenticationId, this.authenticationKeyGenerator.extractKey(this.deserializeAuthentication((byte[])accessTokenModel.getAuthentication())))) {this.replaceToken(authentication, accessToken);}} catch (ClassCastException | IllegalArgumentException var6) {this.replaceToken(authentication, accessToken);}return accessToken;}

這一段的重點是通過

String authenticationId = this.authenticationKeyGenerator.extractKey(authentication);?

獲取authenticationId,再去對應的表里,根據authenticationId查詢出對應用戶的token信息:

accessTokenModel = this.oauthTokenService.getAccessTokenForAuthentication(authenticationId);

這里還有一點需要重點了解的,就是?authenticationId是如何生成的,怎么保證每個用戶的authenticationId都是一樣的:

public class DefaultAuthenticationKeyGenerator implements AuthenticationKeyGenerator {private static final String CLIENT_ID = "client_id";private static final String SCOPE = "scope";private static final String USERNAME = "username";public String extractKey(OAuth2Authentication authentication) {Map<String, String> values = new LinkedHashMap();OAuth2Request authorizationRequest = authentication.getOAuth2Request();if (!authentication.isClientOnly()) {values.put("username", authentication.getName());}values.put("client_id", authorizationRequest.getClientId());if (authorizationRequest.getScope() != null) {values.put("scope", OAuth2Utils.formatParameterList(new TreeSet(authorizationRequest.getScope())));}return this.generateKey(values);}protected String generateKey(Map<String, String> values) {try {MessageDigest digest = MessageDigest.getInstance("MD5");byte[] bytes = digest.digest(values.toString().getBytes("UTF-8"));return String.format("%032x", new BigInteger(1, bytes));} catch (NoSuchAlgorithmException nsae) {throw new IllegalStateException("MD5 algorithm not available.  Fatal (should be in the JDK).", nsae);} catch (UnsupportedEncodingException uee) {throw new IllegalStateException("UTF-8 encoding not available.  Fatal (should be in the JDK).", uee);}}
}

可以看到,就是通過提取authentication對象的一系列屬性,做MD5的Hash算法算出來的,由于用戶都是一個,authentication提取的屬性能保持都是一致的,所以可以為每個用戶生成一個唯一的authenticationId。

然后在

this.oauthTokenService.getAccessTokenForAuthentication(authenticationId)

在進行查詢:

public OAuthAccessTokenModel getAccessTokenForAuthentication(final String authenticationId) {ServicesUtil.validateParameterNotNull(authenticationId, "Parameter 'authenticationId' must not be null!");return (OAuthAccessTokenModel)this.getSessionService().executeInLocalView(new SessionExecutionBody() {public Object execute() {DefaultOAuthTokenService.this.searchRestrictionService.disableSearchRestrictions();try {return DefaultOAuthTokenService.this.oauthTokenDao.findAccessTokenByAuthenticationId(authenticationId);} catch (ModelNotFoundException e) {throw new UnknownIdentifierException(e);}}});}

這里可以直接看到查詢sql:?

public OAuthAccessTokenModel findAccessTokenByAuthenticationId(String authenticationId) {Map<String, Object> params = new HashMap();params.put("id", authenticationId);return (OAuthAccessTokenModel)this.searchUnique(new FlexibleSearchQuery("SELECT {pk} FROM {OAuthAccessToken} WHERE {authenticationId} = ?id ", params));}

?也就是從表OAuthAccessToken進行查詢。

這也就是Hybris標準的登陸功能的實現原理。

按理來說只要是同一個用戶登陸,在過期時間之類,都能查詢到對應的token。

而筆者遇到的問題,沒能查詢到,就是在登陸之前修改了表Employee的字段loginDisabled的狀態

導致OAuthAccessToken表對應的用戶數據被清空,從而需要刷新token。

那么刷新token 的邏輯是怎樣呢?

前面也有:

在DefaultTokenServices的createAccessToken方法中,當查詢不到token時,會重新生成refresh_token和access_token:

if (refreshToken == null) {refreshToken = this.createRefreshToken(authentication);} else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken)refreshToken;if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {refreshToken = this.createRefreshToken(authentication);}}OAuth2AccessToken accessToken = this.createAccessToken(authentication, refreshToken);this.tokenStore.storeAccessToken(accessToken, authentication);refreshToken = accessToken.getRefreshToken();if (refreshToken != null) {this.tokenStore.storeRefreshToken(refreshToken, authentication);}return accessToken;

然后返回token對象。

結論

在解決問題的同時,通過源碼詳細的講解了Hybris的登陸原理,希望對大家有所幫助。

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/84435.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/84435.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/84435.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

c/c++的opencv椒鹽噪聲

在 C/C 中實現椒鹽噪聲 椒鹽噪聲&#xff08;Salt-and-Pepper Noise&#xff09;&#xff0c;也稱為脈沖噪聲&#xff08;Impulse Noise&#xff09;&#xff0c;是數字圖像中常見的一種噪聲類型。它的特點是在圖像中隨機出現純白色&#xff08;鹽&#xff09;或純黑色&#x…

LIEDNet: A Lightweight Network for Low-light Enhancement and Deblurring論文閱讀

摘要 夜間拍攝的圖像常常面臨諸如低光和模糊等挑戰&#xff0c;這些問題主要是由于昏暗環境和長時間曝光的頻繁使用所導致。現有方法要么獨立處理這兩種退化問題&#xff0c;要么依賴于通過復雜機制生成的精心設計的先驗知識&#xff0c;這導致了較差的泛化能力和較高的模型復…

談談worldquant中設置的幾個意思

Decay 是一個設置&#xff0c;用于確定要反映多少過去的位置。正如我們之前詳細介紹的那樣&#xff0c;Decay 值越高&#xff0c;Alpha 周轉率越低。但是&#xff0c;請注意&#xff0c;Alpha 的夏普比率可能會隨著信息延遲而降低。 創建 Alpha 時&#xff0c;頭寸可能會集中在…

大模型和AI工具匯總(一)

一、國內可免費使用的大模型&#xff08;持續更新&#xff09; DeepSeek 模型介紹&#xff1a;DeepSeek 系列包括 DeepSeek V3&#xff08;通用場景&#xff09;、DeepSeek R1&#xff08;推理模型&#xff09;&#xff0c;支持高達 64K 上下文長度&#xff0c;中文場景表現優…

HarmonyOS NEXT 技術特性:分布式軟總線技術架構

HarmonyOS NEXT 技術特性&#xff1a;分布式軟總線技術架構 隨著物聯網發展&#xff0c;2030 預計全球聯網設備達 2000 億&#xff0c;異構設備互聯難題凸顯&#xff0c;分布式軟總線作為 HarmonyOS 生態核心&#xff0c;以軟件虛擬總線打破物理局限&#xff0c;讓跨品牌設備即…

什么是VR展館?VR展館的實用價值有哪些?

VR展館&#xff0c;重塑展覽體驗。在數字化時代浪潮的推動下&#xff0c;傳統的實體展館經歷前所未有的變革。作為變革的先鋒&#xff0c;VR展館以無限的潛力&#xff0c;成為展覽行業的新寵。 VR展館&#xff0c;即虛擬現實展館&#xff0c;是基于VR&#xff08;Virtual Real…

VLA模型:自動駕駛與機器人行業的革命性躍遷,端到端智能如何重塑未來?

當AI開始操控方向盤和機械臂&#xff0c;人類正在見證一場靜默的產業革命。 2023年7月&#xff0c;谷歌DeepMind拋出一枚技術核彈——全球首個視覺語言動作模型&#xff08;VLA&#xff09;RT-2橫空出世。這個能將“把咖啡遞給穿紅衣服的阿姨”這類自然語言指令直接轉化為機器人…

華為OD機試真題——出租車計費/靠譜的車 (2025A卷:100分)Java/python/JavaScript/C/C++/GO最佳實現

2025 A卷 100分 題型 本專欄內全部題目均提供Java、python、JavaScript、C、C++、GO六種語言的最佳實現方式; 并且每種語言均涵蓋詳細的問題分析、解題思路、代碼實現、代碼詳解、3個測試用例以及綜合分析; 本文收錄于專欄:《2025華為OD真題目錄+全流程解析+備考攻略+經驗分…

40 歲 Windows 開啟 AI 轉型:從系統到生態的智能重構

在科技快速發展的當下&#xff0c;人工智能成為驅動各領域變革的核心力量&#xff0c;擁有 40 年歷史的 Windows 也開啟了向 AI 的全面轉型。2025 年 5 月 19-22 日西雅圖 Build 2025 開發者大會上&#xff0c;微軟展示了 Windows 11 向 AI 智能體核心平臺轉型的戰略&#xff0…

Python實例題:Python3實現可控制肉雞的反向Shell

目錄 Python實例題 題目 代碼實現 reverse_shell_client.py reverse_shell_server.py 實現原理 反向連接機制&#xff1a; 命令執行與傳輸&#xff1a; 功能特點&#xff1a; 關鍵代碼解析 服務端命令處理 客戶端命令執行 客戶端持久化連接 使用說明 啟動服務端…

AWS EC2 使用Splunk DB connect 連接 RDS mysql

1: 先創建 RDS mysql: 我們選擇free: 選擇free 過后,自動生成single instance, 沒有垮AZ 的db 設置。 選擇密碼登入: 注意:上面設置密碼的時候,特別提示:不能有特殊字符,我就設置了: mypassword 下面可以選擇通過EC2 連接,當然也可以不選:

SAP重塑云ERP應用套件

在2025年Sapphire大會上&#xff0c;SAP正式發布了其云ERP產品的重塑計劃&#xff0c;推出全新“Business Suite”應用套件&#xff0c;并對供應鏈相關應用進行AI增強升級。這一變革旨在簡化新客戶進入SAP生態系統的流程&#xff0c;同時為現有客戶提供更加統一、智能和高效的業…

初識 RocketMQ 知識總結:基礎概念、架構解析、核心特性與應用場景

Apache RocketMQ 是一款由阿里巴巴開源的分布式消息中間件&#xff0c;具有高吞吐量、低延遲、高可靠性等特點&#xff0c;廣泛應用于互聯網、金融、電商等領域。以下從多個維度對 RocketMQ 進行全面解析&#xff1a; 一、RocketMQ 基礎概念 1. 定義與定位 分布式消息中間件…

[特殊字符] UI-Trans:字節跳動發布的多模態 UI 轉換大模型工具,重塑界面智能化未來

2025 年&#xff0c;字節跳動&#xff08;ByteDance&#xff09;發布了革命性的多模態 UI 轉換模型 —— UI-Trans&#xff0c;引發了業界廣泛關注。作為一款融合視覺理解、語義分析與用戶交互意圖解析的 AI 工具&#xff0c;UI-Trans 在多個領域展現出強大能力&#xff0c;正在…

這個方法關閉PowerBI賬戶的安全默認值

這個方法關閉PowerBI賬戶的安全默認值 如果PowerBI賬戶是在 2019 年 10 月 22 日當天或之后創建的&#xff0c;則可能會自動啟用安全默認值&#xff0c;登錄賬戶會彈出彈框&#xff0c;如圖&#xff1a; 使用四步就可以關閉此彈框的提示&#xff1a; 第一步&#xff1a;轉到 A…

【Linux】磁盤空間不足

錯誤提示: no space left on device 經典版&#xff08;block占用&#xff09; 模擬 dd if/dev/zero of/var/log/nginx.log bs1M count2000排查 #1. df -h 查看哪里空間不足,哪個分區#2. du -sh詳細查看目錄所占空間 du -sh /* 排查占用空間大的目錄 du -sh /var/* du…

計算機視覺---YOLOv2

YOLOv2講解 一、YOLOv2 整體架構與核心特性 YOLOv2&#xff08;You Only Look Once v2&#xff09;于2016年發布&#xff0c;全稱為 YOLO9000&#xff08;因支持9000類目標檢測&#xff09;&#xff0c;在YOLOv1基礎上進行了多項關鍵改進&#xff0c;顯著提升了檢測精度和速度…

【深度學習】1. 感知器,MLP, 梯度下降,激活函數,反向傳播,鏈式法則

一、感知機 對于分類問題&#xff0c;我們設定一個映射&#xff0c;將x通過函數f(x)映射到y 1. 感知機的基本結構 感知機&#xff08;Perceptron&#xff09;是最早期的神經網絡模型&#xff0c;由 Rosenblatt 在 1958 年提出&#xff0c;是現代神經網絡和深度學習模型的雛形…

IP、子網掩碼、默認網關、DNS

IP、子網掩碼、默認網關、DNS 1. 概述1.1 windows配置處 2.IP 地址&#xff08;Internet Protocol Address&#xff09;2.1 公網ip2.2 內網ip2.3 &#x1f310; 公網 IP 與內網 IP 的關系&#xff08;NAT&#xff09; 3. 子網掩碼&#xff08;Subnet Mask&#xff09;4. 默認網…

Azure 公有云基礎架構與核心服務:從基礎到實踐指南

&#x1f525;「炎碼工坊」技術彈藥已裝填&#xff01; 點擊關注 → 解鎖工業級干貨【工具實測|項目避坑|源碼燃燒指南】 一、基礎概念 Azure 的基礎架構由多個核心組件構成&#xff0c;理解這些概念是掌握其技術框架的第一步&#xff1a; 地理區域&#xff08;Geographic R…