如何在WP8应用中安全的使用Azure Blob存储

前面一篇文章中,我们演示了在Windows store应用中安全的使用Azure Blob存储的步骤。Windows Phone上的步骤与此类似,只是在客户端代码以及设置方面有一些区别。但是为了方便读者阅读,我这里将就Windows Phone应用中如何安全的使用Azure Blob存储单独写一遍。这样对于Windows Phone开发者来说,只需要看这篇文章就够了。

我们已经在这篇文章中演示了在Windows Phone应用中使用Azure Blob存储的基本步骤,但是,对于一个商业应用来说,保证数据的安全性是很重要的一环。上次文章的代码中,对Blob的访问权限是通过PublicAccess来控制的,理论上如果PublicAccess设置为OFF,那么第三方就应该无法访问该Blob。但是这里有一个明显的安全隐患:我们的代码中明文存储了Access Key字符串,而通过一些反编译工具第三方能够很容易的获得这个字符串。如果这个Access Key被暴露的话,那么Blob中的内容就毫无秘密可言了。为此,我们需要找到一个可靠的办法来保证验证信息的安全。

为了解决这个问题,我们需要做到以下两条:

1.保证用户在未经授权的情况下无法获得验证信息。

2.用户通过授权后获得的验证信息不能被重复使用。

对于第一条,我们可以自己建立一个服务器,与该服务器的连接需要先进行身份验证,然后从服务器上获得用于连接Windows Azure 存储服务的验证信息。不过我们既然已经使用了Windows Azure,那么完全可以使用Windows Azure 云服务来为我们做同样的事情。

对于第二条,Windows Azure 存储服务提供了共享访问签名(Shared Access Signature)来保证验证信息的时效性。共享访问签名是一个在特定时间间隔内授予容器、Blob以及其他存储对象受限访问权限的 URI。也就是说,共享访问签名是一个URI, 客户端通过这个URI能够在规定时间内访问容器和Blob, 而超过了时间段的话这个URI就无效了,需要重新获取。

结合这两种方式,那么我们就能够实现对验证信息的保护了。我们将生成共享访问签名的代码放在Windows Azure云服务上,客户端通过访问云服务接口获得共享访问签名来访问Windows Azure存储服务商的Blob。

那么让我们来看看如果要安全的实现与前一篇文章相同的功能所需要完成的步骤。

一. 创建云服务并实现服务接口:

1. 从以下链接下载并安装Azure Cloud Service SDK For .NET:

https://www.windowsazure.com/en-us/downloads/?sdk=net

针对不同的Visual Studio版本,您需要安装相应的SDK,这样在您的Visual Studio的项目模板中会出现Azure Cloud Service的模板。

2. 通过Azure Cloud Service模板创建一个Cloud服务,在项目向导中加入WCF Service Web Role:

 

完成后项目向导会自动建立两个项目,一个是Cloud Service项目WindowsAzure1,另一个是提供WCF Service的Web Role项目WCFServiceWebRole1。WCFServiceWebRole1中会生成一个名为IService1的接口及实现该接口Service1类。

 

3. 修改WCFServiceWebRole1项目中的IService1接口添加用于获取共享访问签名的接口函数:

    public interface IService1

    {

        [OperationContract]

        string[] GetUploadSAS(string ContainerName, string BlobName);

        [OperationContract]

        string[] GetDownloadSAS(string ContainerName, string BlobName);

    }

这两个接口函数一个用来获得用于上传的共享访问签名,一个用来获得用于下载的共享访问签名,它们都接收两个参数:容器名称和Blob名称,返回一个含有两个字符串的字符串数组,第一个字符串用来存放用于访问Blob的URI,第二个字符串用来存放共享访问签名。

