Токен не аутентифицируется с OpenIdDict / Connect - PullRequest
0 голосов
/ 20 февраля 2020

В настоящее время я работаю над преобразованием существующего приложения, состоящего из интерфейса Angular 9, с Asp. Net MVC / Web API / Identity backend. Я конвертирую. Net в. Net Core 2.2. Бэкэнд использует как куки, так и аутентификацию и авторизацию JWT. Я перехожу к OpenId Dict / Identity и OpenId Connect. К счастью, я создал отдельный проект для тестирования решения, потому что оно было пробным и много ошибок. Мне очень не хватает понимания OpenId Dict / Connect.

Мне нужна помощь в создании действительного токена на предъявителя. Токен, который выдает проверенного пользователя.

Хорошо известны следующие значения:

{
    "issuer": "https://localhost:44324/",
    "token_endpoint": "https://localhost:44324/connect/token",
    "end_session_endpoint": "https://localhost:44324/connect/logout",
    "userinfo_endpoint": "https://localhost:44324/api/userinfo",
    "jwks_uri": "https://localhost:44324/.well-known/jwks",
    "grant_types_supported": [
        "password"
    ],
    "scopes_supported": [
        "openid",
        "email",
        "profile",
        "roles"
    ],
    "claims_supported": [
        "aud",
        "exp",
        "iat",
        "iss",
        "jti",
        "sub"
    ],
    "id_token_signing_alg_values_supported": [
        "RS256"
    ],
    "subject_types_supported": [
        "public"
    ],
    "token_endpoint_auth_methods_supported": [
        "client_secret_basic",
        "client_secret_post"
    ],
    "claims_parameter_supported": false,
    "request_parameter_supported": false,
    "request_uri_parameter_supported": false
}

В Postman я генерирую токен, передавая следующее:

KEY     VALUE
grant_type  password
username    Phil
password    P@ssw0rd
client_id   incidentservices
scope       openid profile email roles

Он генерирует следующее:

{
    "token_type": "Bearer",
    "access_token": "CfDJ8ISU_Npa_VFIjdjKglRsdyVxwkfPoTyt7Bfx9UPtjrl57djzvm9VDvgNwFbJj-O_op4Mjl2AyecK0bBgb1o3B5p2G3TNdpXyPyQocLz-4zz2lROBAv-M8XsDvaJHEXE234YVqcQcju2Rp80MS7cVhBIrQVdaA94CgF_DE662o-i5wMrod2XACzUDdqDMgLJe9whr5RsIdsAmdCMHoK_yZHEKg68SgBJiSSXok8b35FRwDu1KEDY6cPht0FjBO02XFdsSnRJksBunx6eyif4vReh7-Q09BrQ0F1tk2nnZ3RxB2ttDKdpiZmcgQ3UAVWwxo1psAEkV6yRcroG1cKWM530APP2FLqa52wVzYr-AsESGEIiMKNaypK6Z7LWMnjV3Cu0F0lfxFJLPAO1A2uA2wtDphhvwNRBQafzbFYyEnMfFEQ8NkQ9nOSkiUQ2EbPB34qmiU2IEZijFJT1h5ZvbovCGP8qzslv6O2QUfJkDO0RlymqJNQZeoNPI4rwCyIbUZ9kP3rdoiOcrBjbQdKd5BiCAVQ4GHXOwYj2Oq61foPZByK0ek0imna22yybPYdMsUawHv12WY4Y57DIsFx4cLl8n2d0rjiE20a7rhNjjlzmofsN_jfN8e1JBnMBq0YgDkjASr2t-JcT-EQME16dQx2nVNqnZt95EYfcJxuMugR4pDZ18HJNnpRqx-PnhZapUryVqFMfPHuEEVewX0qpYPPlK6z-DMmGLxynukCC6aO1O70D_FgdemlmeWYViIXa11NHQscBobgfHAdYyA8NCKbVhUyK5R_hN7Ult5vMxVLmWAD25q1_DVwR-7gX7fZWwLuTcOJCVCZeZ9pldxrclL6zNJdHuh4B9_pGq_cN7EomF9Pyq0bM74-LPhBaKfYpVBnNIN6133jjXGFa1cOiS0fvJ3n54LEXXjsGM1_jAV7kOfT0ORwJOz_chIsE7fMgUTNZtUztjAlc-rkoQcPADBgTNs_fFhPM5857qMOBbMwL-",
    "expires_in": 3600,
    "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjgyQ0Q2NjdCOUQxRjNENjY0OTVBMENENTBCNTZCNkZCMTk5MjlDNjIiLCJ0eXAiOiJKV1QifQ.eyJzdWIiOiI3MTM0NkQ2Mi05QkE1LTRCNkQtOUVDQS03NTU1NzRENjI4RDgiLCJ0b2tlbl91c2FnZSI6ImlkX3Rva2VuIiwianRpIjoiOTAxNWIzYmEtMjEwYS00MzA5LThhZjgtNmRmZmU2YTJhNThmIiwiYXVkIjoiaW5jaWRlbnRzZXJ2aWNlcyIsImF0X2hhc2giOiJaRUtWZk9FNjF5SFdVdVVQeUQ1MXRnIiwiYXpwIjoiaW5jaWRlbnRzZXJ2aWNlcyIsIm5iZiI6MTU4MTk3MjI3NiwiZXhwIjoxNTgxOTczNDc2LCJpYXQiOjE1ODE5NzIyNzYsImlzcyI6Imh0dHBzOi8vbG9jYWxob3N0OjQ0MzI0LyJ9.sr48DAD2FWfPasKEioAFPZsXLEaRaubft4IIqj8uIG4dUfSFc59Je_q0FbwwA_HmHYVPnxga5yy_aBFTyAdTDyP1-2fTHdtnYau65_M5TbIupMETR6lLOB40Q51P2Nah83uOc6d_DAWiRQyI7q7AG3gRYewSU8QYoVDeAc8HKHIlnsp-HDSKlqcQScl25OqH7EcF-kM9zJChZFHroxc7kduYJyYpWad9081oqnXCyAkJK_R2g_EZKszBdvPTj4G3Wv29221lwLfNn1Dt7GTPkxaCaitWzXd708GR3xLAAwL4t6ENJz_CvtiPjS5lIPTjRlx8UHm9CztWdaFFgU-kQA"
}

