本指南旨在帮助开发者通过阿里云RAM(访问控制)和 STS(安全令牌服务),将 Data Agent 控制台安全地内嵌到自建运维平台或数据门户中。集成后,您的用户可以直接在自建平台内访问和使用 Data Agent 的功能,无需再次登录阿里云账号,从而提升操作的连贯性和效率。
集成原理
集成的核心是基于角色的临时访问凭证机制。整个流程如下:
获取临时凭证:您的后端服务使用一个预设的 RAM 用户凭证,调用
AssumeRole接口,扮演一个被授予了 Data Agent 访问权限的 RAM 角色,从而获取一套临时的访问凭证(AccessKey、Secret 和 SecurityToken)。换取登录令牌:您的后端服务使用这套临时凭证,调用阿里云登录服务的
GetSigninToken接口,换取一个一次性的登录令牌(SigninToken)。构造登录链接:将获取到的
SigninToken和目标 Data Agent 页面的地址,构造成一个联合登录URL。前端内嵌展示:后端将此联合登录 URL 返回给前端,前端将其作为
src属性嵌入到<iframe>标签中,完成免登录展示。
前提条件
在开始开发前,请确保已完成以下配置:
已经创建用于调用安全令牌服务的 RAM 用户
已经创建用于访问 Data Agent 的 RAM 角色
在 RAM 控制台创建一个 RAM 角色,信任主体类型选择云账号。创建后,记录该角色的 ARN。
参考文档:创建可信实体为阿里云账号的RAM角色
为该角色授予访问 Data Agent 的权限。您有两种方式:
推荐方式:授予
AliyunDMSDataAgentFullAccess系统权限策略,以获取完整功能。自定义方式:您也可以创建自定义权限策略,按需授予最小权限。相关接口列表请参见 Data Agent API 概览。
自定义权限策略示例(仅授予文件分析相关权限):
{ "Version": "1", "Statement": [ { "Action": [ "dms:ListDataAgentSession", "dms:DescribeDataAgentSession", "dms:CreateDataAgent", "dms:UpdateDataAgentSession", "dms:DescribeFileUploadSignature", "dms:ListDataCenterColumn", "dms:EditDataCenterTableColumn", "dms:SaveDataAgentReport", "dms:ListDataCenterTable", "dms:DescribeDataCenterTable", "dms:DescribeDataAgentStatus", "dms:CreateDataAgentSession", "dms:SendChatMessage", "dms:ListFileUpload", "dms:DeleteFileUpload", "dms:DescribeDataCenterDatabase", "dms:RegistryDataAgentUser", "dms:DescribeDataAgentReport", "dms:ListDataCenterDatabase", "dms:GetChatContent", "dms:FileUploadCallback", "dms:ListDataCenterPreviewData" ], "Resource": "*", "Effect": "Allow" } ] }
开发步骤
后端获取临时凭证 :
在后端服务中,使用先前创建的 RAM 用户的 AccessKey,调用
AssumeRole接口。RoleArn:传入先前创建的 RAM 角色的 ARN。RoleSessionName:自定义的会话名称,通常可设置为您平台内部的用户 ID,用于审计追溯。DurationSeconds:临时凭证的有效期,单位为秒。
后端获取登录令牌 :
使用上一步获取的临时凭证(AccessKeyId, AccessKeySecret, SecurityToken),调用
GetSigninToken接口。说明TicketType主要分为normal和mini,如果需要iFrame内嵌需使用mini 。
默认使用normal,对应域名:https://agent.dms.aliyun.com/
如果是mini应用于BID虚商,对应域名 https://agent4service.dms.aliyun.com/。
后端构造联合登录 URL
根据以下格式,构造最终的免登录 URL:
https://signin.aliyun.com/federation?Action=Login &LoginUrl=<登录失效跳转的地址,一般配置为自建WEB配置302跳转的URL> &Destination=<实际访问DataAgent服务页面 https://agent4service.dms.aliyun.com/> &SigninToken=<获取的登录TOKEN>URL 参数说明:
参数
说明
示例
Action固定值为
Login。LoginLoginUrl可选。登录失效后或用户主动退出后跳转的地址。建议配置为您自建平台的页面。
https://your-platform.com/loginDestination必需。实际要访问的目标 Data Agent 服务页面。此地址与
TicketType强相关.https://agent4service.dms.aliyun.com/SigninToken必需。步骤二中获取的登录令牌。
从
GetSigninToken接口返回的值。前端使用
<iframe>嵌入:您的后端服务将上一步构造的完整 URL 返回给前端。前端应用只需将此 URL 设置为
<iframe>元素的src属性即可。<iframe src="[后端返回的联合登录URL]" width="100%" height="800px" frameborder="0"></iframe>
注意事项
会话超时时间:内嵌页面的会话有效期取决于以下两者中的最小值:
调用
AssumeRole接口时传递的DurationSeconds参数值。RAM 角色自身配置的“最大会话时间”。
凭证安全:持有永久 AccessKey 的 RAM 用户应遵循最小权限原则,且其凭证(AK/SK)绝不能泄露到前端代码中。所有涉及永久凭证的操作都必须在您的后端服务中完成。
由于涉及
<iframe>内嵌,您需要通过提交工单联系技术支持,将自建平台的域名添加到 Data Agent 的跨域资源共享白名单中。
附录
以下提供一个完整的 Java 实现示例,涵盖核心逻辑与关键配置,便于您快速理解并集成到实际项目中。
项目依赖:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.aliyun.dms.agent</groupId> <artifactId>data-agent-login-demo</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.15</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.13</version> </dependency> <dependency> <groupId>com.aliyun</groupId> <artifactId>sts20150401</artifactId> <version>1.1.7</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.83</version> </dependency> <dependency> <groupId>com.aliyun</groupId> <artifactId>tea-openapi</artifactId> <version>0.3.9</version> </dependency> <dependency> <groupId>com.aliyun</groupId> <artifactId>tea-console</artifactId> <version>0.0.1</version> </dependency> <dependency> <groupId>com.aliyun</groupId> <artifactId>tea-util</artifactId> <version>0.2.23</version> </dependency> <dependency> <groupId>com.aliyun</groupId> <artifactId>credentials-java</artifactId> <version>1.0.2</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build> </project>配置文件示例:
# RAM User's permanent credentials ACCESS_KEY=LTAI5t... SECRET_KEY=your_secret... # RAM Role's ARN ROLE_ARN=acs:ram::...Java 示例代码:
package com.aliyun.dms.agent; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.aliyun.sts20150401.models.AssumeRoleResponse; import com.aliyun.sts20150401.models.AssumeRoleResponseBody; import com.aliyun.tea.TeaException; import org.apache.http.HttpStatus; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.utils.URIBuilder; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import java.io.IOException; import java.net.URISyntaxException; /** * 读取配置.env 文件 * */ public class ConfigLoader { public static AssumeRoleLoginProperty loadLoginProperty(String env) { try (InputStream input = ConfigLoader.class.getClassLoader().getResourceAsStream(env)) { if (input == null) { throw new RuntimeException("无法找到 .env 文件"); } Properties prop = new Properties(); prop.load(input); return new AssumeRoleLoginProperty( prop.getProperty("ACCESS_KEY"), prop.getProperty("SECRET_KEY"), prop.getProperty("ROLE_ARN") ); } catch (Exception e) { throw new RuntimeException("加载配置失败", e); } } } public class AssumeRoleLoginProperty { private final String key; private final String secret; private final String roleArn; public AssumeRoleLoginProperty(String key, String secret, String roleArn) { this.key = key; this.secret = secret; this.roleArn = roleArn; } public String getKey() { return key; } public String getRoleArn() { return roleArn; } public String getSecret() { return secret; } } public class AgentAssumeRoleLoginHelper { private static final String SIGN_IN_TOKEN_URL = "https://signin4service.aliyun.com/federation"; private static final String SIGN_IN_LOGIN_URL = "https://signin4service.aliyun.com/login.htm"; private static final String DATA_AGENT_URL = "https://signin4service.dms.aliyun.com"; private final AssumeRoleLoginProperty assumeRoleLoginProperty; public AgentAssumeRoleLoginHelper(AssumeRoleLoginProperty assumeRoleLoginProperty) { this.assumeRoleLoginProperty = assumeRoleLoginProperty; } public static void main(String[] args_) throws Exception { try { AssumeRoleLoginProperty property = ConfigLoader.loadLoginProperty(".env"); AgentAssumeRoleLoginHelper helper = new AgentAssumeRoleLoginHelper(property); helper.getLoginUrl("SessionA"); } catch (Exception e) { e.printStackTrace(); } } /** * 使用安全令牌获取登录令牌 * <a href="https://help.aliyun.com/document_detail/91913.html">GetSigninToken文档说明</a> */ private static String getSignInToken(String accessKeyId, String accessKeySecret, String securityToken) throws IOException, URISyntaxException { URIBuilder builder = new URIBuilder(SIGN_IN_TOKEN_URL); builder.setParameter("Action", "GetSigninToken") .setParameter("AccessKeyId", accessKeyId) .setParameter("AccessKeySecret", accessKeySecret) .setParameter("SecurityToken", securityToken) .setParameter("TicketType", "normal"); // 从normal改为mini HttpGet request = new HttpGet(builder.build()); try (CloseableHttpClient httpclient = HttpClients.createDefault()) { try (CloseableHttpResponse response = httpclient.execute(request)) { if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { String context = EntityUtils.toString(response.getEntity()); JSONObject jsonObject = JSON.parseObject(context); return jsonObject.getString("SigninToken"); } else { System.out.println(response.getStatusLine()); } } } return null; } /** * <b>description</b> : * <p>使用凭据初始化账号Client</p> * * @return Client * @throws Exception */ public com.aliyun.sts20150401.Client createClient() throws Exception { // 工程代码建议使用更安全的无AK方式,凭据配置方式请参见:https://help.aliyun.com/document_detail/378657.html。 com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config() .setAccessKeyId(assumeRoleLoginProperty.getKey()) .setAccessKeySecret(assumeRoleLoginProperty.getSecret()); // Endpoint 请参考 https://api.aliyun.com/product/Sts config.endpoint = "sts.cn-beijing.aliyuncs.com"; return new com.aliyun.sts20150401.Client(config); } private static String buildLoginUrl(String signInToken) throws URISyntaxException { URIBuilder builder = new URIBuilder(SIGN_IN_TOKEN_URL); builder.setParameter("Action", "Login"); // 登录失效跳转的地址,一般配置为自建WEB配置302跳转的URL builder.setParameter("LoginUrl", SIGN_IN_LOGIN_URL); builder.setParameter("Destination", DATA_AGENT_URL); builder.setParameter("SigninToken", signInToken); HttpGet request = new HttpGet(builder.build()); return request.getURI().toString(); } /** * @param sessionName 角色会话名称, 该参数为用户自定义参数。通常设置为调用该 API 的用户身份,例如:用户名 */ public void getLoginUrl(String sessionName) throws Exception { try { /* Step 1 通过AssumeRole接口获取临时AK,SK,SecurityToken */ System.out.println("-------------------- AssumeRole --------------------"); com.aliyun.sts20150401.Client client = createClient(); com.aliyun.sts20150401.models.AssumeRoleRequest assumeRoleRequest = new com.aliyun.sts20150401.models.AssumeRoleRequest(); assumeRoleRequest.setRoleArn(assumeRoleLoginProperty.getRoleArn()); assumeRoleRequest.setRoleSessionName(sessionName); assumeRoleRequest.setDurationSeconds(3600L); com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions(); // 复制代码运行请自行打印 API 的返回值 AssumeRoleResponse response = client.assumeRoleWithOptions(assumeRoleRequest, runtime); AssumeRoleResponseBody.AssumeRoleResponseBodyCredentials credentials = response.getBody().getCredentials(); System.out.println("Expiration: " + credentials.getExpiration()); System.out.println("Access Key Id: " + credentials.getAccessKeyId()); System.out.println("Access Key Secret: " + credentials.getAccessKeySecret()); System.out.println("Security Token: " + credentials.getSecurityToken()); /* Step 2 获取SigninToken */ System.out.println("-------------------- GetSigninToken --------------------"); String signInToken = getSignInToken(credentials.getAccessKeyId(), credentials.getAccessKeySecret(), credentials.getSecurityToken()); System.out.println("Your SigninToken is: " + signInToken); /* Step 3 获取登录地址 */ System.out.println("-------------------- GetLoginUrl --------------------"); String loginUrl = buildLoginUrl(signInToken); System.out.println("Your LoginUrl is: " + loginUrl); } catch (TeaException error) { // 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。 // 错误 message System.out.println(error.getMessage()); // 诊断地址 System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); } catch (Exception _error) { TeaException error = new TeaException(_error.getMessage(), _error); // 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。 // 错误 message System.out.println(error.getMessage()); // 诊断地址 System.out.println(error.getData().get("Recommend")); com.aliyun.teautil.Common.assertAsString(error.message); } } }