Asp Core:Azure Ad Auth + 自定义 JWT + 自定义身份存储 [英] Asp Core: Azure Ad Auth + custom JWT + custom Identity store

查看:23
本文介绍了Asp Core:Azure Ad Auth + 自定义 JWT + 自定义身份存储的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

使用 ASP.NET Core 2.0,我尝试实现以下目标:

With ASP.NET Core 2.0 I try to achieve the following:

  1. 通过 Azure AD(注册应用)进行身份验证
  2. 自定义 JWT 作为身份验证方案
    • 使网络应用身份验证跨服务器/实例工作
    • 能够保存承载以使用桌面客户​​端登录

所有这些部分都有工作示例,但是在尝试将它们组合起来时,我偶然发现了一些问题.

All these parts have working examples, but while trying to combine them I stumbled over some problems.

Web Api + Azure Ad Auth 示例使用 JWT 令牌进行身份验证,但没有验证或创建令牌的逻辑.它也没有登录/注销的逻辑,但这似乎是合理的,它只是 Api.

The Web Api + Azure Ad Auth example uses JWT Tokens for authentication, but doesn't have logic for validating or creating tokens. Neither does it have logic for login/logout, but this seems reasonable, its just Api.

这里是 Web Api 示例代码的快速提示:

Here is a quick reminder of the code of the Web Api example:

AzureAdAuthenticationBuilderExtensions.cs

using System;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

namespace Microsoft.AspNetCore.Authentication
{
    public static class AzureAdServiceCollectionExtensions
    {
        public static AuthenticationBuilder AddAzureAdBearer(this AuthenticationBuilder builder)
            => builder.AddAzureAdBearer(_ => { });

        public static AuthenticationBuilder AddAzureAdBearer(this AuthenticationBuilder builder, Action<AzureAdOptions> configureOptions)
        {
            builder.Services.Configure(configureOptions);
            builder.Services.AddSingleton<IConfigureOptions<JwtBearerOptions>, ConfigureAzureOptions>();
            builder.AddJwtBearer();
            return builder;
        }

        private class ConfigureAzureOptions: IConfigureNamedOptions<JwtBearerOptions>
        {
            private readonly AzureAdOptions _azureOptions;

            public ConfigureAzureOptions(IOptions<AzureAdOptions> azureOptions)
            {
                _azureOptions = azureOptions.Value;
            }

            public void Configure(string name, JwtBearerOptions options)
            {
                options.Audience = _azureOptions.ClientId;
                options.Authority = $"{_azureOptions.Instance}{_azureOptions.TenantId}";
            }

            public void Configure(JwtBearerOptions options)
            {
                Configure(Options.DefaultName, options);
            }
        }
    }
}