Я вызываю тестовый веб-API, используя приведенный выше 'токен доступа' со следующим заголовком:

KEY     VALUE
Authorization   Bearer CfDJ8ISU_Npa_VFIjdjKglRsdyVxwkfPoTyt7Bfx9UPtjrl57djzvm9VDvgNwFbJj-O_op4Mjl2AyecK0bBgb1o3B5p2G3TNdpXyPyQocLz-4zz2lROBAv-M8XsDvaJHEXE234YVqcQcju2Rp80MS7cVhBIrQVdaA94CgF_DE662o-i5wMrod2XACzUDdqDMgLJe9whr5RsIdsAmdCMHoK_yZHEKg68SgBJiSSXok8b35FRwDu1KEDY6cPht0FjBO02XFdsSnRJksBunx6eyif4vReh7-Q09BrQ0F1tk2nnZ3RxB2ttDKdpiZmcgQ3UAVWwxo1psAEkV6yRcroG1cKWM530APP2FLqa52wVzYr-AsESGEIiMKNaypK6Z7LWMnjV3Cu0F0lfxFJLPAO1A2uA2wtDphhvwNRBQafzbFYyEnMfFEQ8NkQ9nOSkiUQ2EbPB34qmiU2IEZijFJT1h5ZvbovCGP8qzslv6O2QUfJkDO0RlymqJNQZeoNPI4rwCyIbUZ9kP3rdoiOcrBjbQdKd5BiCAVQ4GHXOwYj2Oq61foPZByK0ek0imna22yybPYdMsUawHv12WY4Y57DIsFx4cLl8n2d0rjiE20a7rhNjjlzmofsN_jfN8e1JBnMBq0YgDkjASr2t-JcT-EQME16dQx2nVNqnZt95EYfcJxuMugR4pDZ18HJNnpRqx-PnhZapUryVqFMfPHuEEVewX0qpYPPlK6z-DMmGLxynukCC6aO1O70D_FgdemlmeWYViIXa11NHQscBobgfHAdYyA8NCKbVhUyK5R_hN7Ult5vMxVLmWAD25q1_DVwR-7gX7fZWwLuTcOJCVCZeZ9pldxrclL6zNJdHuh4B9_pGq_cN7EomF9Pyq0bM74-LPhBaKfYpVBnNIN6133jjXGFa1cOiS0fvJ3n54LEXXjsGM1_jAV7kOfT0ORwJOz_chIsE7fMgUTNZtUztjAlc-rkoQcPADBgTNs_fFhPM5857qMOBbMwL-

