在Windows Azure移动服务中实施自定义身份验证

问题描述:

Windows Azure移动服务当前没有用于自定义身份验证和查看功能请求的选项

Windows Azure Mobile Services currently doesn't have an option for custom authentication and looking at the feature request

http://feedback.azure.com/forums/216254 -mobile-services/suggestions/3313778-custom-user-auth

它不会很快到来.

在.NET后端和.NET应用程序中,您如何实现自定义身份验证,这样您就不必使用Facebook,Google或它们的任何其他当前提供程序?

With a .NET backend and a .NET application how do you implement custom authentication, so that you don't have to use Facebook, Google or any of their other current providers?

关于如何使用JS后端以及iOS和Android来完成此工作,有很多部分完成的教程,但是.NET示例在哪里?

There are plenty of partially completed tutorials on how this this is done with a JS backend and iOS and Android but where are the .NET examples?

我终于在下面列出的文章,一些智能感知以及一些反复试验的帮助下完成了该解决方案.

I finally worked through the solution, with some help of the articles listed below, some intellisense and some trial and error.

WAMS的工作方式

首先,我想以一种非常简单的形式描述WAMS是什么,因为这部分使我困惑了一段时间,直到它最终被点击为止. WAMS只是打包用于快速部署的现有技术的集合.在这种情况下,您需要了解的是:

First I wanted to describe what WAMS is in a very simple form as this part confused me for a while until it finally clicked. WAMS is just a collection of pre-existing technologies packaged up for rapid deployment. What you need to know for this scenario is:

如您所见,WAMS实际上只是WebAPI和其他内容的容器,在此不做详细介绍.在Azure中创建新的移动服务时,您将下载一个包含WebAPI的项目.他们使用的示例是TodoItem,因此您将在项目中看到此方案的代码.

As you can see WAMS is really just a container for a WebAPI and other things, which I won't go into detail here. When you create a new Mobile Service in Azure you get to download a project that contains the WebAPI. The example they use is the TodoItem, so you will see code for this scenario through the project.

下面是您从中下载此示例的地方(我只是在做Windows Phone 8应用)

Below is where you download this example from (I was just doing a Windows Phone 8 app)

我可以对此做进一步的介绍,但是本教程将帮助您入门:

I could go on further about this but this tutorial will get you started:

http://azure.microsoft.com/zh-CN/documentation/articles/mobile-services-dotnet-backend-windows-store-dotnet-get-started/

设置WAMS项目

您将需要您的MasterKey和ApplicationKey.您可以从Azure门户获取它们,单击您的Mobile Services应用,然后按底部的管理密钥".

You will need your MasterKey and ApplicationKey. You can get them from the Azure Portal, clicking on your Mobile Services App and pressing Manage Keys at the bottom

您刚刚下载的项目,在Controllers文件夹中,我刚刚创建了一个名为AccountController.cs的新控制器,并在其中放入了

The project you just downloaded, in the Controllers folder I just created a new controller called AccountController.cs and inside I put

    public HttpResponseMessage GetLogin(String username, String password)
    {
        String masterKey = "[enter your master key here]";
        bool isValidated = true;

        if (isValidated)
            return new HttpResponseMessage() { StatusCode = HttpStatusCode.OK, Content = new StringContent("{ 'UserId' : 'F907F58C-09FE-4F25-A26B-3248CD30F835', 'token' : '" + GetSecurityToken(new TimeSpan(1,0, 0), String.Empty, "F907F58C-09FE-4F25-A26B-3248CD30F835", masterKey)  + "' }") };
        else
            return Request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Username and password are incorrect");

    }

    private static string GetSecurityToken(TimeSpan periodBeforeExpires, string aud, string userId, string masterKey)
    {
        var now = DateTime.UtcNow;
        var utc0 = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
        var payload = new
        {
            exp = (int)now.Add(periodBeforeExpires).Subtract(utc0).TotalSeconds,
            iss = "urn:microsoft:windows-azure:zumo",
            ver = 2,
            aud = "urn:microsoft:windows-azure:zumo",
            uid = userId
        };

        var keyBytes = Encoding.UTF8.GetBytes(masterKey + "JWTSig");
        var segments = new List<string>();

        //kid changed to a string
        var header = new { alg = "HS256", typ = "JWT", kid = "0" };
        byte[] headerBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(header, Formatting.None));
        byte[] payloadBytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(payload, Formatting.None));
        segments.Add(Base64UrlEncode(headerBytes));
        segments.Add(Base64UrlEncode(payloadBytes));
        var stringToSign = string.Join(".", segments.ToArray());
        var bytesToSign = Encoding.UTF8.GetBytes(stringToSign);
        SHA256Managed hash = new SHA256Managed();
        byte[] signingBytes = hash.ComputeHash(keyBytes);
        var sha = new HMACSHA256(signingBytes);
        byte[] signature = sha.ComputeHash(bytesToSign);
        segments.Add(Base64UrlEncode(signature));
        return string.Join(".", segments.ToArray());
    }

    // from JWT spec
    private static string Base64UrlEncode(byte[] input)
    {
        var output = Convert.ToBase64String(input);
        output = output.Split('=')[0]; // Remove any trailing '='s
        output = output.Replace('+', '-'); // 62nd char of encoding
        output = output.Replace('/', '_'); // 63rd char of encoding
        return output;
    }

