阿里云RAM账号免密登录Java最佳实践

发布于:2025-04-22 ⋅ 阅读:(22) ⋅ 点赞:(0)

        参考官方文章地址:如何使用免登访问流程_阿里云集成转售解决方案-阿里云帮助中心

        参考代码地址:如何使用安全访问服务Python及Java示例代码_阿里云集成转售解决方案-阿里云帮助中心

1. RAM的含义

        阿里云的访问控制RAM(Resource Access Management)提供了强大的细粒度权限管理功能,适用于企业中多个部门或角色需要访问ECS资源的情况。为了保障敏感信息和关键业务流程的安全,您可以根据各部门或角色的具体职责分配不同的访问权限。通过实施权限分离策略,不仅能显著提升管理效率,还能有效降低信息泄露的风险。本文介绍如何通过控制RAM用户的权限,以实现对云服务器ECS资源的访问控制。

2. 场景示例

        假设您公司是使用ECS来托管应用程序和服务。其中,IT架构规划由管理人员主导,他们对所有ECS资源拥有控制权,包括但不限于创建资源、调整资源分配及安全策略配置等关键职责。开发人员负责项目的持续迭代和功能创新,并承担将项目部署到ECS上的任务。运维人员则承担起保障系统正常运转的责任,通过创建快照、创建镜像、执行相关脚本等方式维护现有服务。

针对这三类人员的需求,我们将设计如下权限方案:

  • 管理人员:可以拥有创建、删除ECS实例及修改安全组规则等所有ECS操作权限。

  • 开发人员:能够查看所有ECS实例的信息,但不能修改任何设置,同时可以登录ECS实例进行操作。

  • 运维人员:具备创建部分资源的权限,但不具备删除资源的权限,如创建快照和镜像、执行脚本等任务。

       针对以上三类不同的人群便可设置不同的RAM账号,对应设置不同的权限,以实现资源及操作权限的隔离。

3. 为什么要RAM免密登录?

        RAM免密登录主要有以下三点好处:安全性,避免长期保存密码;自动化,适合脚本或应用自动执行任务;权限控制,可以精细化管理权限;减少人为错误,比如密码泄露或输错。

4. 免密登录的方式

RAM免密登录的原理

其核心原理是通过非交互式身份验证替代传统密码,常用技术包括:

  1. AccessKey(长期凭证)

    • 用户生成一对 AccessKey ID 和 Secret Access Key,直接嵌入代码或配置文件中,用于API请求签名验证。

    • 风险:长期有效,一旦泄露可能被滥用。

  2. STS(临时安全令牌)

    • 通过STS服务申请临时凭证(有效期几分钟至几小时),动态生成Token,到期自动失效。

    • 适用场景:移动端或第三方临时访问。

  3. 角色扮演(Role Assume)

    • 将权限赋予某个角色(如ECS实例角色),实例启动时自动获取临时凭证,无需硬编码密钥。

    • 原理:通过云服务的元数据接口(如阿里云的100.100.100.200)动态获取临时Token。

  4. 联合身份(Federation)

    • 集成企业AD或SSO服务,用户通过现有身份系统登录后,映射为云平台的临时角色。

        本文主要介绍角色扮演获取STS(临时安全令牌)实现免密登录。

5. 免密登录流程

6. 示例代码

package com.hundsun.openplat.api.alivno.aliSDK;

import java.io.IOException;
import java.net.URISyntaxException;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import com.aliyuncs.sts.model.v20150401.AssumeRoleRequest;
import com.aliyuncs.sts.model.v20150401.AssumeRoleResponse;
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;

/**
 *
 * 免密登陆STS
 *
 * 获取安全令牌
 */
