外部系统接入SSO-OAuth2(6.3.x 之后版本支持)

一、概述

统一身份认证系统提供了单点登录功能。本文档详述了统一身份认证系统单点登录接口,介绍了应用系统对接统一身份认证系统的过程和方法,用以帮助开发人员将应用系统接入统一身份认证系统。

本文档支持的协议:OAuth2

二、统一认证集成规范

2.1 SSO登录场景

序号 场景 预期结果 确认
1 登录跳转,在未登录的条件下,直接访问业务系统 跳转到单点登录界面
2 登录跳转,在已登录的条件下,直接访问业务系统 直接进入业务系统
3 正常登录,先登录认证,然后访问业务系统 直接进入业务系统
4 正常登出,在单点登录系统登出 访问业务系统要重新登录
5 正常登出,在业务系统登出 单点登录系统同步登出

2.2 认证流程

OAuth2 登录和认证流程
外部系统接入SSO-OAuth2(6.3.x 之后版本支持)

  1. 后端验证 token 后,创建本地会话(如 JSESSIONID 或自定义 Cookie);
  2. 后续应用请求靠本地会话维持登录状态,不再依赖原始 token;
  3. 本地会话有过期时间(如 30 分钟无操作过期);

三、认证服务接口

3.1 SSO服务端认证

浏览器登录

分类 说明
请求地址(开发环境) http://${host}:${port}/pamirs/sso/auth?client_id={client_id}&redirect_uri={redirect_uri}&state={state}
请求地址(测试环境) http://${host}:${port}/pamirs/sso/auth?client_id={client_id}&redirect_uri={redirect_uri}&state={state}
请求方式 GET
请求参数 redirect_uri:应用系统回调地址
client_id:SSO 服务端颁发的应用 ID
state:可选但推荐,用于防止 CSRF 的随机字符串(UUID 随机码)
请求示例 http://${host}:${port}/pamirs/sso/auth?client_id=client123456&redirect_rui=http://app.example.com&state=abc123
响应说明 1. 已登录:重定向到 redirect 地址并携带授权码 code,如 http://app.example.com&state=abc123&code=xxx
2. 未登录:重定向到服务器 SSO 登录地址
备注 SSO 登录成功后,会携带授权码并重定向到 redirect 地址

3.2 验证授权码

分类 说明
请求地址(开发环境) http://${host}:${port}/pamirs/sso/oauth2/authorize
请求地址(测试环境) http://${host}:${port}/pamirs/sso/oauth2/authorize
请求方式 POST
请求 Body {"grant_type":"authorization_code","client_id":"xxxx","client_secret":"xxxxx","code":"xxxx"}
请求示例 http://${host}:${port}/pamirs/sso/oauth2/authorize
响应说明 {"code":"0","msg":"成功","data":{"access_token":"xxxxx","expires_in":7200,"refresh_token":"xxxxxx","refresh_token_expiresIn":604800}}
备注

3.2 根据token获取用户信息

分类 说明
请求地址(开发环境) http://${host}:${port}/pamirs/sso/oauth2/getUserInfo
请求地址(测试环境) http://${host}:${port}/pamirs/sso/oauth2/getUserInfo
请求方式 POST
请求头 Authorization: Bearer xxxxxxxx
请求 Body {"client_id":"xxxx"}
请求示例 http://${host}:${port}/pamirs/sso/oauth2/getUserInfo
响应 {"code":"0","msg":"成功","data":{"name":"xxxxx","email":"xxxxx","login":"xxxxxx","id":"xxxxxxx"}}
备注 成功返回用户信息;失败返回错误码:1

根据实际情况其他协议的验证接口

3.3 单点登出接口

分类 说明
请求地址(开发环境) http://${host}:${port}/pamirs/sso/oauth2/logout
请求地址(测试环境) http://${host}:${port}/pamirs/sso/oauth2/logout
请求方式 POST
请求头 Authorization: Bearer xxxxxxxx
请求 Body {"client_id":"xxxx"}
请求示例 http://${host}:${port}/pamirs/sso/oauth2/logout
响应头 HTTP/1.1 200;Content-Type: application/html;charset=UTF-8
响应体 登出成功页面
备注 应用系统向 SSO 系统发起登出请求,SSO 收到后会通知所有其它系统登出该用户

四、服务注册

  1. 添加以下依赖
        <!-- sso相关 -->
        <dependency>
            <groupId>pro.shushi.pamirs.core</groupId>
            <artifactId>pamirs-sso-api</artifactId>
        </dependency>
        <dependency>
            <groupId>pro.shushi.pamirs.core</groupId>
            <artifactId>pamirs-sso-common</artifactId>
        </dependency>
        <dependency>
            <groupId>pro.shushi.pamirs.core</groupId>
            <artifactId>pamirs-sso-server</artifactId>
        </dependency>
  2. 配置文件
    pamirs
      sso:
        enabled: true
        server:
          loginUrl: http://127.0.0.1:8091/login
          authType: OAUTH2