И используя точку останова, я проверяю User.Identity , IsAuthenticated - false, а Name - null. Я использую сертификат WebApp2-TestAuth.pfx, который я сгенерировал. Это метод запуска, но я поместил кишки в отдельный класс c.

    public class Startup
    {
        public IConfiguration Configuration { get; }
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }
        //
        public void ConfigureServices(IServiceCollection services)
        {
            //
            AuthSettings _authSettings = new AuthSettings();
            _authSettings = Options.Create<AuthSettings>(
                Configuration.GetSection("AuthSettings").Get<AuthSettings>()).Value;
            services.AddSingleton<AuthSettings>(_authSettings);
            services.Configure<CookiePolicyOptions>(options =>
            {
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });
            services.AddSingleton<IEmailSender, NotificationService>();
            services.AddCors();
            ConfigureDatabase(services);
            services.AddIdentity<ApplicationUser, ApplicationRole>(options =>
                {
                    options.Stores.MaxLengthForKeys = 128;
                    options.Password.RequireDigit = true;
                    options.Password.RequiredLength = 8;
                    options.Password.RequireLowercase = true;
                    options.Password.RequireUppercase = true;
                    options.Password.RequireNonAlphanumeric = true;
                })
                .AddEntityFrameworkStores<ApplicationDbContext>();
            // Register the OpenIddict services.
            OpenIdDictStartup.OpenIdDictStartupServices(services, _authSettings);
            services.AddMvc(options =>
                {
                    options.Filters.Add(new RequireHttpsAttribute());
                })
                .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
                .AddRazorPagesOptions(options =>
                {
                    options.AllowAreas = true;
                });
            //
        }
        //
        public virtual void ConfigureDatabase(IServiceCollection services)
        {
            string _connetionString = Configuration.GetConnectionString("DefaultConnection");
            if (string.IsNullOrEmpty(_connetionString))
            {
                throw (new ApplicationException("no connection string found"));
            }
            services.AddDbContext<ApplicationDbContext>(options =>
            {
                options.UseSqlServer(_connetionString);
                options.UseOpenIddict();
            });
        }
        //
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseDeveloperExceptionPage();
            OpenIdDictStartup.OpenIdDictConfigure(app, env);
            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
            OpenIdDictStartup.InitializeOpenIddictApplicationAsync(
                app.ApplicationServices).GetAwaiter().GetResult();
            //
        }
    }

Это кишки кода OpenId Dict / Connect:

using System;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography.X509Certificates;
//
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Builder;
using Microsoft.IdentityModel.Tokens;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using AspNet.Security.OpenIdConnect.Primitives;
//
using OpenIddict.Abstractions;
using OpenIddict.Core;
using OpenIddict.EntityFrameworkCore.Models;
//
using WebApp02.Data;
using WebApp02.Models;
//
namespace WebApp02.OpenIddict
{
    public static class OpenIdDictStartup
    {
        //
        public static void OpenIdDictStartupServices(IServiceCollection services, AuthSettings authSettings)
        {
            //
            X509Certificate2 _jwtSigningCert = new X509Certificate2(authSettings.CertLocation, authSettings.CertPassword);
            services.AddOpenIddict()
                .AddCore(options =>
                {
                    options.UseEntityFrameworkCore()
                        .UseDbContext<ApplicationDbContext>();
                })
                .AddServer(options =>
                {
                    options.UseMvc();
                    options.EnableTokenEndpoint("/connect/token");
                    options.EnableLogoutEndpoint("/connect/logout");
                    options.EnableUserinfoEndpoint("/api/userinfo");
                    options.AllowPasswordFlow();
                    options.AddSigningCertificate(_jwtSigningCert);
                    options.DisableHttpsRequirement();
                    options.RegisterScopes(OpenIdConnectConstants.Scopes.Email,
                                           OpenIdConnectConstants.Scopes.Profile,
                                           OpenIddictConstants.Scopes.Roles);
                });
            DualCookieJWTStartupServicesAuthentication(services, authSettings);
            //
        }
        //
        public static void DualCookieJWTStartupServicesAuthentication(IServiceCollection services, AuthSettings authSettings)
        {
            //
            X509Certificate2 _jwtSigningCert = new X509Certificate2(authSettings.CertLocation, authSettings.CertPassword);
            services.AddAuthentication()
                .AddCookie(options =>
                {
                    options.SlidingExpiration = true;
                    options.LoginPath = new PathString("/Identity/Account/Login");
                    options.Events = new CookieAuthenticationEvents
                    {
                        OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync
                    };
                })
                .AddJwtBearer(options =>
                {
                    options.Authority = authSettings.JwtAuthority;
                    options.RequireHttpsMetadata = false;
                    options.SaveToken = true;
                    options.IncludeErrorDetails = true;
                    options.TokenValidationParameters = new TokenValidationParameters()
                    {
                        ValidIssuer = authSettings.JwtIssuer,
                        ValidateIssuer = true,
                        ValidAudience = authSettings.JwtAudience,
                        IssuerSigningKey = new X509SecurityKey(_jwtSigningCert),
                        ValidateIssuerSigningKey = true,
                    };
                });
            services.AddAuthorization(options =>
            {
                var _policy = new AuthorizationPolicyBuilder()
                    .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme, CookieAuthenticationDefaults.AuthenticationScheme)
                    .RequireAuthenticatedUser()
                    .Build();
                options.AddPolicy("DualCookieJWT", _policy);
            });
            //
        }
        //
        public static void OpenIdDictConfigure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseAuthentication();
        }
        //
        public static async Task InitializeOpenIddictApplicationAsync(IServiceProvider services)
        {
            //
            string _clientId = "incidentservices";
            using (var _scope = services.GetRequiredService<IServiceScopeFactory>().CreateScope())
            {
                try
                {
                    var _dbContext = _scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
                    await _dbContext.Database.EnsureCreatedAsync();
                    var _openIddictManager = _scope.ServiceProvider.GetRequiredService<OpenIddictApplicationManager<OpenIddictApplication>>();
                    if (await _openIddictManager.FindByClientIdAsync(_clientId) == null)
                    {
                        var _descriptor = new OpenIddictApplicationDescriptor
                        {
                            ClientId = _clientId,
                            Permissions =
                            {
                                OpenIddictConstants.Permissions.Endpoints.Logout,
                                OpenIddictConstants.Permissions.Endpoints.Token,
                                OpenIddictConstants.Permissions.GrantTypes.Password,
                                OpenIddictConstants.Permissions.Scopes.Email,
                                OpenIddictConstants.Permissions.Scopes.Profile,
                                OpenIddictConstants.Permissions.Scopes.Roles
                            }
                        };
                        await _openIddictManager.CreateAsync(_descriptor);
                    }
                }
                catch (Exception _ex)
                {
                    Console.WriteLine(_ex.ToString());
                }
                //
            }
        }
    }
}

Контроллер AuthorizationController был взят из образца ядра OpenIddict версии 2.0.1 Mvc .Server. Я изменил CreateTicketAsyn c, поэтому он выглядит следующим образом:

private async Task<AuthenticationTicket> CreateTicketAsync(
    OpenIdConnectRequest request, ApplicationUser user,
    AuthenticationProperties properties = null)
{
    var _principal = await _signInManager.CreateUserPrincipalAsync(user);
    var _ticket = new AuthenticationTicket(_principal, properties,
        OpenIddictServerDefaults.AuthenticationScheme);
    if (!request.IsAuthorizationCodeGrantType() && !request.IsRefreshTokenGrantType())
    {
        _ticket.SetScopes(request.GetScopes());
        _ticket.SetResources("resource_server");
    }
    var _identity = (ClaimsIdentity)_principal.Identity;
    if (_ticket.HasScope(OpenIdConnectConstants.Scopes.Profile))
    {
        var _subject = new Claim(OpenIdConnectConstants.Claims.Subject,
            _authSettings.OIDCSubject,
            OpenIdConnectConstants.Destinations.AccessToken);
        _identity.AddClaim(_subject);
        //
        var _name = new Claim(OpenIdConnectConstants.Claims.Username, user.UserName);
        _name.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken);
        _identity.AddClaim(_name);
        //
        var _uname = new Claim(JwtRegisteredClaimNames.UniqueName, user.UserName);
        _uname.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken);
        _identity.AddClaim(_uname);
    }
    IList<string> _roles = await _userManager.GetRolesAsync(user);
    foreach (string _role in _roles)
    {
        _identity.AddClaim(new Claim("roles", _role));
    }
    foreach (var _claim in _ticket.Principal.Claims)
    {
        _claim.SetDestinations(GetDestinations(_claim, _ticket));
    }
    return _ticket;
    //
}

Пожалуйста, помогите, я не знаю, как получить токен на предъявителя.

...