public class StsService {
    private static String getRoleArn(String accountId, String roleName) {
        return String.format("acs:ram::%s:role/%s", accountId, roleName);
    }
    private static final String SIGN_IN_DOMAIN = "https://signin.aliyun.com/federation";
    /**
     * 使用安全令牌获取登录令牌
     * https://help.aliyun.com/document_detail/91913.html
     *
     * @param accesskeyId
     * @param accessKeySecret
     * @param securityToken
     * @return
     * @throws IOException
     * @throws URISyntaxException
     */
    private static String getSignInToken(String accesskeyId, String accessKeySecret, String securityToken)
            throws IOException, URISyntaxException {
        URIBuilder builder = new URIBuilder(SIGN_IN_DOMAIN);
        builder.setParameter("Action", "GetSigninToken")
                .setParameter("AccessKeyId", accesskeyId)
                .setParameter("AccessKeySecret", accessKeySecret)
                .setParameter("SecurityToken", securityToken)
                .setParameter("TicketType", "mini");
        HttpGet request = new HttpGet(builder.build());
        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;
    }
    private static String getHdmLoginUrl(String pageUrl, String signInToken) throws URISyntaxException {
        URIBuilder builder = new URIBuilder(SIGN_IN_DOMAIN);
        builder.setParameter("Action", "Login");
        // 登录失效跳转的地址,一般配置为自建WEB配置302跳转的URL
        builder.setParameter("LoginUrl", "https://uicuat.hundsun.cn/iuccasserver/login?service=https%3A%2F%2Fwww.sit.hs.net%3A443%2Fcom.hundsun.openplat.front%2Fcloud%2Fopen%2Fuser%2FtoLogin.html");
        // 实际访问 DAS 的页面,比如全局大盘,实时大盘,某个实例详情等
        builder.setParameter("Destination", pageUrl);
        builder.setParameter("SigninToken", signInToken);
        HttpGet request = new HttpGet(builder.build());
        return request.getURI().toString();
    }
    /**
     * 通过AssumeRole接口获取用户临时身份
     * 参考 https://help.aliyun.com/document_detail/28763.html
     *
     * @param accountId
     * @param accessKeyId
     * @param accessKeySecret
     * @param ramRole
     * @return
     * @throws ClientException
     */
    private static AssumeRoleResponse.Credentials assumeRole(String accountId, String accessKeyId,
                                                             String accessKeySecret, String ramRole)
            throws ClientException {
        String defaultRegion = "cn-hangzhou";
        IClientProfile profile = DefaultProfile.getProfile(defaultRegion, accessKeyId, accessKeySecret);
        DefaultAcsClient client = new DefaultAcsClient(profile);
        AssumeRoleRequest request = new AssumeRoleRequest();
        // 设置RAMArn, accountId为资源Owner的UID,即主账号
        request.setRegionId(defaultRegion);
        request.setRoleArn(getRoleArn(accountId, ramRole));
        // 用户自定义参数。此参数用来区分不同的令牌,可用于用户级别的访问审计。格式:^[a-zA-Z0-9\.@\-_]+$
        request.setRoleSessionName("session-name");
        // 指定的过期时间,单位为秒。过期时间范围:900 ~ 3600,默认值为 3600
        request.setDurationSeconds(3600L);
        AssumeRoleResponse response = client.getAcsResponse(request);
        return response.getCredentials();
    }
    public static void main(String[] args) throws IOException, URISyntaxException {
        try {
            /*
            Step 0 准备子账号和权限授权
             */
            String accountId = "";
            // 用来访问 DAS 产品的Role,可以按照需要添加AliyunHDMReadOnlyAccess(只读),AliyunHDMFullAccess 权限
            // 默认使用 “aliyunid-ag-ram-role-admin”
            String ramRole = "";
            //  某个子账号AK,SK, 要求需要有 AliyunSTSAssumeRoleAccess 权限
            String accessKeyId = "";
            String accessKeySecret = "";
            /*
             Step 1 通过AssumeRole接口获取临时AK, SK, SecurityToken
             */
            AssumeRoleResponse.Credentials credentials = assumeRole(accountId, accessKeyId, accessKeySecret, ramRole);
            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
             */
            String signInToken = getSignInToken(credentials.getAccessKeyId(),
                    credentials.getAccessKeySecret(),
                    credentials.getSecurityToken());
            System.out.println("Your SigninToken is: " + signInToken);
            /*
            Step 3 构造免登录链接,比如 DAS 的监控大盘
             */
            //String pageUrl = getHdmLoginUrl("https://hdm.console.aliyun.com/?hideTopbar=true#/customDashboard?", signInToken);
            String pageUrl = getHdmLoginUrl("https://ecs-buy4service.aliyun.com/wizard/#/prepay/", signInToken);
            System.out.println("Your PageUrl is : " + pageUrl);
        } catch (ClientException e) {
            System.out.println("Failed:");
            System.out.println("Error code: " + e.getErrCode());
            System.out.println("Error message: " + e.getErrMsg());
            System.out.println("RequestId: " + e.getRequestId());
        }
    }
}

 


网站公告

今日签到

点亮在社区的每一天
去签到