如何在Windows store应用中安全的使用Azure Blob存储(1)

上一篇文章中,我们演示了在Windows store应用中使用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 Store应用项目中点击Project->Add service Reference,将刚才获得的service URL填入Address中,按确定添加该Service Reference:

 

2.  在Windows Store应用中添加一个用于图片上传的按钮,在Click的处理函数中加入上传代码。我们首先创建WCF Service客户端实例,然后通过调用该实例的GetUploadSASAsync函数获得访问Blob的URI和共享访问签名。根据共享访问签名,我们可以生成存储证明进而通过Blob的URI访问该Blob。得到了该Blob的实例之后,就可以调用UploadFromFileAsync或UploadFromStreamAsync函数来上传文件或流数据到Windows Azure的存储服务了。

            // Use the client to call GetUploadSASAsync on the cloud service.           

            Service1Client client = new Service1Client();

            var blobInfo = await client.GetUploadSASAsync("imagecontainer", "imageblob");

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

 

            //upload from a file

            StorageFolder library = Windows.Storage.KnownFolders.PicturesLibrary;

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

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

            await blockBlob.UploadFromFileAsync(img);

 

            //upload from a stream

            var stream = await img.OpenReadAsync();

            await blockBlob.UploadFromStreamAsync(stream.GetInputStreamAt(0));

      

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

            // Use the client to call GetDownloadSASAsync on the cloud service.          

            Service1Client client = new Service1Client();

            var blobInfo = await client.GetDownloadSASAsync("imagecontainer", "imageblob");          

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

           

            //download the file to the pictures library

            StorageFolder library = Windows.Storage.KnownFolders.PicturesLibrary;

            var img = await library.CreateFileAsync("image1.jpg", CreationCollisionOption.ReplaceExisting);

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

            await blockBlob.DownloadToFileAsync(img);

 

            //download to the in-memory stream

            InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream();

            await blockBlob.DownloadToStreamAsync(stream.GetOutputStreamAt(0));

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