Авторизация на основе ролей для веб-API с IdentityServer4

Я использую IdentityServer4 (v2.2.1) с .Net Core 2.0 и Asp.Net Core Identity. В моем решении три проекта.

  1. IdentityServer
  2. Веб-API
  3. Веб-приложение MVC

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

В настоящее время я могу реализовать авторизацию баз ролей на контроллере приложений MVC, но я не могу передать / настроить то же самое для контроллера WEB API.

Ниже приведены файлы сервера идентификации: Config.cs

public static IEnumerable<ApiResource> GetApiResources()
    {
        return new List<ApiResource>
        {
            //SCOPE - Resource to be protected by IDS 
            new ApiResource("TCSAPI", "TCS API")
            {
                UserClaims = { "role" }
            }
        };
    }


 public static IEnumerable<Client> GetClients()
    {
        return new List<Client>
        {
new Client
            {
                ClientId = "TCSIdentity",
                ClientName = "TCS Mvc Client Application .",
                AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
                RequireConsent = false,
                ClientSecrets =
                {
                    new Secret("secret".Sha256())
                },

                RedirectUris = { "http://localhost:5002/signin-oidc" },
                PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" },

                AlwaysSendClientClaims= true,
                AlwaysIncludeUserClaimsInIdToken = true,

                AllowedScopes =
                {
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.StandardScopes.Email,
                    IdentityServerConstants.StandardScopes.Profile,
                    IdentityServerConstants.StandardScopes.OfflineAccess,
                    "TCSAPI",
                    "office",
                    "role",
                },
                AllowOfflineAccess = true
            }
  };
    }

public static IEnumerable<IdentityResource> GetIdentityResources()
    {
        return new IdentityResource[]
        {
            new IdentityResources.OpenId(),
            new IdentityResources.Profile(),
            new IdentityResources.Email(),
            new IdentityResource
            {
                Name = "role",
                DisplayName="User Role",
                Description="The application can see your role.",
                UserClaims = new[]{JwtClaimTypes.Role,ClaimTypes.Role},
                ShowInDiscoveryDocument = true,
                Required=true,
                Emphasize = true
            }
        };
    }

Startup.cs

 public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

        services.AddIdentity<ApplicationUser, IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();

        // Add application services.
        services.AddTransient<IEmailSender, EmailSender>();

        services.AddMvc();

        // configure identity server with in-memory stores, keys, clients and scopes
        services.AddIdentityServer()
            .AddDeveloperSigningCredential()
            .AddInMemoryPersistedGrants()
            .AddInMemoryIdentityResources(Config.GetIdentityResources())
            .AddInMemoryApiResources(Config.GetApiResources())
            .AddInMemoryClients(Config.GetClients())
            .AddAspNetIdentity<ApplicationUser>();
    }

Веб-приложение MVC (базовая авторизация ролей хорошо работает для веб-приложения MVC):

RoleClaimAction.cs использует этот файл для добавления ролей в удостоверение.

internal class RoleClaimAction : ClaimAction
{
    public RoleClaimAction()
        : base("role", ClaimValueTypes.String)
    {
    }

    public override void Run(JObject userData, ClaimsIdentity identity, string issuer)
    {
        var tokens = userData.SelectTokens("role");
        IEnumerable<string> roles;

        foreach (var token in tokens)
        {
            if (token is JArray)
            {
                var jarray = token as JArray;
                roles = jarray.Values<string>();
            }
            else
                roles = new string[] { token.Value<string>() };

            foreach (var role in roles)
            {
                Claim claim = new Claim("role", role, ValueType, issuer);
                if (!identity.HasClaim(c => c.Subject == claim.Subject
                                         && c.Value == claim.Value))
                {
                    identity.AddClaim(claim);
                }
            }
        }
    }
} 

Веб-приложение MVC / Startup.cs

 public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();

        JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
        services.AddCors();
        services.AddAuthentication(options =>
        {
            options.DefaultScheme = "Cookies";
            options.DefaultChallengeScheme = "oidc";
        })
            .AddCookie("Cookies")
            .AddOpenIdConnect("oidc", options =>
            {
                options.SignInScheme = "Cookies";
                options.Authority = "http://localhost:5000";
                options.RequireHttpsMetadata = false;

                options.ClientId = "TCSIdentity";

                //HYBRID FLOW
                options.ClientSecret = "secret";

                options.ClaimActions.Add(new RoleClaimAction()); // <-- 

                options.ResponseType = "code id_token token";
                options.GetClaimsFromUserInfoEndpoint = true;
                options.Scope.Add("TCSAPI");
                options.Scope.Add("offline_access");
                //END HYBRID FLOW
                options.SaveTokens = true;
                options.Scope.Add("role");

                options.TokenValidationParameters.NameClaimType = "name";
                options.TokenValidationParameters.RoleClaimType = "role";
            });

    }

