Oinone登录扩展:对接SSO(适应于4.7.8之前的版本)

适配版本

4.7.8之前的版本

概述

在企业内部,对于已有一套完整的登录系统(SSO)的情况下,通常会要求把所有的系统都对接到SSO中;本文主要讲解用Oinone开发的项目对接SSO的具体实现。

对接步骤

1、项目自定义实现UserCookieLogin,可参考示例说明:

pro.shushi.pamirs.user.api.login.UserCookieLoginFree

2、对接SSO示例

对接流程说明:

  • 1)【必须】从请求头Header或者Query中获取到token;
  • 2)【必须】去SSO服务端验证token的有效性;
  • 3)【可选】根据token去服务端获取用户信息;如果token可以直接反解析出用户信息,则该步骤忽略;
  • 4)【可选】根据实际情况用户信息是否进行DB的存储;
  • 5)【必须】验证token有效后,生成Session和Cookie(即token换cookie); 注意超时时间需要 <= SSO服务端token失效时间。
package pro.shushi.pamirs.demo.core.sso;

import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import pro.shushi.pamirs.demo.core.sso.constant.HttpConstant;
import pro.shushi.pamirs.demo.core.sso.constant.SSOConstant;
import pro.shushi.pamirs.demo.core.sso.constant.SessionUserTypeEnum;
import pro.shushi.pamirs.demo.core.sso.model.ApiCommonTransient;
import pro.shushi.pamirs.demo.core.sso.utils.AuthenticateUtils;
import pro.shushi.pamirs.demo.core.sso.model.PermissionInfoResp;
import pro.shushi.pamirs.meta.annotation.fun.extern.Slf4j;
import pro.shushi.pamirs.meta.api.dto.model.PamirsUserDTO;
import pro.shushi.pamirs.meta.api.dto.protocol.PamirsRequestVariables;
import pro.shushi.pamirs.meta.api.session.PamirsSession;
import pro.shushi.pamirs.meta.common.exception.PamirsException;
import pro.shushi.pamirs.meta.common.spring.BeanDefinitionUtils;
import pro.shushi.pamirs.meta.common.util.UUIDUtil;
import pro.shushi.pamirs.resource.api.enmu.UserSignUpType;
import pro.shushi.pamirs.user.api.cache.UserCache;
import pro.shushi.pamirs.user.api.constants.UserConstant;
import pro.shushi.pamirs.user.api.enmu.UserExpEnumerate;
import pro.shushi.pamirs.user.api.enmu.UserLoginTypeEnum;
import pro.shushi.pamirs.user.api.login.IUserLoginChecker;
import pro.shushi.pamirs.user.api.login.UserCookieLogin;
import pro.shushi.pamirs.user.api.login.UserCookieLoginSimple;
import pro.shushi.pamirs.user.api.model.PamirsUser;
import pro.shushi.pamirs.user.api.model.tmodel.PamirsUserTransient;
import pro.shushi.pamirs.user.api.service.UserService;
import pro.shushi.pamirs.user.api.utils.CookieUtil;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeUnit;

/**
 *
 * @author wangxian
 *
 *  完全自定义login的过程
 *  需要实现登陆部分login 以及拦截部分fetchUserIdByReq
 *  如果fetchUserIdByReq返回值为null的时候 将会被拦截
 */
@Slf4j
@Order(0)
@Component
public class DemoUserSSOCookieLogin extends UserCookieLogin<PamirsUser>  {

    //刷新令牌
    private static String REFRESH_TOKEN = "refreshToken";
    //系统id
    private static String CLIENT_ID = "client-id";
    //访问令牌
    private static String AUTHORIZATION = "Authorization";

    private IUserLoginChecker checker;

    @Autowired
    private UserService userService;
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Override
    public String createSessionId(HttpServletRequest request, PamirsUser idModel) {
        return UUIDUtil.getUUIDNumberString();
    }

    @Override
    public String type() {
        return UserLoginTypeEnum.COOKIE.value();
    }

    @Override
    public PamirsUser resolveAndVerification(PamirsUserTransient user) {
        if (checker == null) {
            checker = BeanDefinitionUtils.getBean(IUserLoginChecker.class);
        }
        return checker.check4login(user);
    }