摘自Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(sharedOptions =>
    {
        sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddAzureAdBearer(options => Configuration.Bind("AzureAd", options));

    services.AddMvc();
}

另一方面,Web 应用程序 + Azure 广告示例使用带有 cookie 的 OpenId 并且确实具有登录/注销逻辑:

The Web Application + Azure Ad example on the other hand uses OpenId with cookies and does have login/logout logic:

AzureAdAuthenticationBuilderExtensions.cs

using System;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

namespace Microsoft.AspNetCore.Authentication
{
    public static class AzureAdAuthenticationBuilderExtensions
    {
        public static AuthenticationBuilder AddAzureAd(this AuthenticationBuilder builder)
            => builder.AddAzureAd(_ => { });

        public static AuthenticationBuilder AddAzureAd(this AuthenticationBuilder builder, Action<AzureAdOptions> configureOptions)
        {
            builder.Services.Configure(configureOptions);
            builder.Services.AddSingleton<IConfigureOptions<OpenIdConnectOptions>, ConfigureAzureOptions>();
            builder.AddOpenIdConnect();
            return builder;
        }

        private class ConfigureAzureOptions : IConfigureNamedOptions<OpenIdConnectOptions>
        {
            private readonly AzureAdOptions _azureOptions;

            public ConfigureAzureOptions(IOptions<AzureAdOptions> azureOptions)
            {
                _azureOptions = azureOptions.Value;
            }

            public void Configure(string name, OpenIdConnectOptions options)
            {
                options.ClientId = _azureOptions.ClientId;
                options.Authority = $"{_azureOptions.Instance}{_azureOptions.TenantId}";
                options.UseTokenLifetime = true;
                options.CallbackPath = _azureOptions.CallbackPath;
                options.RequireHttpsMetadata = false;
            }

            public void Configure(OpenIdConnectOptions options)
            {
                Configure(Options.DefaultName, options);
            }
        }
    }
}

摘自Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(sharedOptions =>
    {
        sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddAzureAd(options => Configuration.Bind("AzureAd", options))
    .AddCookie();

    services.AddMvc(options =>
    {
        var policy = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .Build();
        options.Filters.Add(new AuthorizeFilter(policy));
    })
    .AddRazorPagesOptions(options =>
    {
        options.Conventions.AllowAnonymousToFolder("/Account");
    });
}

AccountController.cs

public class AccountController : Controller
{
    [HttpGet]
    public IActionResult SignIn()
    {
        var redirectUrl = Url.Page("/Index");
        return Challenge(
            new AuthenticationProperties { RedirectUri = redirectUrl },
            OpenIdConnectDefaults.AuthenticationScheme
        );
    }

    [HttpGet]
    public IActionResult SignOut()
    {
        var callbackUrl = Url.Page("/Account/SignedOut", pageHandler: null, values: null, protocol: Request.Scheme);
        return SignOut(
            new AuthenticationProperties { RedirectUri = callbackUrl },
            CookieAuthenticationDefaults.AuthenticationScheme, OpenIdConnectDefaults.AuthenticationScheme
        );
    }
}

我以某种方式合并了两个变体,但显然它不起作用.我当然在登录方法中用 JwtBearerDefaults 替换了 CookieAuthenticationDefault.

I have merged somehow both variants, but apparently it doesn't work. I replaced of course CookieAuthenticationDefault with JwtBearerDefaults in the login method.

摘自Startup.cs

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

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

    services.AddAuthentication(sharedOptions =>
    {
        sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
        sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddAzureAd(options => Configuration.Bind("AzureAd", options))
    .AddJwtBearer(options =>
    {
        options.IncludeErrorDetails = true;

        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = "localhost",
            ValidAudience = "localhost",
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("test"))
        };
    });

    services.AddMvc(options =>
    {
        var policy = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .Build();
        options.Filters.Add(new AuthorizeFilter(policy));
    })
    .AddRazorPagesOptions(options =>
    {
        options.Conventions.AllowAnonymousToFolder("/Account");
    });
}

我不完全理解不同的身份验证是如何链接或相互依赖的.我明白,OpenId 在内部使用了某种 JWT,但仍然存在以下问题:

I don't fully understand how the different authentications are chained or depend on each other. I understand, that OpenId uses internally some sort of JWT, still the following questions remain:

  • 为什么 Web Api 示例只使用 JWT,而另一个使用带有 cookie 的 OpenId?
  • 为什么 OpenId 示例首先不使用 JWT?
  • 自定义 JWT 是否与 OpenId 配合使用?
  • 是否可以引入自定义身份存储,但保留用于登录的 Azure AD(和登录名)?

如果你能给我一些指导就好了,不需要完整的示例(即使这很好)

It would be great if you could offer me some guidance, no fully working example needed (even though this would be great)

推荐答案

如果你想在你的 ASP.NET Core web 应用程序中结合 Cookies 和 Bearer 授权,你可以按照下面的代码片段:

If you want combine Cookies and Bearer authorization in your ASP.NET Core web application, you could follow the code snippet below:

services.AddAuthentication(sharedOptions =>
{
    sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddJwtBearer(jwtOptions=> {
    jwtOptions.IncludeErrorDetails = true;
    jwtOptions.Authority = "{Authority}";
    jwtOptions.Audience = "{Audience}";
    jwtOptions.TokenValidationParameters = new TokenValidationParameters()
    {
        ValidIssuer = "{ValidIssuer}",
        ValidAudience = "{ValidAudience}"
    };
    jwtOptions.Events = new JwtBearerEvents()
    {
        OnAuthenticationFailed = context => {
            //TODO:
            return Task.FromResult(0);
        },
        OnTokenValidated = context => {
            //At this point, the security token has been validated successfully and a ClaimsIdentity has been created
            var claimsIdentity = (ClaimsIdentity)context.Principal.Identity;
            //add your custom claims here
            claimsIdentity.AddClaim(new Claim("test", "helloworld!!!"));

            return Task.FromResult(0);
        }
    };
})
.AddAzureAd(options => Configuration.Bind("AzureAd", options))
.AddCookie();

我注意到你添加了一个全局的AuthorizeFilter,这时候你需要确保匿名操作需要用AllowAnonymous属性来修饰.

I noticed that you added a global AuthorizeFilter, at this time you need to make sure the anonymous action(s) need to decorate with AllowAnonymous attribute.

services.AddMvc(options =>
{
    var policy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .AddAuthenticationSchemes(CookieAuthenticationDefaults.AuthenticationScheme, JwtBearerDefaults.AuthenticationScheme)
        .Build();
    options.Filters.Add(new AuthorizeFilter(policy));
})

或者你可以用 Authorize 属性装饰控制器动作,如下所示:

Or you could decorate the controller action with Authorize attribute as follows:

[Authorize(AuthenticationSchemes = "Cookies,Bearer")]
public IActionResult UserInfo()
{
    return Json(User.Claims.Select(c => new { key = c.Type, value = c.Value }));
}

对于 OpenID Connect 中间件,您可以修改 AzureAdAuthenticationBuilderExtensions.cs 文件下的 Configure(string name, OpenIdConnectOptions options) 方法以添加自定义声明(例如角色、等)如下:

For the OpenID Connect middleware, you could modify the Configure(string name, OpenIdConnectOptions options) method under AzureAdAuthenticationBuilderExtensions.cs file to add your custom claims (e.g. role,etc.) as follows:

public void Configure(string name, OpenIdConnectOptions options)
{
    options.ClientId = _azureOptions.ClientId;
    options.Authority = $"{_azureOptions.Instance}{_azureOptions.TenantId}";
    options.UseTokenLifetime = true;
    options.CallbackPath = _azureOptions.CallbackPath;
    options.RequireHttpsMetadata = false;
    //the new code
    options.Events = new OpenIdConnectEvents
    {
        OnTokenValidated = context =>
        {   
            var claimsIdentity = (ClaimsIdentity)context.Principal.Identity;
            //add your custom claims here
            claimsIdentity.AddClaim(new Claim("test", "helloworld!!!"));

            return Task.FromResult(0);
        }
    };
}

总而言之,在配置时,您可以结合 Cookie 和 Bearer 身份验证,在验证令牌后,您可以检索用户标识符并从数据库中获取其他用户信息,然后将它们附加到 ClaimsIdentity.

In summary, upon the configuration, you could combine Cookies and Bearer authentication, and after the token has been validated, you could retrieve the user identifier and get additional user info from your database, then attach them to ClaimsIdentity.

对于桌面客户端,您可以利用 ADAL(适用于 AD 应用 v1.0)或 MSAL(适用于 AD 应用 v2.0)登录并检索 access_tokenid_token,然后将其用作用于访问您的网络应用程序的不记名令牌.

For desktop clients, you could leverage ADAL (for AD app v1.0) or MSAL (for AD app v2.0) to log in and retrieve the access_token or id_token, then use it as the bearer token to access your web application.

这篇关于Asp Core:Azure Ad Auth + 自定义 JWT + 自定义身份存储的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