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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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…