    /**
     * 重写登录拦截功能
     * 该函数主要作用,通过三方权限校验.
     * @param request
     * @return
     */
    @Override
    public PamirsUserDTO fetchUserIdByReq(HttpServletRequest request) {
        PamirsRequestVariables pamirsRequestVariables =  PamirsSession.getRequestVariables();
        log.debug("==cookie== {}", JSON.toJSONString(request.getCookies()));
        log.debug("==headNames== {}", JSON.toJSONString(request.getHeaderNames()));
        log.debug("==params=={}",JSON.toJSONString(request.getContextPath()));
        String sessionId = CookieUtil.getValue(request, "pamirs_uc_session_id");
        PamirsUserDTO pamirsUserDTO = UserCache.get(sessionId);
        if (pamirsUserDTO ==null) {
            //H5-企业微信登录,其他SSO登录。获取标识。根据实际情况修改和适配
            String accessToken = request.getParameter("accessToken");
            ApiCommonTransient permissionInfo = AuthenticateUtils.getPermissionInfo(accessToken);
            // *******登录成功的条件判断,各SSO根据情况自行修改********
            if(HttpConstant.SUCCESS.equals(permissionInfo.getCode())) {
                // SSO用户换 Oinone 的用户
                PamirsUser pamirsUser = setUserInfoToCookiesAndSetUserIdToCache(permissionInfo,accessToken);
                return new PamirsUserDTO().setUserId(pamirsUser.getId()).setPhone(pamirsUser.getPhone()).setUserCode(
                        pamirsUser.getCode()).setLogin(pamirsUser.getLogin()).setEmail(pamirsUser.getEmail()).setUserName(pamirsUser.getName());
            } else {
                //用户未登录,并且token未获取到用户null(根据实际情况修改)
                return null;
            }
        } else {
            return fetchUserIdByReq4Pamirs(request);
        }
    }

    /**
     * 开放一个用户登录setCookies函数 供登录和跳转校验
     * @param permissionInfo
     * @param accessToken
     * @return
     */
    public PamirsUser setUserInfoToCookiesAndSetUserIdToCache(ApiCommonTransient permissionInfo, String accessToken) {
        String sessionId;
        PermissionInfoResp ssoResponseTransient = JSON.parseObject(permissionInfo.getData().toString(), PermissionInfoResp.class);
        // *********示例:SSO跟业务系统中用户ID关联;其他关联条件请根据数据情况处理*******
        PamirsUser pamirsUser = userService.queryById(ssoResponseTransient.getUserInfo().getId());
        if (pamirsUser == null) {
            // ********SSO中有用户数据,Oinone的系统中没有需要怎么处理************/
            // ********这里仅仅是一个示例,在上述情况下创建用户,请根据实际情况处理,请根据实际情况处理*****/
            pamirsUser = createOrUpdatePamirsUser(ssoResponseTransient);
        }
        HttpServletRequest httpServletRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        HttpServletResponse httpServletResponse = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        UserCookieLoginSimple userCookieLoginSimple = new UserCookieLoginSimple();

        sessionId = userCookieLoginSimple.createSessionId(httpServletRequest, null);
        String cacheKey = userCookieLoginSimple.parseSessionId(sessionId);
        UserCache.putCache(cacheKey, userCookieLoginSimple.coverToUserDTO(pamirsUser));
        try {
            CookieUtil.set(httpServletResponse, UserConstant.USER_SESSION_ID, sessionId);
        } catch (Exception e) {
            log.error("SSO Login Cookie Set Err", e);
        }

        //set accessToken to cookies
        String userCodeCacheKey = SSOConstant.USER_REDIS_CACHE + pamirsUser.getId();
        redisTemplate.opsForValue().set(userCodeCacheKey,accessToken,3600, TimeUnit.SECONDS);
        return pamirsUser;
    }

