ajax - 双重登录MVC和WebAPI

标签 ajax asp.net-mvc asp.net-web-api identityserver3

我开发了两个独立的应用程序:MVC 和 WebAPI。在 MVC 应用程序的某些页面上,我对 WebAPI 执行 ajax 请求。此外,我使用 IdentityServer3 作为身份验证/授权框架。

我已经根据 GitHub 上发布的教程/示例为 MVC 部分实现了基于 cookie 的身份验证,为 WebAPI 实现了基于 token 的身份验证。它们都按预期工作,但用户必须登录两次(分别在 MVC 和 WebAPI 中),这似乎是合理的,因为我使用了不同的身份验证类型。

是否可以以要求用户登录一次的方式使用IdentityServer3?我想知道通过 MVC 应用程序生成访问 token (在基于 cookie 的授权之后)并将其提供给应用程序的 JavaScript 部分(该 token 将在 ajax 调用期间使用)是否是一个好主意。我认为这个解决方案可以避免双重登录。我读过很多关于类似问题的帖子,但他们没有给出明确的答案。

编辑:

我遵循 Paul Taylor 的建议使用“混合流”,并且找到了几个示例来说明如何实现它(除其他事项外 this tutorial ),但我不知道如何执行有效的 ajax向 WebAPI 发出请求。目前,我收到 401 Unauthorized 错误,尽管 HTTP header Authorization: Bearer <access token>为所有 ajax 请求设置。

IdentityServer 项目

范围:

var scopes = new List<Scope>
{
    StandardScopes.OfflineAccess,
    new Scope
    {
        Enabled = true,
        Name = "roles",
        Type = ScopeType.Identity,
        Claims = new List<ScopeClaim>
        {
            new ScopeClaim(IdentityServer3.Core.Constants.ClaimTypes.Role, true)
        }
    },            
    new Scope
    {
        Enabled = true,
        DisplayName = "Web API",
        Name = "api",
        ScopeSecrets = new List<Secret>
        {
            new Secret("secret".Sha256())
        },
        Claims = new List<ScopeClaim>
        {
            new ScopeClaim(IdentityServer3.Core.Constants.ClaimTypes.Role, true)
        },
        Type = ScopeType.Resource
    }
};

scopes.AddRange(StandardScopes.All);

客户:

new Client
{
    ClientName = "MVC Client",
    ClientId = "mvc",
    Flow = Flows.Hybrid,
    ClientSecrets =
    {
        new Secret("secret".Sha256())
    },
    AllowedScopes = new List<string>
    {
        Constants.StandardScopes.OpenId,
        Constants.StandardScopes.Profile,
        Constants.StandardScopes.Email,
        Constants.StandardScopes.Roles,
        Constants.StandardScopes.Address,
        Constants.StandardScopes.OfflineAccess,
        "api"
    },
    RequireConsent = false,
    AllowRememberConsent = true,
    AccessTokenType = AccessTokenType.Reference,

    RedirectUris = new List<string>
    {
        "http://localhost:48197/"
    },
    PostLogoutRedirectUris = new List<string>
    {
        "http://localhost:48197/"
    },
    AllowAccessTokensViaBrowser = true
}

MVC应用项目

启动配置

const string AuthorityUri = "https://localhost:44311/identity";

public void Configuration(IAppBuilder app)
{
    JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();

    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AuthenticationType = "Cookies"
    });

    app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
    {
        ClientId = "mvc",
        Authority = AuthorityUri,
        RedirectUri = "http://localhost:48197/", 
        ResponseType = "code id_token",
        Scope = "openid profile email roles api offline_access",
        TokenValidationParameters = new TokenValidationParameters
        {
            NameClaimType = "name",
            RoleClaimType = "role"
        },
        SignInAsAuthenticationType = "Cookies",
        Notifications = new OpenIdConnectAuthenticationNotifications
        {
            AuthorizationCodeReceived = async n =>
            {
                var tokenClient = new TokenClient(AuthorityUri + "/connect/token", "mvc", "secret");

                TokenResponse tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(n.Code, n.RedirectUri);

                if (tokenResponse.IsError)
                    throw new Exception(tokenResponse.Error);

                UserInfoClient userInfoClient = new UserInfoClient(AuthorityUri + "/connect/userinfo");

                UserInfoResponse userInfoResponse = await userInfoClient.GetAsync(tokenResponse.AccessToken);

                ClaimsIdentity id = new ClaimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);
                id.AddClaims(userInfoResponse.Claims);

                id.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
                id.AddClaim(new Claim("expires_at", DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToLocalTime().ToString()));
                id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
                id.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
                id.AddClaim(new Claim("sid", n.AuthenticationTicket.Identity.FindFirst("sid").Value));

                n.AuthenticationTicket = new AuthenticationTicket(
                    new ClaimsIdentity(id.Claims, n.AuthenticationTicket.Identity.AuthenticationType, "name", "role"),
                    n.AuthenticationTicket.Properties);
            },
            RedirectToIdentityProvider = n => { // more code }
        }
    });
}

收到访问 token 后,我将其存储在 sessionStorage 中。

@model IEnumerable<System.Security.Claims.Claim>
<script>
    sessionStorage.accessToken = '@Model.First(c => c.Type == "access_token").Value';
</script>

以下 JavaScript 函数用于执行 ajax 请求:

function ajaxRequest(requestType, url, parameters)
{
    var headers = {};
    if (sessionStorage.accessToken) {
        headers['Authorization'] = 'Bearer ' + sessionStorage.accessToken;
    }

    $.ajax({
        url: url,
        method: requestType,
        dataType: 'json',
        data: parameters,
        headers: headers
    });
}

WebAPI 项目

启动配置:

JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();

app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
    Authority = "https://localhost:44311/identity",
    ClientId = "mvc",
    ClientSecret = "secret",
    RequiredScopes = new[] { "api", "roles" }
});

你能告诉我我做错了什么吗?

编辑(已解决)

我的 WebAPI 配置无效,因为术语具有误导性。原来是ClientIdClientSecret应包含范围名称及其 secret ( link to reported issue )。

以下 WebAPI 启动配置按预期工作:

app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
    Authority = "https://localhost:44311/identity",

    // It has been changed:
    ClientId = "api", // Scope name
    ClientSecret = "secret", // Scope secret

    RequiredScopes = new[] { "api", "roles" }
});

最佳答案

您需要使用IdentityServer3的“混合流”。

这是有关如何使用 IdentityServer3 实现它的教程。 https://identityserver.github.io/Documentation/docsv2/overview/mvcGettingStarted.html

此页面解释了混合流程的工作原理以及如何实现它(使用 IdentityServer4 - 与 IdentityServer3 不同,仍在积极开发中,以防您可以选择升级)。 http://docs.identityserver.io/en/release/quickstarts/5_hybrid_and_api_access.html .

关于ajax - 双重登录MVC和WebAPI,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47691355/

相关文章:

angularjs - Angular JS : JSON data is not getting displayed in ng-Grid

ajax - 如何扩展 socket.io?

javascript - ajax成功后删除div

javascript - 将 ajax 调用推广到函数中

c# - 如何在不重启 AppDomain 的情况下在运行时将模块添加到 MVC4

asp.net-mvc - 温莎城堡与 owin 和 Identity

c# - 为什么在具体解码base64string的时候需要先转bytes再转string?

javascript - 如何在没有本地主机的情况下调用 URL?

c# - 将默认值设置为 Html.DropDownListFor

javascript - 允许访问麦克风时,getUserMedia 失败并出现 TrackStartError