You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在.NET Core 5.0 + AspNet Core Identity项目中集成Keycloak实现双登录模式?

现有ASP.NET Core 5.0 WebAPI集成Keycloak双登录方案

方案核心思路

不用修改原有账号密码登录、JWT生成、授权校验的任何逻辑,仅扩展双JWT认证能力与新增Keycloak登录入口,实现两种登录方式完全兼容。

第一步:调整认证配置,支持双JWT源校验

修改你的AddCustomAuthentication扩展方法,新增Keycloak专属的JWT认证方案,同时配置默认授权策略可匹配任意一种认证方式通过:

public static IServiceCollection AddCustomAuthentication(this IServiceCollection services,
    IConfiguration configuration)
{
    // 原有本地JWT配置
    var localKey = new SymmetricSecurityKey(
        Encoding.UTF8.GetBytes(configuration.GetSection("AppSettings:Token").Value));
    services.AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer("LocalJwt", opt =>
        {
            opt.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = localKey,
                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;
                }
            };
        })
        // 新增Keycloak JWT认证方案
        .AddJwtBearer("KeycloakJwt", opt =>
        {
            opt.Authority = configuration["Keycloak:Authority"]; // 格式:http://keycloak地址/realms/你的realm名称
            opt.Audience = configuration["Keycloak:Audience"]; // 你在Keycloak中创建的客户端ID
            opt.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                ValidateAudience = true,
                ValidateIssuer = true,
                ClockSkew = TimeSpan.Zero,
                // 将Keycloak返回的角色Claim映射为你系统通用的Role Claim
                RoleClaimType = ClaimTypes.Role
            };
        });
    
    // 配置全局授权策略:任意一种JWT认证通过即可
    services.AddAuthorization(options =>
    {
        options.DefaultPolicy = new AuthorizationPolicyBuilder("LocalJwt", "KeycloakJwt")
            .RequireAuthenticatedUser()
            .Build();
    });

    return services;
}

对应在appsettings.json中新增Keycloak配置项即可。

第二步:新增Keycloak登录CQRS入口,复用现有token生成逻辑

前端Angular先完成Keycloak侧的登录流程拿到Keycloak返回的access token后,调用后端新增的Keycloak登录接口,后端逻辑直接复用你现有用户校验、token生成的能力,返回和原有登录完全一致的token结构,前端后续请求无需任何修改:

新增KeycloakLoginCommand处理逻辑

public class KeycloakLoginCommand : IRequest<GetToken>
{
    public string KeycloakAccessToken { get; set; }
}

public class KeycloakLoginCommandHandler : IRequestHandler<KeycloakLoginCommand, GetToken>
{
    private readonly IConfiguration _config;
    private readonly UserManager<User> _userManager;
    private readonly JwtGenerator _jwtGenerator;
    private readonly ApplicationDbContext _context;

    public KeycloakLoginCommandHandler(IConfiguration config, UserManager<User> userManager, JwtGenerator jwtGenerator, ApplicationDbContext context)
    {
        _config = config;
        _userManager = userManager;
        _jwtGenerator = jwtGenerator;
        _context = context;
    }

    public async Task<GetToken> Handle(KeycloakLoginCommand request, CancellationToken cancellationToken)
    {
        // 1. 校验Keycloak Token有效性
        var handler = new JwtSecurityTokenHandler();
        var validationParameters = new TokenValidationParameters
        {
            ValidIssuer = _config["Keycloak:Authority"],
            ValidAudience = _config["Keycloak:Audience"],
            IssuerSigningKeyResolver = (token, securityToken, kid, parameters) =>
            {
                var httpClient = new HttpClient();
                var keySet = httpClient.GetFromJsonAsync<JsonWebKeySet>($"{_config["Keycloak:Authority"]}/protocol/openid-connect/certs", cancellationToken).Result;
                return keySet.GetKeyById(kid);
            },
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateIssuerSigningKey = true,
            ClockSkew = TimeSpan.Zero
        };

        try
        {
            var claimsPrincipal = handler.ValidateToken(request.KeycloakAccessToken, validationParameters, out _);
            // 2. 从Keycloak Token中获取邮箱匹配本地用户
            var email = claimsPrincipal.FindFirstValue(ClaimTypes.Email);
            var user = await _userManager.FindByEmailAsync(email);
            
            // 可选逻辑:如果本地不存在该用户,可自动创建对应账号
            if (user == null)
                throw new BadRequestException("User not found in local system");
            
            UserManagement.ForbiddenForLoginUser(user);
            
            // 3. 完全复用原有登录的token生成、refresh token存储逻辑
            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);
        }
        catch (SecurityTokenException)
        {
            throw new BadRequestException("Invalid Keycloak token");
        }
    }
}

第三步:授权逻辑零修改适配

你原有全局授权配置、CustomAuthorizationAttribute都是基于ClaimTypes.Role做校验,两种登录方式生成的Claims身份都会携带该字段,无需做任何调整即可同时适配两种登录的权限校验。

场景1适配说明

针对admin@gmail.com同时存在本地数据库和Keycloak的场景,两种登录方式都会匹配到同一个本地用户,最终角色权限以你本地aspnet users表的role字段为准。如果需要以Keycloak侧的角色为准,仅需在Keycloak登录逻辑中用Keycloak返回的角色值更新本地用户的role字段即可。

注意事项

  • Keycloak客户端配置需要开启对应登录流(推荐授权码流),并添加Mapper将角色信息映射到Token的role Claim中
  • 你原有本地账号密码登录的所有逻辑完全保留,没有任何侵入性修改

内容的提问来源于stack exchange,提问作者Arzu Suleymanov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.10 00:42:02