4. 在WCFServiceWebRole1项目的Service1类中实现GetUploadSAS函数用来返回用于上传的共享访问签名,与上一篇演示的图片上传代码类似,我们首先使用账号名与访问密钥建立Blob客户端对象实例,然后根据容器名得到容器的对象实例,如果容器不存在的话则建立容器并且关闭对匿名用户的访问权限。然后,我们通过Blob名获得Blob块对象实例,通过该实例调用GetSharedAccessSignature获得一个具有5分钟读写访问权限的共享访问签名。

        public string[] GetUploadSAS(string ContainerName, string BlobName)

        {

            string[] blobInfo = new string[2];

            if (string.IsNullOrEmpty(ContainerName) || string.IsNullOrEmpty(BlobName))

            {

                throw new ArgumentNullException();

            }

 

           var credentials = new StorageCredentials(accountName, accessKey);

            var account = new CloudStorageAccount(credentials, true);

            var blobClient = account.CreateCloudBlobClient();

            var container = blobClient.GetContainerReference(ContainerName);

            container.CreateIfNotExists();

            BlobContainerPermissions containerPermissions = new BlobContainerPermissions();

            containerPermissions.PublicAccess = BlobContainerPublicAccessType.Off;

            container.SetPermissions(containerPermissions);

 

            var blob = container.GetBlockBlobReference(BlobName);

            blobInfo[0] = blob.Uri.AbsoluteUri;

            blobInfo[1] = blob.GetSharedAccessSignature(new SharedAccessBlobPolicy()

            {

                SharedAccessExpiryTime = DateTime.UtcNow.AddMinutes(5),

                Permissions = SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.Read

            });

            return blobInfo;

        }

 5. 在WCFServiceWebRole1项目的Service1类中实现GetDownloadSAS函数用来返回用于下载图片的共享访问签名,代码与步骤4类似,首先使用账号名与访问密钥建立Blob客户端对象实例,然后根据容器名得到容器的对象实例,接下来就可以通过Blob名获得Blob块对象实例,调用该实例的GetSharedAccessSignature函数来获得一个具有5分钟只读访问权限的共享访问签名:

        public string[] GetDownloadSAS(string ContainerName, string BlobName)

        {

            string[] blobInfo = new string[2];

            if (string.IsNullOrEmpty(ContainerName) || string.IsNullOrEmpty(BlobName))

            {

                throw new ArgumentNullException();

            }

 

            var credentials = new StorageCredentials(accountName, accessKey);

            var account = new CloudStorageAccount(credentials, true);

            var blobClient = account.CreateCloudBlobClient();

            var container = blobClient.GetContainerReference(ContainerName);

           

            var blob = container.GetBlockBlobReference(BlobName);

            blobInfo[0] = blob.Uri.AbsoluteUri;

            blobInfo[1] = blob.GetSharedAccessSignature(new SharedAccessBlobPolicy()

            {

                SharedAccessExpiryTime = DateTime.UtcNow.AddMinutes(5),

                Permissions = SharedAccessBlobPermissions.Read

            });

            return blobInfo;

        }

 

6. 完成上述代码以后,就可以对该Azure应用点击Build->Publish将该服务发布Windows Azure上去了:

     a. 发布向导首先会让您提供您的Windows Azure账号,如果您已经登录过,那么可以直接在下拉框中选中,如果您是第一次登录,那么需要按Sign In按钮输入账号信息。然后按Next进入下一页:

 

     b. 如果您的Windows Azure上没有创建过Cloud Service,那么您需要创建一个新的Cloud Service,并且设定服务器位置。或者您也可以选择覆盖一个已有的Cloud Service。这里我们将新建的Cloud Service取名为SASService:

 

     c. 完成设置之后,发布向导会将该Cloud Service及应用部署到您的Windows Azure上去,您可以通过Windows Azure管理门户确认该Cloud Service以及应用是否已经部署成功。当Cloud Service部署完成后,Service Status会显示Created, Production会显示Running:

 

    d. 点击该Cloud service的URL进入提供WCF Service的网站,我们可以看到,该网站提供的WCF Service是Service1.svc。所以访问该WCF Service的URL就是https://sasservice.cloudapp.net/Service1.svc

 

     e. 打开这个URL,确认该WCF Service可用,并且可以得到在客户端调用该服务的C#示例代码。

 

 

二. 在客户端使用共享访问签名来上传和下载图片。

         1. 在Windows Phone应用项目中点击Project->Add service Reference,将刚才获得的service URL填入Address中,按确定添加该Service Reference:

 

 

