Невозможно заставить работать атрибут [Authorize (Role = "Administrator")] - PullRequest
0 голосов
/ 23 января 2020

Я застрял в этой проблеме уже несколько дней и не нашел решения.

Сначала я создал 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();
 }

Когда я нажимаю на ссылку на странице, вызывающей этот метод, контекст, передаваемый на сервер, не содержит роли для запроса:

enter image description here

Как видите, TequestedClaimType пуст.

Еще одна вещь, когда я получаю своего пользователя, к нему не прикрепляется список ролей:

enter image description here

Между тем роли входят в Идентификацию Главного объекта:

enter image description here

Я прочитал много постов здесь и на других сайтах, но пока я ничего не пытался решить мою проблему.

Любая помощь будет приветствоваться.

ОБНОВЛЕНИЕ 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();
}

1 Ответ

0 голосов
/ 27 января 2020

Я, наконец, заставляю его работать, собирая различные источники из Интернета.

Когда я боролся, главная проблема была в клиенте MVC в файле startup.cs.

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);
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...