    // ********这里仅仅是一个示例,在上述情况下创建用户,请根据实际情况处理,请根据实际情况处理*****/
    private PamirsUser createOrUpdatePamirsUser(PermissionInfoResp ssoResponseTransient) {
        PamirsUser pamirsUser;
        pamirsUser = new PamirsUser();
        pamirsUser.unsetEmail();
        pamirsUser.unsetPhone() ;
        pamirsUser.setId(ssoResponseTransient.getUserInfo().getId());
        pamirsUser.setUserType(SessionUserTypeEnum.COMPANY_ADMIN.displayName());
        pamirsUser.setSignUpType(UserSignUpType.BACKSTAGE);
        pamirsUser.setName(ssoResponseTransient.getUserInfo().getUsername());
        pamirsUser.setLogin(ssoResponseTransient.getUserInfo().getUsername());
        pamirsUser.setNickname(ssoResponseTransient.getUserInfo().getNickname());
        pamirsUser.setRealname(ssoResponseTransient.getUserInfo().getNickname());
        pamirsUser.setId(ssoResponseTransient.getUserInfo().getId());
        userService.createOrUpdate(pamirsUser);
        return pamirsUser;
    }

    /**
     * 原始用户登陆后函数调用
     * @param request
     * @return
     */
    private PamirsUserDTO fetchUserIdByReq4Pamirs(HttpServletRequest request) {
        PamirsUserDTO pamirsUserDTO = super.fetchUserIdByReq(request);
        if (pamirsUserDTO == null || pamirsUserDTO.getUserId() == null) {
            return pamirsUserDTO;
        }
        PamirsUser user = new PamirsUser().setId(pamirsUserDTO.getUserId()).queryById();
        if (user != null && !Boolean.TRUE.equals(user.getActive())) {
            //清理下登录的cookie
            String sessionId = CookieUtil.getValue(request, UserConstant.USER_SESSION_ID);
            if (!StringUtils.isBlank(sessionId)) {
                HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
                UserCache.logout(request, response, sessionId);
            }
            log.error("{}当前用户是{},{}", UserExpEnumerate.USER_CAN_NOT_ACTIVE_ERROR, pamirsUserDTO.getUserId(), pamirsUserDTO.getLogin());
            throw PamirsException.construct(UserExpEnumerate.USER_CAN_NOT_ACTIVE_ERROR).errThrow();
        }

        return pamirsUserDTO;
    }

}

3、通常自定义登录的时候需要获取HttpRequest和HttpResponse,从HttpRequest获取请求头或者请求参数,获取方式如下:

// HttpServletRequest获取
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// HttpServletResponse获取
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();

对接SSO示例代码包

示例代码包点击可以进行下载:
SS0对接Demo示例

Oinone社区 作者:望闲原创文章,如若转载,请注明出处:https://doc.oinone.top/backend/4747.html

访问Oinone官网:https://www.oinone.top获取数式Oinone低代码应用平台体验

Like (0)
望闲's avatar望闲数式管理员
Previous 2023年11月24日 am9:44
Next 2023年11月24日 pm4:51