2. 在Windows Phone应用中添加一个用于图片上传的按钮,在Click的处理函数中加入上传代码。这里需要注意的是,和Windows Store应用不同,WCF Service对Windows Phone客户端暴露出来的接口是同步的而不是异步的。因此这边要用事件响应的方式获得WCF Service接口的返回值。我们首先创建WCF Service客户端实例,然后添加对GetUploadedSASCompleted事件的响应函数,然后再调用GetUploadSASAsync函数。在GetUploadedSASCompleted事件的响应函数中,我们可以获得访问Blob的URI和共享访问签名。根据共享访问签名,我们可以生成存储证明进而通过Blob的URI访问该Blob。得到了该Blob的实例之后,就可以调用UploadFromFileAsync或UploadFromStreamAsync函数来上传文件或流数据到Windows Azure的存储服务了。

        private void btnUpload_Click(object sender, RoutedEventArgs e)

        {

              Service1Client client = new Service1Client();

              client.GetUploadSASCompleted +=client_GetUploadSASCompleted;           

              client.GetUploadSASAsync("imagecontainer", "imageblob");

        }

        private async void client_GetUploadSASCompleted(object sender, GetUploadSASCompletedEventArgs e)

        {

            var blobInfo = e.Result;

            StorageCredentials credentials = new StorageCredentials(blobInfo[1]);

            CloudBlockBlob blockBlob = new CloudBlockBlob(new Uri(blobInfo[0]), credentials);

 

            //upload from the local folder

            StorageFolder local = Windows.Storage.ApplicationData.Current.LocalFolder;

            await blockBlob.UploadFromFileAsync(local.Path + @"\image.jpg", FileMode.Open);

 

            //upload to the stream           

            var img = await local.GetFileAsync("image.jpg");

            var stream = await img.OpenStreamForReadAsync();

            await blockBlob.UploadFromStreamAsync(stream);

        }

3. 同样,再添加一个用于下载图片的按钮,在Click的处理函数中加入下载代码,过程与步骤二类似,只是这里需要调用GetDownloadSASAsync来获得Blob的URI和共享访问签名。在获得了Blob实例之后需要调用DownloadToFileAsync和DownloadToStreamAsync来下载Blob数据到文件或者数据流中:

        private void btnDownload_Click(object sender, RoutedEventArgs e)

        {

            Service1Client client = new Service1Client();

            client.GetDownloadSASCompleted +=client_GetDownloadSASCompleted;

            client.GetDownloadSASAsync("imagecontainer", "imageblob");

        }

        private async void client_GetDownloadSASCompleted(object sender, GetDownloadSASCompletedEventArgs e)

        {

            var blobInfo = e.Result;

            StorageCredentials credentials = new StorageCredentials(blobInfo[1]);           

            CloudBlockBlob blockBlob = new CloudBlockBlob(new Uri(blobInfo[0]), credentials);

 

            //download to the local folder

            StorageFolder local = Windows.Storage.ApplicationData.Current.LocalFolder;

            await blockBlob.DownloadToFileAsync(local.Path + @"\image.jpg", FileMode.CreateNew);

             //download to the stream

            StorageFile img = await local.CreateFileAsync("image.tmp");

            Stream stream = await img.OpenStreamForWriteAsync();

            await blockBlob.DownloadToStreamAsync(stream);

        }

完成了以上两大步骤以后,我们就可以通过共享签名的方式来安全的存取Windows Azure上的Blob数据了。

 三. 进一步提高WCF Service的可靠性和安全性

我们已经实现了对Windows Azure上Blob数据的安全存取。但是这还不足以应用到生产环境。在生产环境,我们还需要保证WCF Service的高可靠性。设置服务器的负载均衡是一个很好的解决办法。下面就让我们来介绍一下如何修改WCFServiceWebRole1的设定以实现负载均衡。

1.在Cloud Service项目WindowsAzure1的Roles文件夹中,右键点击WCFServiceWebRole1选择Properties打开属性页。

 

2.在Configuration页面中将Instance count设为2或更多,Windows Azure会该为Cloud Service创建相应数目虚拟机做负载平衡。这样如果其中一台虚拟机出问题的时候其他虚拟机还能继续服务,从而保证了服务的稳定性。

 

另外,第三方还有可能通过抓包的方式来获取身份验证及数据信息,虽然因为我们使用了共享访问签名,可以设置该身份验证信息的有效时间,但还是可能形成隐患。为了避免这种情况,我们需要使用https与Windows Azure进行连接。由于Windows Phone客户端和Storage Service的连接已经是https的了,所以这里只需要关心Windows Phone客户端和Cloud Service的连接设置。下面就是设定与Cloud Service的通讯为https连接的步骤:

1.为了采用https连接,我们需要一张SSL证书。而在实际生产环境中,您需要使用一张受信任的证书或者公司颁发的证书。出于演示目的,这里我使用了一张自签名的证书,在Developer Command Prompt for VS2013中以管理员身份运行makecert命令可以帮助我们创建一张自签名证书:

