package com.iailab.module.report.framework.jmreport.core.service; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.iailab.framework.common.exception.ServiceException; import com.iailab.framework.common.util.servlet.ServletUtils; import com.iailab.framework.security.config.SecurityProperties; import com.iailab.framework.security.core.LoginUser; import com.iailab.framework.security.core.util.SecurityFrameworkUtils; import com.iailab.framework.tenant.core.context.TenantContextHolder; import com.iailab.framework.web.core.util.WebFrameworkUtils; import com.iailab.module.system.api.oauth2.OAuth2TokenApi; import com.iailab.module.system.api.oauth2.dto.OAuth2AccessTokenCheckRespDTO; import com.iailab.module.system.api.permission.PermissionApi; import com.iailab.module.system.enums.permission.RoleCodeEnum; import lombok.RequiredArgsConstructor; import org.jeecg.modules.jmreport.api.JmReportTokenServiceI; import org.springframework.http.HttpHeaders; import javax.servlet.http.HttpServletRequest; import java.util.Objects; /** * {@link JmReportTokenServiceI} 实现类,提供积木报表的 Token 校验、用户信息的查询等功能 * * @author 随心 */ @RequiredArgsConstructor public class JmReportTokenServiceImpl implements JmReportTokenServiceI { /** * 积木 token head 头 */ private static final String JM_TOKEN_HEADER = "X-Access-Token"; /** * auth 相关格式 */ private static final String AUTHORIZATION_FORMAT = SecurityFrameworkUtils.AUTHORIZATION_BEARER + " %s"; private final OAuth2TokenApi oauth2TokenApi; private final PermissionApi permissionApi; private final SecurityProperties securityProperties; /** * 自定义 API 数据集appian自定义 Header,解决 Token 传递。 * 参考 api数据集token机制详解 文档 * * @return 新 head */ @Override public HttpHeaders customApiHeader() { // 读取积木标标系统的 token HttpServletRequest request = ServletUtils.getRequest(); String token = request.getHeader(JM_TOKEN_HEADER); // 设置到 iailab 系统的 token HttpHeaders headers = new HttpHeaders(); headers.add(securityProperties.getTokenHeader(), String.format(AUTHORIZATION_FORMAT, token)); return headers; } /** * 校验 Token 是否有效,即验证通过 * * @param token JmReport 前端传递的 token * @return 是否认证通过 */ @Override public Boolean verifyToken(String token) { Long userId = SecurityFrameworkUtils.getLoginUserId(); if (!Objects.isNull(userId)) { return true; } return buildLoginUserByToken(token) != null; } /** * 获得用户编号 *
* 虽然方法名获得的是 username,实际对应到项目中是用户编号 * * @param token JmReport 前端传递的 token * @return 用户编号 */ @Override public String getUsername(String token) { Long userId = SecurityFrameworkUtils.getLoginUserId(); if (ObjectUtil.isNotNull(userId)) { return String.valueOf(userId); } LoginUser user = buildLoginUserByToken(token); return user == null ? null : String.valueOf(user.getId()); } /** * 基于 token 构建登录用户 * * @param token token * @return 返回 token 对应的用户信息 */ private LoginUser buildLoginUserByToken(String token) { if (StrUtil.isEmpty(token)) { return null; } // TODO 如下的实现不算特别优雅,主要咱是不想搞的太复杂,所以参考对应的 Filter 先实现了 // ① 参考 TokenAuthenticationFilter 的认证逻辑(Security 的上下文清理,交给 Spring Security 完成) // 目的:实现基于 JmReport 前端传递的 token,实现认证 TenantContextHolder.setIgnore(true); // 忽略租户,保证可查询到 token 信息 LoginUser user = null; try { OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkAccessToken(token).getCheckedData(); if (accessToken == null) { return null; } user = new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType()) .setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes()); } catch (ServiceException ignored) { // do nothing:如果报错,说明认证失败,则返回 false 即可 } if (user == null) { return null; } SecurityFrameworkUtils.setLoginUser(user, WebFrameworkUtils.getRequest()); // ② 参考 TenantContextWebFilter 实现(Tenant 的上下文清理,交给 TenantContextWebFilter 完成) // 目的:基于 LoginUser 获得到的租户编号,设置到 Tenant 上下文,避免查询数据库时的报错 TenantContextHolder.setIgnore(false); TenantContextHolder.setTenantId(user.getTenantId()); return user; } @Override public String[] getRoles(String token) { // 设置租户上下文。原因是:/jmreport/** 纯前端地址,不会走 buildLoginUserByToken 逻辑 LoginUser loginUser = SecurityFrameworkUtils.getLoginUser(); if (loginUser == null) { return null; } TenantContextHolder.setTenantId(loginUser.getTenantId()); // 参见文档 https://help.jeecg.com/jimureport/prodSafe.html 文档 // 适配:如果是本系统的管理员,则转换成 jimu 报表的管理员 Long userId = SecurityFrameworkUtils.getLoginUserId(); return permissionApi.hasAnyRoles(userId, RoleCodeEnum.SUPER_ADMIN.getCode()).getCheckedData() ? new String[]{"admin"} : null; } @Override public String getTenantId() { // 补充说明:不能直接通过 TenantContext 获取,因为 jimu 报表前端请求时,没有带上 tenant-id Header LoginUser loginUser = SecurityFrameworkUtils.getLoginUser(); if (loginUser == null) { return null; } return StrUtil.toStringOrNull(loginUser.getTenantId()); } }