Data Agent 控制台免登录集成指南

更新时间:
复制为 MD 格式

本指南旨在帮助开发者通过阿里云RAM(访问控制)和 STS(安全令牌服务),将 Data Agent 控制台安全地内嵌到自建运维平台或数据门户中。集成后,您的用户可以直接在自建平台内访问和使用 Data Agent 的功能,无需再次登录阿里云账号,从而提升操作的连贯性和效率。

集成原理

集成的核心是基于角色的临时访问凭证机制。整个流程如下:

  1. 获取临时凭证:您的后端服务使用一个预设的 RAM 用户凭证,调用AssumeRole 接口,扮演一个被授予了 Data Agent 访问权限的 RAM 角色,从而获取一套临时的访问凭证(AccessKey、Secret 和 SecurityToken)。

  2. 换取登录令牌:您的后端服务使用这套临时凭证,调用阿里云登录服务的 GetSigninToken 接口,换取一个一次性的登录令牌(SigninToken)。

  3. 构造登录链接:将获取到的 SigninToken 和目标 Data Agent 页面的地址,构造成一个联合登录URL。

  4. 前端内嵌展示:后端将此联合登录 URL 返回给前端,前端将其作为 src 属性嵌入到 <iframe> 标签中,完成免登录展示。

前提条件

在开始开发前,请确保已完成以下配置:

  • 已经创建用于调用安全令牌服务的 RAM 用户

    1. 登录 RAM 控制台

    2. 创建一个 RAM 用户,访问方式需选择为使用永久 AccessKey 访问,并保存其 AccessKey ID 和 AccessKey Secret。

    3. 为该 RAM 用户授予 AliyunSTSAssumeRoleAccess 系统权限策略。此权限允许该用户调用 AssumeRole 接口。

  • 已经创建用于访问 Data Agent 的 RAM 角色

    1. 在 RAM 控制台创建一个 RAM 角色,信任主体类型选择云账号。创建后,记录该角色的 ARN。

    2. 为该角色授予访问 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"
            }
          ]
        }

开发步骤

  1. 后端获取临时凭证 :

    在后端服务中,使用先前创建的 RAM 用户的 AccessKey,调用AssumeRole接口。

    • RoleArn:传入先前创建的 RAM 角色的 ARN。

    • RoleSessionName:自定义的会话名称,通常可设置为您平台内部的用户 ID,用于审计追溯。

    • DurationSeconds:临时凭证的有效期,单位为秒。

  2. 后端获取登录令牌 :

    使用上一步获取的临时凭证(AccessKeyId, AccessKeySecret, SecurityToken),调用 GetSigninToken 接口。

    说明
  3. 后端构造联合登录 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

    Login

    LoginUrl

    可选。登录失效后或用户主动退出后跳转的地址。建议配置为您自建平台的页面。

    https://your-platform.com/login

    Destination

    必需。实际要访问的目标 Data Agent 服务页面。此地址与 TicketType 强相关.

    https://agent4service.dms.aliyun.com/

    SigninToken

    必需。步骤二中获取的登录令牌。

    从 GetSigninToken 接口返回的值。

  4. 前端使用 <iframe> 嵌入:

    您的后端服务将上一步构造的完整 URL 返回给前端。前端应用只需将此 URL 设置为 <iframe> 元素的 src 属性即可。

    <iframe src="[后端返回的联合登录URL]" width="100%" height="800px" frameborder="0"></iframe>

注意事项

  • 会话超时时间:内嵌页面的会话有效期取决于以下两者中的最小值:

    1. 调用 AssumeRole 接口时传递的 DurationSeconds 参数值。

    2. 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);
            }
    
        }
    }