MVC WEB APP / HomeController.cs этот метод действия хорошо работает с авторизацией по базе ролей, но когда я пытаюсь передать токен в Web Api для доступа к чему-либо с авторизацией по базе ролей, он не может авторизоваться. например var content = await client.GetStringAsync ("http://localhost:5001/user");

[Authorize(Roles = "User")]
    [Route("user")]
    public async Task<IActionResult> UserAccess()
    {

        var tokenClient = new TokenClient("http://localhost:5000/connect/token", "RoleApi", "secret");
        var tokenResponse = await tokenClient.RequestClientCredentialsAsync("TCSAPI");

        var client = new HttpClient();
        client.SetBearerToken(tokenResponse.AccessToken);
        var content = await client.GetStringAsync("http://localhost:5001/user");

        ViewBag.Json = JArray.Parse(content).ToString();
        return View("json");
    }
    [Authorize(Roles = "Admin")]
    [Route("admin")]
    public async Task<IActionResult> AdminAccess()
    {
        var accessToken = await HttpContext.GetTokenAsync("id_token");

        var client = new HttpClient();
        client.SetBearerToken(accessToken);
        var content = await client.GetStringAsync("http://localhost:5001/admin");
        ViewBag.Json = JArray.Parse(content).ToString();
        return View("json");
    }

WEBAPI / Startup.cs

 public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvcCore()
            .AddAuthorization()
            .AddJsonFormatters();
        services.AddAuthentication("Bearer")
            .AddIdentityServerAuthentication(options =>
            {
                options.Authority = "http://localhost:5000";
                options.RequireHttpsMetadata = false;
                options.ApiName = "TCSAPI";
            });

        services.AddCors(options =>
        {
            options.AddPolicy("default", policy =>
            {
                policy.WithOrigins("http://localhost:5002")
                .AllowAnyHeader()
                .AllowAnyMethod();
            });
        });
    }

WEB API / TestController.cs

[Route("admin")]
    [Authorize(Roles = "Admin")]
    public IActionResult AdminAccess()
    {
        return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
    }
    [Route("user")]
    [Authorize(Roles = "User")]
    public IActionResult UserAccess()
    {
        return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
    }
    [AllowAnonymous]
    [Route("public")]
    public IActionResult PublicAccess()
    {
        return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
    }

person Fawad Ali Siddiqi    schedule 10.05.2018    source источник
comment
Как у вас возникла эта проблема? предоставьте информацию, если вы это сделали. Спасибо   -  person Mohammad Olfatmiri    schedule 30.12.2018
comment
Я добавил вкладку меню и таблицу разрешений меню. а в UserProfileService я читаю разрешения пользователей из базы данных и добавляю каждое разрешение в качестве утверждения. в действиях моего контроллера я использовал заявление как разрешение. Таким образом, теперь я могу контролировать разрешения на контроллере и отдельные методы действий.   -  person Fawad Ali Siddiqi    schedule 31.12.2018
comment
@ можете ли вы предоставить окончательный код?   -  person Mohammad Olfatmiri    schedule 01.01.2019
comment
не могу подтвердить. Я проверю, смогу ли я вытащить требуемый код, чем поделюсь.   -  person Fawad Ali Siddiqi    schedule 03.01.2019


Ответы (1)


Ваш код не совсем авторизация на основе политик. Ваш похоже на авторизацию на основе ролей .NET Framework.

Для авторизации на основе политик, вам нужно сделать следующее:

1. В Startup.cs вашего проекта веб-API вам нужно добавить что-то вроде:

// more code
.AddMvcCore()
            .AddAuthorization(options =>
            {
                options.AddPolicy("Policy1",
                    policy => policy.Requirements.Add(new Policy1Requirement()));
                options.AddPolicy("Policy2",
                    policy => policy.Requirements.Add(new Policy2Requirement()));
                .
                .
                .
                .
            })
 // more code

2. Затем вам нужно создать класс для каждого Policy(X)Requirement:

public class Policy1Requirement : AuthorizationHandler<Policy1Requirement>, IAuthorizationRequirement
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AdminUserRequirement requirement)
    {
        if (!context.User.HasClaim(c => c.Type == "role" && c.Value == "<YOUR_ROLE_FOR_THIS_POLICY>"))
        {
            context.Fail();
        }
        else
        {
            context.Succeed(requirement);
        }
        return Task.FromResult(0);
    }
}

3. И в конце, когда вы применяете политику, вам необходимо иметь:

[Authorize(Policy = "Policy1")]
public class MyController : Controller
{
.
.    
}

Удачи!

PS:

Имена Policy(X) и Policy(X)Requirement приведены только для пояснения. Вы можете использовать любые имена, которые захотите, если вы реализуете правильный интерфейс IAuthorizationRequirement и наследуете класс AuthorizationHandler

person m3n7alsnak3    schedule 10.05.2018