相关推荐

  • 导出导入翻译

    http://168.138.179.151/pamirs/file 导出翻译项: mutation { excelExportTaskMutation { createExportTask( data: { workbookDefinition: { model: "file.ExcelWorkbookDefinition" name: "excelLocationTemplate" } } ) { name } } } { "path": "/file", "lang": "en-US" } 导入翻译项: mutation { excelImportTaskMutation { createImportTask( data: { workbookDefinition: { model: "file.ExcelWorkbookDefinition" name: "excelLocationTemplate" } file: { url: "https://minio.oinone.top/pamirs/upload/zbh/test/2024/06/03/导出国际化配置模板_1717390304285_1717391684633.xlsx" } } ) { name } } } PS:导入自行修改url进行导入

    2024年6月28日
    2.5K00
  • Oinone环境保护(v5.2.3以上)

    概述 Oinone平台为合作伙伴提供了环境保护功能,以确保在一套环境可以在较为安全前提下修改配置文件,启动多个JVM等部署操作。 本章内容主要介绍与环境保护功能相关的启动参数。 名词解释 本地开发环境:开发人员在本地启动业务工程的环境 公共环境:包含设计器镜像和业务工程的环境 环境保护参数介绍 【注意】参数是加在程序实参 (Program arguments)上,通常可能错误的加在Active Profiles上了 -PenvProtected=${value} 是否启用环境保护,默认为true。 环境保护是通过与最近一次保存在数据库的base_platform_environment表中数据进行比对,并根据每个参数的配置特性进行判断,在启动时将有错误的内容打印在启动日志中,以便于开发者进行问题排查。 除此之外,环境保护功能还提供了一些生产配置的优化建议,开发者可以在启动时关注这些日志,从而对生产环境的配置进行调优。 -PsaveEnvironments=${value} 是否将此次启动的环境参数保存到数据库,默认为true。 在某些特殊情况下,为了避免公共环境中的保护参数发生不必要的变化,我们可以选择不保存此次启动时的配置参数到数据库中,这样就不会影响其他JVM启动时发生校验失败而无法启动的问题。 -PstrictProtected=${value} 是否使用严格校验模式,默认为false 通常我们建议在公共环境启用严格校验模式,这样可以最大程度的保护公共环境的元数据不受其他环境干扰。 PS:在启用严格校验模式时,需避免内外网使用不同连接地址的场景。如无法避免,则无法启用严格校验模式。 常见问题 需要迁移数据库,并更换了数据库连接地址该如何操作? 将原有数据库迁移到新数据库。 修改配置文件中数据库的连接地址。 在启动脚本中增加-PenvProtected=false关闭环境保护。 启动JVM服务可以看到有错误的日志提示,但不会中断本次启动。 移除启动脚本中的-PenvProtected=false或将值改为true,下次启动时将继续进行环境保护检查。 可查看数据库中base_platform_environment表中对应数据库连接配置已发生修改,此时若其他JVM在启动前未正确修改,则无法启动。 本地开发时需要修改Redis连接地址到本地,但希望不影响公共环境的使用该如何操作? PS:由于Redis中的元数据缓存是根据数据库差量进行同步的,此操作会导致公共环境在启动时无法正确刷新Redis中的元数据缓存,需要配合pamirs.distribution.session.allMetaRefresh参数进行操作。如无特殊必要,我们不建议使用该形式进行协同开发,多次修改配置会导致出错的概率增加。 本地环境首次启动时,除了修改Redis相关配置外,还需要配置pamirs.distribution.session.allMetaRefresh=true,将本地新连接的Redis进行初始化。 在本地启动时,增加-PenvProtected=false -PsaveEnvironments=false启动参数,以确保本地启动不会修改公共环境的配置,并且可以正常通过环境保护检测。 本地环境成功启动并正常开发功能后,需要发布到公共环境进行测试时,需要先修改公共环境中业务工程配置pamirs.distribution.session.allMetaRefresh=true后,再启动业务工程。 启动一次业务工程后,将配置还原为pamirs.distribution.session.allMetaRefresh=false。

    2024年10月21日
    2.3K00
  • Maven Setting 配置详解

    常用标签概览 servers/server:配置私有仓库用户名和密码进行认证,以 id 进行关联。 mirrors/mirror:配置镜像仓库拉取时的地址源和额外配置。 profiles/profile:配置多个可能使用的镜像仓库。 activeProfiles/activeProfile:配置默认激活的 profile,以 id 进行关联。 Oinone 私有仓库配置 以下配置可以在使用 Oinone 私有仓库的同时,也可以正常使用 aliyun 镜像源。 <servers> <server> <id>shushi</id> <username>${username}</username> <password>${password}</password> </server> </servers> <mirrors> <mirror> <id>shushi</id> <mirrorOf>shushi</mirrorOf> <url>http://ss.nexus.ixtx.fun/repository/public</url> <!– 忽略 https 认证,maven 版本过高时需要配置 –> <blocked>false</blocked> </mirror> </mirrors> <profiles> <profile> <id>shushi</id> <repositories> <repository> <!– 对应 server.id –> <id>shushi</id> <url>http://ss.nexus.ixtx.fun/repository/public</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories> <pluginRepositories> <pluginRepository> <!– 对应 server.id –> <id>shushi</id> <url>http://ss.nexus.ixtx.fun/repository/snapshots</url> <releases> <enabled>false</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </pluginRepository> </pluginRepositories> </profile> <profile> <id>aliyun</id> <repositories> <repository> <id>aliyun</id> <url>https://maven.aliyun.com/repository/public</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>aliyun</id> <url>https://maven.aliyun.com/repository/public</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>false</enabled> </snapshots> </pluginRepository> </pluginRepositories> </profile> </profiles> <activeProfiles> <!– 使用 shushi 私有仓库 –> <activeProfile>shushi</activeProfile> <!– 使用 aliyun 镜像仓库 –> <activeProfile>aliyun</activeProfile> </activeProfiles> 常见问题 使用 mvn 时无法拉取 Oinone 最新版镜像,提示找不到对应的包 原因:在 Oinone 开源后,oinone-pamirs 内核相关包都被部署到 maven 中央仓库,但由于其他镜像仓库的同步存在延时,在未正确同步的其他镜像源拉取时会出现找不到对应的包相关异常。 解决方案:检查 mirrors 中是否配置了 aliyun 镜像源,如果配置了,使用上述 Oinone 私有仓库配置重新配置后,再进行拉取。这一问题是由于 mirrors 配置不当,拦截了所有从 maven 中央仓库拉取的地址替换为了 aliyun 镜像源导致的。

    2025年11月10日
    90700
  • EIP开放接口使用MD5验签发起请求(v5.x)

    验签工具类 PS:该验签方法仅在pamirs-core的5.0.16版本以上可正常使用 public class EipSignUtils { public static final String SIGN_METHOD_MD5 = "md5"; private static final String SIGN_METHOD_HMAC = "hmac"; private static final String SECRET_KEY_ALGORITHM = "HmacMD5"; private static final String MESSAGE_DIGEST_MD5 = "MD5"; public static String signTopRequest(Map<String, String> params, String secret, String signMethod) throws IOException { // 第一步:检查参数是否已经排序 String[] keys = params.keySet().toArray(new String[0]); Arrays.sort(keys); // 第二步:把所有参数名和参数值串在一起 StringBuilder query = new StringBuilder(); if (SIGN_METHOD_MD5.equals(signMethod)) { query.append(secret); } for (String key : keys) { String value = params.get(key); if (StringUtils.isNoneBlank(key, value)) { query.append(key).append(value); } } // 第三步:使用MD5/HMAC加密 byte[] bytes; if (SIGN_METHOD_HMAC.equals(signMethod)) { bytes = encryptHMAC(query.toString(), secret); } else { query.append(secret); bytes = encryptMD5(query.toString()); } // 第四步:把二进制转化为大写的十六进制(正确签名应该为32大写字符串,此方法需要时使用) return byte2hex(bytes); } private static byte[] encryptHMAC(String data, String secret) throws IOException { byte[] bytes; try { SecretKey secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), SECRET_KEY_ALGORITHM); Mac mac = Mac.getInstance(secretKey.getAlgorithm()); mac.init(secretKey); bytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8)); } catch (GeneralSecurityException e) { throw new IOException(e.toString(), e); } return bytes; } private static byte[] encryptMD5(String data) throws IOException { return encryptMD5(data.getBytes(StandardCharsets.UTF_8)); } private static byte[] encryptMD5(byte[] data) throws IOException { try { MessageDigest md = MessageDigest.getInstance(MESSAGE_DIGEST_MD5); return md.digest(data); } catch (NoSuchAlgorithmException e)…

    2024年6月29日
    2.0K00
  • 自定义审批方式、自定义审批节点名称

    @Model.model(审批模型.MODEL_MODEL) @Component public class 审批模型Action { @Function @Function.Advanced(category = FunctionCategoryEnum.CUSTOM_DESIGNER, displayName = "测试自定义审批类型") public WorkflowSignTypeEnum signType(String json) { // json为业务数据,可用JsonUtils转换 return WorkflowSignTypeEnum.COUNTERSIGN_ONEAGREE_ONEREJUST; } @Function @Function.Advanced(category = FunctionCategoryEnum.CUSTOM_DESIGNER, displayName = "测试自定义审批名称") public String customApprovalName() { return UUID.randomUUID().toString(); } }

    2023年12月5日
    2.4K00

Leave a Reply

Please Login to Comment