五、应用注册

  1. 在服务端登记接入的应用,颁发应用的client_Id和client_secret; 注意回调地址,必须带上Schema(http(s))
    外部系统接入SSO-OAuth2(6.3.x 之后版本支持)
    外部系统接入SSO-OAuth2(6.3.x 之后版本支持)

  2. 并确保部署的应用机器能访问到SSO的服务端:

五、客户端接入

5.1 方式一:引入SSO客户端

业务系统在接入SSO统一认证系统时,工程中引入SSO客户端jar包,实现少量业务逻辑就可以完成接入,过程如下:

  1. 引入客户端依赖
    <dependency>
    <groupId>pro.shushi.pamirs.core</groupId>
    <artifactId>pamirs-sso-client-starter</artifactId>
    </dependency>
  2. 应用配置修改
    修改你SpringBoot应用的application.yaml,增加客户端的相关配置项

    pamirs:
      sso:
        #服务端地址
        server-url: http(s)://ssoIp:port/pamirs/sso/auth
        #客户端ID
        client-id: 1003
        #客户端密钥
        client-secret: 23bfde398056736c6ead917c3e6a2a5d
        #客户端排除拦截urls,默认为空,支持AntPathMatcher匹配
        exclude-urls:
          - /sample/demo/page
          - /sample/demo/i18n
        #客户端拦截urls,默认通配所有路径,支持AntPathMatcher匹配
        url-patterns:
          - /sample/*

说明:

  • server-url为SSO服务端认证地址;
  • client-id和client-secret是步骤一在服务端登记时生成的应用标识和密钥,用于客户端向服务端发起accessToken请求做校验;

5.2 方式二:不引入客户端包

使用场景:不使用SSO客户端包或者不是java语言,可以参照上面的认证接口进行。

六、外部应用接入到Oinone应用中心

1、确保在SSO应用注册时,正确填写了 回调地址;
2、在应用中心创建应用,参考下面的截图;
外部系统接入SSO-OAuth2(6.3.x 之后版本支持)
3、配置应用的首页
外部系统接入SSO-OAuth2(6.3.x 之后版本支持)
给应用配置首页:
1、选择“绑定URL”
2、URL配置为:http(s)://ssoIp:port/pamirs/sso/apply/transfer?client_id=xxxxxxxxxxxxxxxxxxxxxx
client_id 为外部应用在SSO服务端登记后生成的唯一应用标识
开发环境:http://${host}:${port}/pamirs/sso/apply/transfer?client_id=xxxxxxxxxxxxxxxxxxxxxx
测试环境:http://${host}:${port}/pamirs/sso/apply/transfer?client_id=xxxxxxxxxxxxxxxxxxxxxx
线上环境:http://${host}:${port}/pamirs/sso/apply/transfer?client_id=xxxxxxxxxxxxxxxxxxxxxx
3、跳转类型选择“开单新窗口”;
4、配置完成保存后,刷新页面。就可以从配置中心跳转到目标页面的回调地址了。

Oinone社区 作者:yexiu原创文章,如若转载,请注明出处:https://doc.oinone.top/other/25443.html

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

(0)
yexiu的头像yexiu数式员工
上一篇 2025年12月24日 pm5:39
下一篇 2026年2月10日 pm3:38

相关推荐

  • 如何实现业务表格跳转页面设计器设计器页面

    后端实现 代理继承界面设计器视图模型 @Model.model(MyView.MODEL_MODEL) @Model(displayName = "视图代理") @Model.Advanced(type = ModelTypeEnum.PROXY) public class MyView extends UiDesignerViewProxy { public static final String MODEL_MODEL = "hr.simple.MyView"; @Field.Integer @Field(displayName = "页面布局ID") private Long uiDesignerViewLayoutId; } 重写查询接口,返回页面布局ID,重写创建接口,实现创建页面逻辑。 package pro.shushi.pamirs.top.core.action; import org.apache.commons.collections4.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import pro.shushi.pamirs.boot.base.constants.ViewConstants; import pro.shushi.pamirs.boot.base.enmu.ActionTargetEnum; import pro.shushi.pamirs.boot.base.ux.annotation.action.UxAction; import pro.shushi.pamirs.boot.base.ux.annotation.action.UxRoute; import pro.shushi.pamirs.boot.base.ux.annotation.button.UxRouteButton; import pro.shushi.pamirs.framework.connectors.data.sql.Pops; import pro.shushi.pamirs.framework.connectors.data.sql.query.LambdaQueryWrapper; import pro.shushi.pamirs.meta.annotation.Action; import pro.shushi.pamirs.meta.annotation.Function; import pro.shushi.pamirs.meta.annotation.Model; import pro.shushi.pamirs.meta.api.dto.condition.Pagination; import pro.shushi.pamirs.meta.api.dto.wrapper.IWrapper; import pro.shushi.pamirs.meta.api.session.PamirsSession; import pro.shushi.pamirs.meta.constant.FunctionConstants; import pro.shushi.pamirs.meta.enmu.*; import pro.shushi.pamirs.top.api.model.MyView; import pro.shushi.pamirs.ui.designer.api.designe.UiDesignerViewLayoutService; import pro.shushi.pamirs.ui.designer.model.UiDesignerViewLayout; import pro.shushi.pamirs.ui.designer.pmodel.UiDesignerViewLayoutProxy; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * @author Yexiu at 20:39 on 2025/3/27 */ @Component @Model.model(MyView.MODEL_MODEL) public class MyViewAction { @Autowired private UiDesignerViewLayoutService uiDesignerViewLayoutService; @Action.Advanced(name = FunctionConstants.create, managed = true) @Action(displayName = "创建", summary = "添加", bindingType = ViewTypeEnum.FORM) @Function(name = FunctionConstants.create) @Function.fun(FunctionConstants.create) public MyView create(MyView data) { UiDesignerViewLayoutProxy uiDesignerViewLayoutProxy = new UiDesignerViewLayoutProxy(); uiDesignerViewLayoutProxy.setBizType(data.getBizType()); uiDesignerViewLayoutProxy.setDesignerActionBarType(data.getDesignerActionBarType()); uiDesignerViewLayoutProxy.setViewType(data.getType()); uiDesignerViewLayoutProxy.setModel(data.getModel()); uiDesignerViewLayoutProxy.setModule(PamirsSession.getServApp()); uiDesignerViewLayoutProxy.setViewTitle(data.getTitle()); uiDesignerViewLayoutProxy.setUsingDefaultView(data.getLoadLayout()); UiDesignerViewLayoutProxy saveUiDesigner = uiDesignerViewLayoutService.create(uiDesignerViewLayoutProxy); data.setDesignerViewId(saveUiDesigner.getId()); return data; } @Function.Advanced(type = FunctionTypeEnum.QUERY, displayName = "查询列表") @Function.fun(FunctionConstants.queryPage) @Function(openLevel = {FunctionOpenEnum.API, FunctionOpenEnum.LOCAL}) public Pagination<MyView> queryPage(Pagination<MyView> page, IWrapper<MyView> queryWrapper) { LambdaQueryWrapper<MyView> wrapper = Pops.<MyView>lambdaQuery().from(MyView.MODEL_MODEL) .eq(MyView::getSys, Boolean.FALSE) .eq(MyView::getSystemSource, SystemSourceEnum.UI); Pagination<MyView> myViewPagination = new MyView().queryPage(page, wrapper); List<MyView> content…

    2025年3月31日
    50800
  • 数式Oinone培训前注意事项

    一、快速上手 (建议至少预习 6 小时) 在正式培训之前,建议需要完成以下任务,以便对培训内容有基本了解: 点击阅读:快速启动入门 该文档为学员提供了从入门到实现 demo 的全过程说明,涵盖了开发工具、框架搭建、常见问题解答等内容 开始培训前,请参与人员确保完成以下任务 阅读并理解文档中的每个步骤。 配置好前后端开发环境。 完成 demo 的基础框架搭建,验证是否能够成功运行。 二、预期成果 通过上述预习,大家完成以下事项:• 成功搭建本地开发环境,并能运行前后端的基本 demo。• 对前后端技术栈有初步了解,为正式培训中的深度学习打下基础,并且提出对应的疑问点 三、其他准备工作 1.技术工具检查:请确在本地已安装并配置好必要的开发工具和环境(如 IDE、Node.js、数据库等);2.参与者反馈:在预习过程中,学员如遇到困难或无法解决的问题(前后端疑问),请提前记录并提交,以便培训期间重点解答;3.版本是否是最新的版本,且建议研发人员版本一致,且类型是 mini; ps:部署包相关信息,联系数式相关人员获取

    未分类 2024年8月2日
    2.9K00
  • IP黑白名单实现拦截三方用户

    已知厂商IP为10.139.0.1,经系统检测122.233.24.28、138.122.12.9为风险IP,需要禁止访问。 白名单配置:10.139.0.1 黑名单配置:122.233.24.28、138.122.12.9 厂商请求到Oinone开放平台请求头需携带X-Forwarded-For和X-Real-IP,例如: X-Forwarded-For 122.233.24.28 X-Real-IP 122.233.24.28 经Nginx代理后,oinone获取的请求头内容: X-Forwarded-For 122.233.24.28, 10.139.0.1 # 代理追加厂商IP到末尾 X-Real-IP 122.233.24.28 # 保持客户端原始值,Nginx不处理 效果:厂商10.139.0.1发送的请求且用户X-Real-IP不在黑名单内才放行。 注意事项 Nginx如果配置X-Real-IP需关闭,否则拿到的永远是厂商IP。 proxy_set_header X-Real-IP $remote_addr; 相关文章 开放应用中的ip黑白名单

    2025年5月15日
    48100
  • 新人引导文档

    类型 文档链接 入门参考必看 7天入门到精通 Oinone 初级学习路径 Oinone 初级学习路径 平台部署启动 无代码docker启动说明 低代码启动说明 前端环境和启动前端工程 无代码设计器启动方式 后端无代码设计器Jar包启动方法 平台部署及依赖说明 Oinone平台部署及依赖说明(v5.0) 问题排查方法 问题排查工具使用手册 前后端研发帮助文档 前端文档 后端文档 项目开发实践要点 【前端】项目开发前端知识要点地图 【后端】项目开发后端知识要点地图 版本升级说明 版本更新日志 工具包 【附件一】下载说明 许可证使用说明 pamirs-license 许可证使用常见问题 更多文档新人引导建议,可以下方评论

    2024年7月15日
    1.6K00
  • 导入导出支持国际化语言分隔符

    需求 导出 Excel 时,所有整数、小数字段需要加千分位分隔符显示 例如:10000 导出成 10,000。 只影响“导出的显示效果”,不改变原始数据的语义。 实现思路 通过修改“ Excel 默认导出模版”的平台逻辑,将整型字段模版定义为文本类型,并自定义导出扩展,将所有整型字段的数据根据国际化配置进行分割。 代码示例 自定义导出扩展,分割整型字段 注意 所有已有的导出扩展必须修改继承类ExcelExportSameQueryPageTemplate<Object> –> GlobalExportExt<Object> 否则,已有的导出扩展生成的Excel将无法正常格式化整型字段。 package pro.shushi.pamirs.top.core.temp.exports; import org.apache.commons.collections4.CollectionUtils; import org.springframework.stereotype.Component; import pro.shushi.pamirs.file.api.context.ExcelDefinitionContext; import pro.shushi.pamirs.file.api.entity.EasyExcelBlockDefinition; import pro.shushi.pamirs.file.api.entity.EasyExcelCellDefinition; import pro.shushi.pamirs.file.api.entity.EasyExcelSheetDefinition; import pro.shushi.pamirs.file.api.extpoint.ExcelExportFetchDataExtPoint; import pro.shushi.pamirs.file.api.extpoint.impl.ExcelExportSameQueryPageTemplate; import pro.shushi.pamirs.file.api.model.ExcelExportTask; import pro.shushi.pamirs.meta.annotation.Ext; import pro.shushi.pamirs.meta.annotation.ExtPoint; import pro.shushi.pamirs.meta.api.dto.config.ModelFieldConfig; import pro.shushi.pamirs.meta.api.session.PamirsSession; import pro.shushi.pamirs.meta.enmu.TtypeEnum; import pro.shushi.pamirs.meta.util.FieldUtils; import pro.shushi.pamirs.resource.api.model.ResourceLang; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @Component @Ext(ExcelExportTask.class) public class GlobalExportExt<T> extends ExcelExportSameQueryPageTemplate<T> implements ExcelExportFetchDataExtPoint { @ExtPoint.Implement(priority = 1) @Override public List<Object> fetchExportData(ExcelExportTask exportTask, ExcelDefinitionContext context) { List<Object> results = super.fetchExportData(exportTask, context); if (CollectionUtils.isEmpty(results)) { return results; } return dataFormat(context, results); } public static List<Object> dataFormat(ExcelDefinitionContext context, List<Object> results) { ResourceLang resourceLang = new ResourceLang().setCode(PamirsSession.getLang()).queryOne(); if (resourceLang == null) { return results; } // 小数分隔符 String decimalPoint = resourceLang.getDecimalPoint(); // 整数分隔符 String thousandsSep = resourceLang.getThousandsSep(); // 数字分组规则(每组多少位,比如 "3") String groupingRule = resourceLang.getGroupingRule(); // 解析 groupSize,只做一次 int groupSize = 3; if (groupingRule != null && groupingRule.matches("\\d+")) { try { groupSize = Integer.parseInt(groupingRule); } catch (NumberFormatException ignore) { } } boolean needGroup = groupSize > 0 && thousandsSep != null && !thousandsSep.isEmpty(); //…

    2025年11月18日
    43000

Leave a Reply

登录后才能评论