目录

一、前言

二、前期准备

三、购买短信服务

四、申请签名和模板

(1) 进入阿里云短信服务控制台

(2) 添加签名

(3) 添加模板

五、RAM申请及权限配置

(1) 进入RAM访问控制界面

(2) 创建用户

(3) 分配权限

(4) 创建角色

(5) 记录关键字段的值

六、阿里云.NET SDK身份验证接入

(1) 了解身份验证配置

 (2) 导入.NET SDK

(3) 配置环境变量

(4) 获取STSToken的实现

七、代码发送短信验证码

八、总结


一、前言

手机登录在账号体系中扮演着重要的角色,它不仅是一种方便的身份验证方式,还有助于提高账号的安全性和用户体验。本篇文章介绍阿里云的短信服务及代码发送短信验证码的实现。

二、前期准备

需要阿里云企业账号、一个已经备案过的域名(如果没有我们可以申请测试账号,一样可以做到发送短信,但只能给自己的手机号发)。

阿里云网站:阿里云登录页 (aliyun.com)

三、购买短信服务

先进入短信服务申请免费或购买短信套餐包。

这里新用户可以试用免费的套餐包,个人用户是100条,企业用户是200条。

 你们自行操作就好啦,只是一些简单的支付流程。(略过略过......)

四、申请签名和模板

先介绍一下签名和模板是什么。

以阿里云发来的短信验证码为例,【阿里云】就是签名,也就是我们现在正在申请的签名。后面的部分为模板,是我们在申请签名成功后需要申请的。模板中可变的字符为模板参数(比如说4-6位随机码),是在申请模板中进行配置的,后面可以通过代码动态给模板参数赋值。所以只要配置好签名和模板,我们的短信格式就确定下来啦。

(1) 进入阿里云短信服务控制台

我买的短信套餐是国内短信,这里选择国内消息。

短信服务 (aliyun.com)

(2) 添加签名

首先要明确签名规范,检查填写的内容是否合规,提高审核通过率,一次申请审核时长大概在2小时左右。

短信签名的规范说明_短信服务-阿里云帮助中心 (aliyun.com)

下面进行签名申请:

提交好后等待审核通过就好啦。

(3) 添加模板

模板规范同理。

短信模板规范_短信服务-阿里云帮助中心 (aliyun.com)

 一些模板示例

 等待审核通过即可。

到这里为止,我们需要记录两条属性后续调用短信API会用到

  • 签名名称

  • 模板Code

五、RAM申请及权限配置

先了解一下RAM用户RAM角色的概念

什么是RAM用户?

RAM用户是RAM的一种实体身份类型,有确定的身份ID和身份凭证,它通常与某个确定的人或应用程序一一对应。RAM用户具备以下特点:

  • RAM用户由阿里云账号(主账号)或具有管理员权限的其他RAM用户、RAM角色创建,创建成功后,归属于该阿里云账号,它不是独立的阿里云账号。
  • RAM用户不拥有资源,不能独立计量计费,由所属的阿里云账号统一付费。
  • RAM用户必须在获得授权后,才能登录控制台或使用API访问阿里云账号下的资源。
  • RAM用户拥有独立的登录密码或访问密钥。
  • 一个阿里云账号下可以创建多个RAM用户,对应企业内的员工、系统或应用程序。

您可以创建RAM用户并为其授权,实现不同RAM用户拥有不同资源访问权限的目的。当您的企业存在多用户协同访问资源的场景时,使用RAM可以按需为用户分配最小权限,避免多用户共享阿里云账号密码或访问密钥,从而降低企业的安全风险。

什么是RAM角色?

RAM 角色机制是向您信任的实体(例如:RAM 用户、某个应用或阿里云服务)进行授权的一种安全方法。根据不同应用场景,受信任的实体可能有如下一些例子:

  • 您云账户下的一个 RAM 用户(可能是代表一个移动 App 的后端服务);
  • 其他云账户中的 RAM 用户(需要进行跨账户的资源访问);
  • ECS 实例上运行的应用程序代码(需要对云资源执行操作);
  • 某些阿里云服务(需要对您账户中的资源进行操作才能提供服务);
  • 企业的身份提供商 IdP,可以用于角色 SSO。

RAM 角色颁发短时有效的访问令牌(STS 令牌),使其成为一种更安全的授予访问权限的方法。

特别说明:

RAM 角色不是传统的权限集,是一种临时身份。如果您需要使用权限集,请前往RAM 权限策略

(1) 进入RAM访问控制界面

RAM 访问控制 (aliyun.com)

(2) 创建用户

用户创建成功后,会得到一个AccessKeyID和AccessKeySecret,这两个值要保存下来,界面关闭后AccessKeySecret不再出现

(3) 分配权限

这里就是给RAM用户分配权限,根据我们的需要选择分配,由于我们的功能是短信验证码,我们分配以下三种权限。

  • AliyunDysmsFullAccess (管理短信服务(SMS)的权限)
  • AliyunRAMFullAccess (管理访问控制(RAM)的权限,即管理用户以及授权的权限)
  • AliyunSTSAssumeRoleAccess (调用STS服务AssumeRole接口的权限)

