我开发了两个独立的应用程序: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 配置无效,因为术语具有误导性。原来是ClientId
和ClientSecret
应包含范围名称及其 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/