Я застрял в этой проблеме уже несколько дней и не нашел решения.
Сначала я создал SSO-сервер с IdentyServer4, эта часть работает, я могу аутентифицировать
Второй Я добавил Asp. Net .Identy на сервер единого входа для управления пользователями и ролями и всем этим в постоянном хранилище. Эта часть тоже работает. Я создал пользователей и роли и прикрепил пользователей к ролям.
В-третьих, я создал веб-приложение APS. NET MVC, защищенное моим сервером единого входа и использующее OWIN. Я использую атрибут Authorize для защиты контроллера, и один из методов в этом контроллере должен быть администратором.
Проблема, когда я пытаюсь получить доступ к этому методу, система переходит в oop, запрашивая и возвращая ProfileService с сервера SSO.
А теперь немного кода:
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 пуст.
Еще одна вещь, когда я получаю своего пользователя, к нему не прикрепляется список ролей:
Между тем роли входят в Идентификацию Главного объекта:
Я прочитал много постов здесь и на других сайтах, но пока я ничего не пытался решить мою проблему.
Любая помощь будет приветствоваться.
ОБНОВЛЕНИЕ 1:
Я немного изменился моя MVC сторона сервера конфигурации клиента, чтобы добавить роли, ограниченные для клиента, вот так: Сначала я добавил это:
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResource("roles", new[] { "role" })
};
}
Я добавил роль администратора для пользователя Алисы. Когда я получаю токен для пользователя, 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": "AliceSmith@email.com",
"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.");
И этот вызов l oop между клиентом и сервер:
[Authorize(Roles = "Administrator")]
public ActionResult About()
{
ViewBag.Message = "Your application description page.";
return View();
}