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

Posted

技术标签:

【中文标题】MVC 5 应用 - 实现 OAuth 授权代码流【英文标题】:MVC 5 application - implement OAuth Authorization code flow 【发布时间】:2014-09-15 10:00:49 【问题描述】:

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

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

我加了

app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());

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

app.UseOAuthBearerAuthentication(new Microsoft.Owin.Security.OAuth.OAuthBearerAuthenticationOptions()

    Provider = new OAuthBearerAuthenticationProvider()
);

但我也坚持执行 Provider 的事件。 任何人都可以指导我正确的方向吗?或者有什么教程可以帮助我吗?

【问题讨论】:

【参考方案1】:

我最终根据 Brock Allen 的这两篇文章得出了一个解决方案:

http://brockallen.com/2013/10/24/a-primer-on-owin-cookie-authentication-middleware-for-the-asp-net-developer/ http://brockallen.com/2014/01/09/a-primer-on-external-login-providers-social-logins-with-owinkatana-authentication-middleware/

基本思路是注册两个认证中间件。主动 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。它的登录方法必须将用户重定向到您的授权服务器的登录页面以获取授权码。您必须提供一个回调函数,您将在其中处理授权代码。

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-authentication 中间件并使用访问令牌作为您的 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 授权代码流的人有用。

【讨论】:

感谢分享。看到所有这些示例都使用天真的UseTwitterAuthenticationUseGoogleAuthentication 等,但没有使用自定义 OAuth 端点,这令人难以置信。你会认为前面提到的那些方法会使用某种常见的抽象...... 如何调试 var authenticateResult = await ctx.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ExternalBearer); ?我得到一个空结果。 Megamind-我在 AuthenticateAsync 方面得到了这个工作。它返回空值。它返回 null。 我在这一行也得到了一个空结果: var authenticateResult = await ctx.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ExternalBearer); sb 能解决这个问题吗? 我需要它的源代码,谁能帮帮我?【参考方案2】:

我使用了官方示例MVC Implicit Client,我认为这是 MVC 应用程序的正确身份验证流程。

对于授权,我使用了这个getting started,尤其是当指定角色[Authorize(Roles = "Foo,Bar")] 并且用户经过身份验证但不拥有这些时,关于无限循环的部分。

【讨论】:

以上是关于MVC 5 应用 - 实现 OAuth 授权代码流的主要内容,如果未能解决你的问题,请参考以下文章

如何使用代码授权流在 Spring 应用程序中提取 Oauth2 访问令牌?

OpenId Connect 问题 - 授权代码流 (OAuth 2.0)

从客户端 JS 代码中使用 OAuth 授权代码流令牌是不是安全?

入门教程:.NET开源OpenID Connect 和OAuth解决方案IdentityServer v3 MVC认证与授权

在带有 PKCE 的 OAuth 授权流中使用时如何在 Azure 应用注册中启用 CORS?

授权代码流后,Spring OAuth2 服务器没有响应刷新令牌