您可以使用自己的验证代码替换GetLogin中的内容.验证后,它将返回所需的安全令牌(JWT).

You can replace what is in GetLogin, with your own validation code. Once validated, it will return a security token (JWT) that is needed.

如果要在本地主机上进行测试,请记住进入web.config文件并填写以下密钥

If you are testing on you localhost, remember to go into your web.config file and fill in the following keys

<add key="MS_MasterKey" value="Overridden by portal settings" />
<add key="MS_ApplicationKey" value="Overridden by portal settings" />

您需要在此处输入主密钥和应用密钥.当您上传它们时,它们将被覆盖,但是如果您在本地运行所有内容,则需要输入它们.

You need to enter in your Master and Application Keys here. They will be overridden when you upload them but they need to be entered if you are running everything locally.

在TodoItemController的顶部添加AuthorizeLevel属性,如下所示

At the top of the TodoItemController add the AuthorizeLevel attribute as shown below

[AuthorizeLevel(AuthorizationLevel.User)]
public class TodoItemController : TableController<TodoItem>

您将需要修改TodoItemController中的大多数功能,但这是获取全部"功能的示例.

You will need to modify most of the functions in your TodoItemController but here is an example of the Get All function.

    public IQueryable<TodoItem> GetAllTodoItems()
    {
        var currentUser = User as ServiceUser;

        Guid id = new Guid(currentUser.Id);

        return Query().Where(todo => todo.UserId == id);
    }

仅需注意一点,我将UserId用作Guid(uniqueidentifier),您需要将其添加到todo模型定义中.您可以将UserId设置为所需的任何类型,例如Int32

Just a side note I am using UserId as Guid (uniqueidentifier) and you need to add this to the todo model definition. You can make the UserId as any type you want, e.g. Int32

Windows Phone/商店应用

请注意,这只是一个示例,一旦工作,就应在主应用程序中清除代码.

Please note that this is just an example and you should clean the code up in your main application once you have it working.

在您的客户端应用上

安装NuGet软件包:Windows Azure移动服务

Install NuGet Package: Windows Azure Mobile Services

进入App.xaml.cs并将其添加到顶部

Go into App.xaml.cs and add this to the top

    public static MobileServiceClient MobileService = new MobileServiceClient(
          "http://localhost:50527/",
          "[enter application key here]"
    );

在我创建的MainPage.xaml.cs中

In the MainPage.xaml.cs I created

public class Token
{
    public Guid UserId { get; set; }
    public String token { get; set; }
}

在主类中添加一个Authenticate函数

In the main class add an Authenticate function

    private bool Authenticate(String username, String password)
    {
        HttpClient client = new HttpClient();
        // Enter your own localhost settings here
        client.BaseAddress = new Uri("http://localhost:50527/");

        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        HttpResponseMessage response = client.GetAsync(String.Format("api/Account/Login?username={0}&password={1}", username, password)).Result;

        if (response.StatusCode == System.Net.HttpStatusCode.OK)
        {
            var token = Newtonsoft.Json.JsonConvert.DeserializeObject<Token>(response.Content.ReadAsStringAsync().Result);

            App.MobileService.CurrentUser = new MobileServiceUser(token.UserId.ToString());
            App.MobileService.CurrentUser.MobileServiceAuthenticationToken = token.token;

            return true;
        }
        else
        {
            //Something has gone wrong, handle it here
            return false;
        }           

    }

然后在Main_Loaded函数中

Then in the Main_Loaded function

    private void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        Authenticate("test", "test");

        RefreshTodoItems();
    }

如果WebAPI中有断点,您将看到它,获取令牌,然后返回ToDoItemController,并且currentUser将填充UserId和令牌.

If you have break points in the WebAPI, you will see it come in, get the token, then come back to the ToDoItemController and the currentUser will be filled with the UserId and token.

您将需要创建自己的登录页面,因为使用此方法时,您无法将自动创建的登录页面与其他身份提供者一起使用.但是无论如何,我还是更喜欢创建自己的登录屏幕.

You will need to create your own login page as with this method you can't use the automatically created one with the other identity providers. However I much prefer creating my own login screen anyway.

在评论中让我知道其他任何问题,如果可以的话,我会帮忙.

Any other questions let me know in the comments and I will help if I can.

安全说明

记住要使用SSL.

参考

[] http://www.thejoyofcode.com/Exploring_custom_identity_in_Mobile_Services_Day_12_.aspx

[] http ://www.contentmaster.com/azure/creating-a-jwt-token-to-access-windows-azure-mobile-services/

[] http://chrisrisner.com/Custom-使用天蓝色的移动服务和LensRocket进行身份验证