【问题标题】:How to use keycloak in .net core 5.0 with AspNet Core Identity?如何在带有 AspNet Core Identity 的 .net core 5.0 中使用 keycloak?
【发布时间】:2021-10-05 01:00:18
【问题描述】:

我在我的项目中使用带有 CQRS 的 aspnet core 5.0 webapi,并且已经实现了 jwt。不使用 aspnet 核心的角色管理,而是为 aspnet 用户表角色字段手动添加,它在任何地方都使用。在互联网上,我找不到任何文章来为现有的身份验证和授权实现 keycloak。我的观点是现在用户使用他们的电子邮件+密码登录,想法并不适用于所有人,而是适用于他们已经存储在 keycloak 中的一些用户,或者对于我们将存储在那里的一些用户,也可以使用 keycloak 为我们的应用程序提供选项登录。

场景 1:

我的 db 和 keycloak 中都有 admin@gmail.com 并且他们都是管理员角色,我需要为两者提供访问权限才能登录我的应用程序,第一个场景已经工作需要先实施第二个场景。

仅找到这篇实现保护应用程序的文章(正如我们已经做过的那样,不是试图替换而是扩展)

Medium keycloak

我的 jwt 配置如下:

public static IServiceCollection AddCustomAuthentication(this IServiceCollection services,
            IConfiguration configuration)
        {
            var key = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(configuration.GetSection("AppSettings:Token").Value));
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(opt =>
                {
                    opt.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuerSigningKey = true,
                        IssuerSigningKey = key,
                        ValidateAudience = false,
                        ValidateIssuer = false,
                        ClockSkew = TimeSpan.Zero
                    };
                    opt.Events = new JwtBearerEvents
                    {
                        OnAuthenticationFailed = context =>
                        {
                            if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                            {
                                context.Response.Headers.Add("Token-Expired", "true");
                            }

                            return Task.CompletedTask;
                        }
                    };
                });
           

         return services;
      }

我的 jwt 服务如下所示:

public JwtGenerator(IConfiguration config)
        {
            _key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.GetSection("AppSettings:Token").Value));
        }
        public string CreateToken(User user)
        {
            var claims = new List<Claim>
            {
                new(ClaimTypes.NameIdentifier, user.Id),
                new(ClaimTypes.Email, user.Email),
                new(ClaimTypes.Name, user.UserName),
                new(ClaimTypes.Role, user.Role.ToString("G").ToLower())
            };
            
            var creds = new SigningCredentials(_key, SecurityAlgorithms.HmacSha512);
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(claims),
                Expires = DateTime.UtcNow.AddMinutes(15),
                SigningCredentials = creds
            };
            var tokenHandler = new JwtSecurityTokenHandler();
            var token = tokenHandler.CreateToken(tokenDescriptor);
            return tokenHandler.WriteToken(token);
        }

我的登录方法如下:

 public async Task<GetToken> Handle(LoginCommand request, CancellationToken cancellationToken)
        {
            var user = await _userManager.FindByEmailAsync(request.Email);
            if (user == null)
                throw new BadRequestException("User not found");
            
            UserManagement.ForbiddenForLoginUser(user);

            var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, false);

            if (result.Succeeded)
            {
                user.IsRoleChanged = false;
                
                RefreshToken refreshToken = new RefreshToken
                {
                    Name = _jwtGenerator.GenerateRefreshToken(),
                    DeviceName = $"{user.UserName}---{_jwtGenerator.GenerateRefreshToken()}",
                    User = user,
                    Expiration = DateTime.UtcNow.AddHours(4)
                };
                await _context.RefreshTokens.AddAsync(refreshToken, cancellationToken);
                await _context.SaveChangesAsync(cancellationToken);

                return new GetToken(_jwtGenerator.CreateToken(user),refreshToken.Name);
            }

            throw new BadRequestException("Bad credentials");
        }

我的授权处理程序:

public static IServiceCollection AddCustomMvc(this IServiceCollection services)
        {
            services.AddMvc(opt =>
            {
                var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
                opt.Filters.Add(new AuthorizeFilter(policy));
                // Build the intermediate service provider
                opt.Filters.Add<CustomAuthorizationAttribute>();
            }).AddFluentValidation(cfg => cfg.RegisterValidatorsFromAssemblyContaining<CreateProjectCommand>());

            return services;
        }

除了我目前的方法之外,实现 keycloak authentiaction+authorization 的最佳实践是什么,并让用户使用两种情况登录,正常登录和 keycloak 登录。

附: Ui 不同,我们使用 Angular 这个只是 webapi 作为后端。

【问题讨论】:

    标签: c# asp.net-core asp.net-identity keycloak


    【解决方案1】:

    由于您的登录方法返回一个 jwt,您可以通过链接 .AddJwtBearer() 来配置多个不记名令牌,一个用于正常登录,一个用于 keycloak。

    这里是一个可能解决您问题的问题的链接:Use multiple jwt bearer authentication

    Keycloak 配置:

    • 转到角色 -> 领域角色并创建相应的角色。
    • 转到客户端 -> 您的客户端 -> 映射器。
    • 创建一个新的角色映射器并为映射器类型选择“用户领域角色”,为令牌声明名称选择“角色”,为声明 JSON 类型选择“字符串”。如果没有映射,之前配置的角色将嵌套在 jwt 的其他位置。

    您可以使用jwt.io 的调试器来检查您的令牌是否正确。结果应如下所示:

    {
      "exp": 1627565901,
      "iat": 1627564101,
      "jti": "a99ccef1-afa9-4a62-965b-15e8d33de7de",
    
      // [...]
      // roles nested in realm_access :(
      "realm_access": {
        "roles": [
          "offline_access",
          "uma_authorization",
          "Admin"
        ]
      },
    
      // [...]
      // your mapped roles in your custom claim
      "roles": [
        "offline_access",
        "uma_authorization",
        "Admin"
      ]
    
      // [...]
    }
    

    【讨论】:

    • 我编辑了我的问题。在这种情况下,授权呢?因为我的应用基于角色的身份验证和 keycloak 令牌在上下文中没有角色。
    • 在我的回答中添加了 keycloak 配置指南
    猜你喜欢
    • 1970-01-01
    • 2018-06-11
    • 1970-01-01
    • 1970-01-01
    • 2019-01-14
    • 1970-01-01
    • 1970-01-01
    • 2019-09-09
    • 2018-02-14
    相关资源
    最近更新 更多