(4) 创建角色

创建成功啦

(5) 记录关键字段的值

这里要记下RAM角色的ARN,后面用于调用AssumeRoleAPI接口的参数。什么是AssumeRole感兴趣的话可以阅读下面这条链接。

调用AssumeRole获取扮演该角色的临时身份_阿里云集成转售解决方案-阿里云帮助中心 (aliyun.com)

我们已经创建并配置好了RAM用户和RAM角色,RAM用户可以使用自己的访问密钥调用AssumeRole接口,以获取某个RAM角色的STS Token,实现通过Credentials工具读取临时安全令牌(STS Token)用作安全登录。

到这里我们需要记录三条重要的字段的值

  • RAM用户的AccessKeyID
  • RAM用户的accessKeySecret
  • RAM角色的ARN

六、阿里云.NET SDK身份验证接入

(1) 了解身份验证配置

为了更好地理解后续的代码,可以先阅读一下阿里云的身份验证配置

如何进行Credentials配置(.NETSDK)_阿里云SDK-阿里云帮助中心 (aliyun.com)

 (2) 导入.NET SDK

我选择的身份验证方式为官方推荐的环境变量配置+STS Token,这种方式最为安全和灵活。

首先要先接入相关SDK,我这里已经整理好了所有引用到的dll,开箱即用。

链接:https://pan.baidu.com/s/1C4GEYoITqxiyB99Js8zq2g 
提取码:wvlz 

(3) 配置环境变量

配置环境变量的目的是不把重要的AccessKey直接暴露在代码内。

配置环境变量ALIBABA_CLOUD_ACCESS_KEY_IDALIBABA_CLOUD_ACCESS_KEY_SECRET

  • Linux和macOS系统配置方法

    执行以下命令:

    export ALIBABA_CLOUD_ACCESS_KEY_ID=<access_key_id>
    export ALIBABA_CLOUD_ACCESS_KEY_SECRET=<access_key_secret>

    <access_key_id>需替换为已准备好的AccessKey ID,<access_key_secret>替换为AccessKey Secret。

  • Windows系统配置方法

    1. 新建环境变量文件,添加环境变量ALIBABA_CLOUD_ACCESS_KEY_IDALIBABA_CLOUD_ACCESS_KEY_SECRET,并写入已准备好的AccessKey ID和AccessKey Secret。

    2. 重启Windows系统。

(4) 获取STSToken的实现

直接上代码

using AlibabaCloud.TeaUtil.Models;
using AlibabaCloud.OpenApiClient.Models;
using AlibabaCloud.SDK.Sts20150401.Models;
using STSClient = AlibabaCloud.SDK.Sts20150401.Client;
using System;

/*
    STS Token:通过调用AssumeRole接口扮演角色获取令牌凭证并调用openapi,临时凭证有过期时间,需要用户更新凭证。
    有权限的RAM用户可以使用自己的访问密钥调用AssumeRole接口,以获取某个RAM角色的STS Token,实现通过Credentials工具读取临时安全令牌(STS Token)
    访问云产品接口。STS Token具有时效性,到期后会自动失效。
    相比于AK硬编码方式访问接口,使用STS Token可有效减少因长期访问AK导致AK泄露的风险。
*/
public class STSToken
{
    //阿里云账号下的访问RAM用户所需的钥匙AccessKey,包括keyID和keySecret,这里已经配置好环境变量。
    private const string accessKeyID = "ALIBABA_CLOUD_ACCESS_KEY_ID";
    private const string accessKeySecret = "ALIBABA_CLOUD_ACCESS_KEY_SECRET";
    //sts服务器地址 参考 https://api.aliyun.com/product/Sts
    private const string endPointSTS = "sts.cn-beijing.aliyuncs.com";
    private const string regionId = "cn-beijing";
    //阿里云RAM角色的Arn
    private const string roleArn = "填写上面的用户角色Arn";
    //Token生效时长/秒,默认就是3600,可以改
    public const long effectiveDuration = 3600;
    private static STSClient CreateClient(string accessKeyId, string accessKeySecret)
    {
        Config config = new Config()
        {
            AccessKeyId = accessKeyId,
            AccessKeySecret = accessKeySecret,
            Endpoint = endPointSTS,
            RegionId = regionId
        };
        return new STSClient(config);
    }

    public static AssumeRoleResponseBody.AssumeRoleResponseBodyCredentials GetAssumeRoleData()
    {
        STSClient client = CreateClient(Environment.GetEnvironmentVariable(accessKeyID), Environment.GetEnvironmentVariable(accessKeySecret));
       
        AssumeRoleRequest assumeRoleRequest = new AssumeRoleRequest
        {
            RoleArn = roleArn,
            RoleSessionName = "随便起个名,不可以中文",
            DurationSeconds = effectiveDuration
        };
        RuntimeOptions runtime = new RuntimeOptions();
        try
        {
            AssumeRoleResponse arr = client.AssumeRoleWithOptions(assumeRoleRequest, runtime);
            //返回演员角色数据,包括keyID keySecret 和Token
            return arr.Body.Credentials;
        }
        catch (Tea.TeaException error)
        {
          // Debug.LogError(error);
        }
        return null;
    }
}