C:\>makecert -r -pe -n CN="sasservice.cloudapp.net " -b 03/13/2014 -e 03/13/2015 -eku 1.3.6.1.5.5.7.3.1 -sky exchange -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12 -sv sasCert.pvk sasCert.cer

2.期间您需要设定并输入私钥的保护口令。生成的证书包含两个文件,一个是pvk文件,用于存储证书私钥,一个是cer文件,用于存储证书公钥。关于如何使用makecert命令的详细信息,您可以参考以下链接:

https://msdn.microsoft.com/zh-cn/library/bfsktky3(v=vs.110).aspx

3.接下来您需要用pvk2pfx命令生成pfx文件以供后用:

C:\>pvk2pfx -pvk sasCert.pvk -pi “<pvk password>” -spc sasCert.cer -pfx sasCert.pfx -po “<pfx password>”

pfx文件中包含了证书公钥和私钥。-pi参数用来设定前面输入的私钥的保护口令,-po参数用来设定输出的pfx文件的私钥的保护口令。

4.在客户端右键点击sasCert.cer文件,选择Install certificate安装该证书,按照导入向导一步步将证书安装到Local Machine的Personal存储中。

5. 在Windows Azure管理门户进入前面建立的名为sasservice的云服务管理界面,点击Certificates标签页,在该页面中点击Upload弹出上传向导,根据向导将第3步生成的pfx文件上传到服务端。

 

 6.回到在Cloud Service项目WindowsAzure1的Roles文件夹,右键点击WCFServiceWebRole1选择Properties打开属性页,在Certificates标签页中添加证书,Store Location选择Local Machine, Store Name选择My,Thumbprint选择步骤4中已经安装到客户端的sasservice证书。Cloud Service项目需要改Thumprint信息以便能够在服务端找到步骤5中上传的那张带私钥的证书。

 

7.在Endpoints标签页中,将Endpoint的Protocol改为https, Public Port改为443. SSL Certificate Name选择步骤6中添加的证书名Certificate1:

 

8. 打开WCFServiceWebRole1的web.config文件,加入以下设置使得WCF服务支持https:

<configuration>

 <system.serviceModel>

    <services>

      <service name="WCFServiceWebRole1.Service1">

        <endpoint address=""

                  binding="basicHttpBinding"

                  bindingConfiguration="secureHttpBinding"

                  contract="WCFServiceWebRole1.IService1"/>

        <endpoint address="mex"

                  binding="mexHttpsBinding"

                  contract="IMetadataExchange" />

      </service>

    </services>

    <bindings>

      <basicHttpBinding>

        <binding name="secureHttpBinding">

          <security mode="Transport">

            <transport clientCredentialType="None"/>

          </security>

        </binding>

      </basicHttpBinding>

    </bindings>

    <behaviors>

      <serviceBehaviors>

        <behavior>

         <serviceMetadata httpGetEnabled="false" httpsGetEnabled="true"/>

        </behavior>

      </serviceBehaviors>

    </behaviors>

</configuration>

 9. 对WindowsAzure1项目点击Build->Publish将该服务重新发布Windows Azure上,这样Cloud Service就只允许通过https来访问了。

10. 在Windows Phone应用客户端更新Service Reference,将原来的支持http的Service Renference删除,使用以下支持https的URL为应用重新添加Service Reference:

https://sasservice.cloudapp.net/Service1.svc

11. 由于Windows Phone应用并没有可以直接设置证书的地方,我们需要在应用中使用代码安装前面生成的那张证书。为了演示起见,我首先将证书加入到项目中去,在属性中设置Build Action为Content, Copy to Output Directory为Copy if newer。在界面上添加了一个Button用于安装该证书:

private async void btnCertificate_Click(object sender, RoutedEventArgs e)

{

        var cert =await Package.Current.InstalledLocation.GetFileAsync(@"Assets\sasCert.cer");

        await Launcher.LaunchFileAsync(cert);

}

         在安装了证书以后,Windows Phone应用客户端就可以通过https来与Cloud Service进行通讯了。

 通过以上一系列操作,我们终于将可以在Windows Phone应用中安全的使用Windows Azure上的storage service了。由于步骤比较复杂,我这里附上了完整的示例代码以供大家参考。

 

 

AzureStorageDemoWP.zip