MVC 5应用程序 - 实现OAuth授权代码流程

时间:2014-09-15 10:00:50

标签: oauth oauth-2.0 asp.net-mvc-5 owin katana

基于本教程http://www.asp.net/aspnet/overview/owin-and-katana/owin-oauth-20-authorization-server,我创建了一个授权服务器,一个资源服务器和一个MVC客户端。 MVC客户端有一个Controller,它从资源服务器获取一些数据。资源服务器需要身份验证。 MVC客户端从授权服务器获取授权代码,并将用户重定向到授权服务器以进行身份​​验证。最后,MVC客户端交换访问令牌的授权码以访问资源服务器。这是OAuth 2协议所描述的授权代码流。这很好。

现在,我要求让MVC客户端的Controller本身需要身份验证。我找不到这方面的教程。

我添加了

app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());

到我的Startup.Auth.cs。 我假设,我需要设置选项以重定向到授权服务器。我也可以在选项上设置提供商:

app.UseOAuthBearerAuthentication(new Microsoft.Owin.Security.OAuth.OAuthBearerAuthenticationOptions()
{
    Provider = new OAuthBearerAuthenticationProvider()
});

但我也坚持实施提供者的事件。 有人能引导我朝正确的方向发展吗?或者是否有任何可能对我有帮助的教程?

2 个答案:

答案 0 :(得分:30)

我最终找到了基于Brock Allen这两篇文章的解决方案:

这个基本的想法是注册两个身份验证中间件。活动Cookie身份验证和被动OAuthBearer身份验证。在Startup.Auth.cs中,它们是这样添加的:

app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
    LoginPath = new PathString("/ExternalLogin/Login"),
});
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
{
    AuthenticationType = DefaultAuthenticationTypes.ExternalBearer,
    AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Passive,
});

您还添加了一个ExternalLogin-Controller。其Login-method必须将用户重定向到Authorization Server的Login-page以获取授权代码。您必须提供一个回调函数,您将在其中处理授权代码。

public async Task<ActionResult> Login(string returnUrl)
{
    if (string.IsNullOrEmpty(returnUrl) && Request.UrlReferrer != null)
        returnUrl = Server.UrlEncode(Request.UrlReferrer.PathAndQuery);

    if (Url.IsLocalUrl(returnUrl) && !string.IsNullOrEmpty(returnUrl))
        _returnUrl = returnUrl;

    //callback function
    _redirectUrl = Url.Action("AuthorizationCodeCallback", "ExternalLogin", null, Request.Url.Scheme);

    Dictionary<string, string> authorizeArgs = null;
    authorizeArgs = new Dictionary<string, string>
    {
        {"client_id", "0123456789"}
        ,{"response_type", "code"}
        ,{"scope", "read"}
        ,{"redirect_uri", _redirectUrl}
        // optional: state
    };

    var content = new FormUrlEncodedContent(authorizeArgs);
    var contentAsString = await content.ReadAsStringAsync();
    return Redirect("http://localhost:64426/oauth/authorize?" + contentAsString);
}

在您的回调函数中,您交换访问令牌(以及刷新令牌)的授权码,挑战您的被动OAuthBearer身份验证中间件,并使用Access令牌作为Cookie进行登录。

public async Task<ActionResult> AuthorizationCodeCallback()
{
    // received authorization code from authorization server
    string[] codes = Request.Params.GetValues("code");
    var authorizationCode = "";
    if (codes.Length > 0)
        authorizationCode = codes[0];

    // exchange authorization code at authorization server for an access and refresh token
    Dictionary<string, string> post = null;
    post = new Dictionary<string, string>
    {
        {"client_id", "0123456789"}
        ,{"client_secret", "ClientSecret"}
        ,{"grant_type", "authorization_code"}
        ,{"code", authorizationCode}
        ,{"redirect_uri", _redirectUrl}
    };

    var client = new HttpClient();
    var postContent = new FormUrlEncodedContent(post);
    var response = await client.PostAsync("http://localhost:64426/token", postContent);
    var content = await response.Content.ReadAsStringAsync();

    // received tokens from authorization server
    var json = JObject.Parse(content);
    _accessToken = json["access_token"].ToString();
    _authorizationScheme = json["token_type"].ToString();
    _expiresIn = json["expires_in"].ToString();
    if (json["refresh_token"] != null)
        _refreshToken = json["refresh_token"].ToString();

    //SignIn with Token, SignOut and create new identity for SignIn
    Request.Headers.Add("Authorization", _authorizationScheme + " " + _accessToken);
    var ctx = Request.GetOwinContext();
    var authenticateResult = await ctx.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ExternalBearer);
    ctx.Authentication.SignOut(DefaultAuthenticationTypes.ExternalBearer);
    var applicationCookieIdentity = new ClaimsIdentity(authenticateResult.Identity.Claims, DefaultAuthenticationTypes.ApplicationCookie);
    ctx.Authentication.SignIn(applicationCookieIdentity);

    var ctxUser = ctx.Authentication.User;
    var user = Request.RequestContext.HttpContext.User;

    //redirect back to the view which required authentication
    string decodedUrl = "";
    if (!string.IsNullOrEmpty(_returnUrl))
        decodedUrl = Server.UrlDecode(_returnUrl);

    if (Url.IsLocalUrl(decodedUrl))
        return Redirect(decodedUrl);
    else
        return RedirectToAction("Index", "Home");
}

我希望这对在MVC 5应用程序中实现OAuth授权代码流的人有用。

答案 1 :(得分:1)

我使用了官方样本MVC Implicit Client,我认为这是MVC应用程序的正确认证流程。

对于授权,我使用了这个getting started,特别是当指定角色 _Chanel = _Client.CreateChannel(); 并且用户经过身份验证但不拥有任何这些内容时,有关无限循环的部分。