七、代码发送短信验证码

上代码

using AlibabaCloud.TeaUtil.Models;
using AlibabaCloud.OpenApiClient.Models;
using AlibabaCloud.SDK.Dysmsapi20170525;
using AlibabaCloud.SDK.Sts20150401.Models;
using AlibabaCloud.SDK.Dysmsapi20170525.Models;
using CreConfig = Aliyun.Credentials.Models.Config;
using System;

using System.Text.RegularExpressions;

    public class SMS
    {
        //签名名称
        private const string signName = "标题四申请的签名名称";
        //模板Code
        private const string templateCode = "标题四申请的模板Code";
        //短信供应商域名
        private const string endpoint = "dysmsapi.aliyuncs.com";
        private const string regionId = "cn-beijing";

        /// <summary>
        /// phoneNumbers 可以填多个手机号,以,作为分隔符
        /// </summary>
        /// <param name="phoneNumbers"></param>
        public static void Send(string phoneNumbers)
        {

            //这里先做个正则校验,手机号是否合规,不合规抛异常

             if (!IsPhoneNumber(phoneNumbers))
            {
                throw new Exception($"{phoneNumbers}:不合规,请检查手机号");
            }
            Config config = GetConfig();
            Client client = new Client(config);
            SendSmsRequest sendSmsRequest = new SendSmsRequest
            {
                SignName = signName,
                TemplateCode = templateCode,
                PhoneNumbers = phoneNumbers,
                TemplateParam = GenerateSMSCode(6)
            };

            RuntimeOptions runtime = new RuntimeOptions();
            try
            {
                SendSmsResponse response = client.SendSmsWithOptions(sendSmsRequest, runtime);
               //Debug.Log(response.Body.Code);
            }
            catch (Tea.TeaException error)
            {
                //Debug.LogError(error.Message);
            }
        }
        private static DateTime lastGenerateTime;
        private static AssumeRoleResponseBody.AssumeRoleResponseBodyCredentials data;
        private static Config GetConfig()
        {
            //检查token是否过期
            if (data == null || VerifyExpiration(lastGenerateTime, DateTime.Now))
            {
                data = STSToken.GetAssumeRoleData();
                lastGenerateTime = DateTime.Now;
            }
            if (data == null)
            {
                throw new Exception("Token申请失败");
            }

           // Debug.Log(data.SecurityToken);
            //使用STS创建Config
            CreConfig creConfig = GetCreConfig(data);
            Config config = new Config()
            {
                Credential = new Aliyun.Credentials.Client(creConfig),
                Endpoint = endpoint,
                RegionId = regionId
            };
            return config;
        }

        private static CreConfig GetCreConfig(AssumeRoleResponseBody.AssumeRoleResponseBodyCredentials data)
        {
            CreConfig config = new CreConfig
            {
                Type = "sts",
                AccessKeyId = data.AccessKeyId,
                AccessKeySecret = data.AccessKeySecret,
                SecurityToken = data.SecurityToken
            };
            return config;
        }

        

        private static bool IsPhoneNumber(string phoneNumbers)
        {
            string[] phoneNumbersSplit = phoneNumbers.Split(',');
            for (int i = 0; i < phoneNumbersSplit.Length; i++)
            {
                if (!IsMobile(phoneNumbersSplit[i]))
                {
                    return false;
                }
            }
            return true;
        }

        /// <summary>
        /// 验证手机号码
        /// </summary>
        /// <param name="mobile"></param>
        /// <returns></returns>
        public static bool IsMobile(string mobile)
        {
            if (string.IsNullOrEmpty(mobile))
                return false;
            return Regex.IsMatch(mobile, @"^(1)\d{10}$");
        }

        private static string GenerateSMSCode(int randomCount)
        {
            // json---->{code:257781}
            System.Text.StringBuilder sb = new();
            sb.Append("{");
            sb.Append("code:");
            for (int i = 0; i < randomCount; i++)
            {
                sb.Append(UnityEngine.Random.Range(i == 0 ? 1 : 0, 10));
            }
            sb.Append("}");
            return sb.ToString();
        }
        /// <summary>
        /// 是否过期
        /// </summary>
        /// <param name="lastTime"></param>
        /// <param name="currentTime"></param>
        /// <returns></returns>
        private static bool VerifyExpiration(DateTime lastTime, DateTime currentTime)
        {
            TimeSpan t = currentTime - lastTime;
            return t.TotalSeconds >= STSToken.effectiveDuration;
        }
    }

到这里就结束啦,只需要调用Send方法输入手机号就可以接收到短信验证码了,并且缓存了Token,在Token有效期内复用安全又可靠。

最终效果

八、总结

本篇文章主要讲解的是通过阿里云平台发送短信验证码操作流程,实现了短信验证码单条发送的功能。最主要的身份鉴权功能已经完成,其他功能实现起来非常容易(比如说短信的批量发送)。

想了解更多短信服务可以查看阿里云官方相关文档,并且官网上提供了代码测试平台可以很方便的进行API测试。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