自几天以来,我一直在解决这个问题,但没有找到任何解决方案。
首先,我使用IdentyServer4创建了一个SSO服务器,这一部分有效,我可以进行身份验证
其次,我将Asp.Net.Identy添加到SSO服务器中,以管理用户和角色,并将所有这些都存储到持久性存储中。这部分也起作用。我已经创建了用户和角色,并将用户附加到角色。
第三,我创建了一个APS.NET MVC Web应用程序,该应用程序由SSO服务器保护并使用OWIN。我使用Authorize属性来保护控制器,并且该控制器中的一种方法要求是Administrator。
问题是,当我尝试访问此方法时,系统进入循环,从SSO服务器来回查询ProfileService。
现在有一些代码:
ProfileService进入SSO服务器:
using IdentityServer.Models;
using IdentityServer4.Extensions;
using IdentityServer4.Models;
using IdentityServer4.Services;
using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
namespace IdentityServer.Services
{
public class ProfileService : IProfileService
{
private readonly IUserClaimsPrincipalFactory<ApplicationUser> _claimsFactory;
private readonly UserManager<ApplicationUser> _userManager;
private readonly RoleManager<IdentityRole> _roleManager;
public ProfileService(UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager, IUserClaimsPrincipalFactory<ApplicationUser> claimsFactory)
{
_userManager = userManager;
_claimsFactory = claimsFactory;
_roleManager = roleManager;
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var sub = context.Subject.GetSubjectId();
var user = await _userManager.FindByIdAsync(sub);
var principal = await _claimsFactory.CreateAsync(user);
var claims = await _userManager.GetClaimsAsync(user);
claims = claims.Where(claim => context.RequestedClaimTypes.Contains(claim.Type)).ToList();
// Add custom claims in token here based on user properties or any other source
claims.Add(new Claim("employee_id", user.EmployeeId ?? string.Empty));
context.IssuedClaims = (List<Claim>)claims;
}
public async Task IsActiveAsync(IsActiveContext context)
{
var sub = context.Subject.GetSubjectId();
var user = await _userManager.FindByIdAsync(sub);
context.IsActive = user != null;
}
}
}
Startup.cs到Web MVC应用程序中
using Owin;
using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using System;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.Owin.Security.OpenIdConnect;
using System.Security.Claims;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using System.Linq;
using Microsoft.Owin.Extensions;
using Microsoft.Owin.Security.Authorization.Infrastructure;
using IdentityModel;
using System.Threading.Tasks;
using System.Collections.Generic;
[assembly: OwinStartup(typeof(WebApplication1.Startup))]
namespace WebApplication1
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
// Pour plus d'informations sur la configuration de votre application, visitez https://go.microsoft.com/fwlink/?LinkID=316888
app.UseAuthorization();
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationType = "Cookies",
ExpireTimeSpan = TimeSpan.FromSeconds(10),
SlidingExpiration = true
});
var tokenHandler = new JwtSecurityTokenHandler();
tokenHandler.InboundClaimFilter.Clear();
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "oidc",
SignInAsAuthenticationType = "Cookies",
Authority = "https://localhost:5001", //adresse du serveur IdentityServer
ClientId = "mvc", //nom du client au sens IdentityServer, doit être configuré dans la liste des client du fichier config.cs du serveur
RedirectUri = "https://localhost:44354/Home/About", //redirection si login ok doit -être dans la liste des RefirectUris du client configuré sur le serveur
PostLogoutRedirectUri = "https://localhost:44354/Home",
ResponseType = "id_token token",
Scope = "openid profile web_api",
UseTokenLifetime = true,
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = async n =>
{
var claims_to_exclude = new[]
{
"aud", "iss", "nbf", "exp", "nonce", "iat", "at_hash"
};
List<Claim> claims_to_keep =
n.AuthenticationTicket.Identity.Claims.Where(x => false == claims_to_exclude.Contains(x.Type)).ToList();
claims_to_keep.Add(new Claim("id_token", n.ProtocolMessage.IdToken));
if (n.ProtocolMessage.AccessToken != null)
{
claims_to_keep.Add(new Claim("access_token", n.ProtocolMessage.AccessToken));
//var userInfoClient = new UserInfoClient(EP_Configuration.epIdpUserInfoAccessPoint);
//var userInfoResponse = await userInfoClient.GetAsync(n.ProtocolMessage.AccessToken);
//var userInfoClaims = userInfoResponse.Claims; // filter sub since we're already getting it from id_token
//claims_to_keep.AddRange(userInfoClaims);
}
var ci = new ClaimsIdentity(
n.AuthenticationTicket.Identity.AuthenticationType);
ci.AddClaims(claims_to_keep);
n.AuthenticationTicket = new Microsoft.Owin.Security.AuthenticationTicket(
ci, n.AuthenticationTicket.Properties
);
},
RedirectToIdentityProvider = n =>
{
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
{
var id_token = n.OwinContext.Authentication.User.FindFirst("id_token")?.Value;
n.ProtocolMessage.IdTokenHint = id_token;
}
return Task.FromResult(0);
}
}
});
app.UseStageMarker(PipelineStage.Authenticate);
}
}
}
安全方法:
[Authorize(Roles = "Administrator")]
public ActionResult About()
{
ViewBag.Message = "Your application description page.";
return View();
}
当我单击调用此方法的页面中的链接时,上下文传递到服务器不包含要声明的角色:
您可以看到,TequestedClaimType为空。
另一件事,当我得到我的用户时,它没有附加任何角色列表:
同时,角色进入了Pincipal对象的标识:
我在这里和其他站点上阅读了很多文章,但到目前为止,我没有尝试解决问题。
欢迎任何帮助。
更新1:
我稍微更改了我的MVC客户端配置服务器端,以如下方式将作用域添加到客户端:首先,我添加了这个:
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResource("roles", new[] { "role" })
};
}
我向Alice用户添加了管理员角色。当我获得用户令牌时,token_Id为:
{
"nbf": 1580127045,
"exp": 1580127345,
"iss": "https://localhost:5001",
"aud": "mvc",
"nonce": "637157238434573534.ZWU0MjNhYTItZDU5Ni00YjQxLThlNzYtYTRkYjAyNGYxZDA5NDZiZmZmZTktMzYwZS00NzliLThkN2UtZTJlZDNjYzRkZGUx",
"iat": 1580127045,
"at_hash": "JaDEsHC7VCXRuLghvo-pZQ",
"sid": "47c344d69e4f20899c8c9f8594f5c47f",
"sub": "99de568f-49d8-4dd1-b256-14c2647504cd",
"auth_time": 1580121815,
"idp": "local",
"amr": [
"pwd"
]
}
{
"nbf": 1580127045,
"exp": 1580130645,
"iss": "https://localhost:5001",
"aud": [
"https://localhost:5001/resources",
"web_api"
],
"client_id": "mvc",
"sub": "99de568f-49d8-4dd1-b256-14c2647504cd",
"auth_time": 1580121815,
"idp": "local",
"name": "Alice Smith",
"given_name": "Alice",
"family_name": "Smith",
"email": "[email protected]",
"email_verified": "true",
"website": "http://alice.com",
"address": "{ 'street_address': 'One Hacker Way', 'locality': 'Heidelberg', 'postal_code': 69118, 'country': 'Germany' }",
"employee_id": "",
"role": "Administrator",
"scope": [
"openid",
"profile",
"roles",
"web_api"
],
"amr": [
"pwd"
]
}
在我的家庭控制器中,此代码始终返回false:
if (!User.IsInRole("Administrator"))
throw new SecurityException("User is not an admin.");
并且此调用循环在客户端和服务器之间:
[Authorize(Roles = "Administrator")]
public ActionResult About()
{
ViewBag.Message = "Your application description page.";
return View();
}
我最终通过从网络上收集不同的资源来使它正常工作。
我战斗时的主要问题是在startup.cs的MVC客户端中。
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync("https://localhost:5001");
var userInfoResponse = await client.GetUserInfoAsync( new UserInfoRequest {Address = disco.UserInfoEndpoint, Token= n.ProtocolMessage.AccessToken });
claims_to_keep.AddRange(userInfoClaims);
以及完整的内存类:
using Owin;
using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using System;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.Owin.Security.OpenIdConnect;
using System.Security.Claims;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using System.Linq;
using Microsoft.Owin.Extensions;
using Microsoft.Owin.Security.Authorization.Infrastructure;
using IdentityModel;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Net.Http;
using IdentityModel.Client;
[assembly: OwinStartup(typeof(WebApplication1.Startup))]
namespace WebApplication1
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
// Pour plus d'informations sur la configuration de votre application, visitez https://go.microsoft.com/fwlink/?LinkID=316888
app.UseAuthorization();
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationType = "Cookies",
ExpireTimeSpan = TimeSpan.FromSeconds(10),
SlidingExpiration = true
});
var tokenHandler = new JwtSecurityTokenHandler();
tokenHandler.InboundClaimFilter.Clear();
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "oidc",
SignInAsAuthenticationType = "Cookies",
Authority = "https://localhost:5001", //adresse du serveur IdentityServer
ClientId = "mvc", //nom du client au sens IdentityServer, doit être configuré dans la liste des client du fichier config.cs du serveur
RedirectUri = "https://localhost:44354/Home", //redirection si login ok doit -être dans la liste des RefirectUris du client configuré sur le serveur
PostLogoutRedirectUri = "https://localhost:44354/Home",
ResponseType = "id_token token",
Resource = "Role",
Scope = "openid profile web_api roles",
UseTokenLifetime = true,
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = async n =>
{
var claims_to_exclude = new[]
{
"test"//"aud", "iss", "nbf", "exp", "nonce", "iat", "at_hash"
};
List<Claim> claims_to_keep =
n.AuthenticationTicket.Identity.Claims.Where(x => false == claims_to_exclude.Contains(x.Type)).ToList();
claims_to_keep.Add(new Claim("id_token", n.ProtocolMessage.IdToken));
if (n.ProtocolMessage.AccessToken != null)
{
claims_to_keep.Add(new Claim("access_token", n.ProtocolMessage.AccessToken));
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync("https://localhost:5001");
var userInfoResponse = await client.GetUserInfoAsync( new UserInfoRequest {Address = disco.UserInfoEndpoint, Token= n.ProtocolMessage.AccessToken });
var userInfoClaims = userInfoResponse.Claims; // filter sub since we're already getting it from id_token
claims_to_keep.AddRange(userInfoClaims);
}
var ci = new ClaimsIdentity(
n.AuthenticationTicket.Identity.AuthenticationType);
ci.AddClaims(claims_to_keep);
n.AuthenticationTicket = new Microsoft.Owin.Security.AuthenticationTicket(
ci, n.AuthenticationTicket.Properties
);
},
RedirectToIdentityProvider = n =>
{
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
{
var id_token = n.OwinContext.Authentication.User.FindFirst("id_token")?.Value;
n.ProtocolMessage.IdTokenHint = id_token;
}
return Task.FromResult(0);
}
}
});
app.UseStageMarker(PipelineStage.Authenticate);
}
}
}