• keycloak~uma远程资源授权对接asp.net core


    官方的keycloak的适配器并没有提供.net版本的,所以我们需要自己去实现一下,目前打算把资源服务器对接KC之后,让资源服务器的API接口通过KC的UMA授权方式来管理起来,所以需要对这个功能进行开发,springboot版本官方已经实现,.net core版本我们自己实现了一下,对UMA授权不清楚的同学可以先看我这篇文章《keycloak~授权功能的使用》。

    • 添加标准的依赖包
    <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.13" />
    
    • 如果你只用kc做认证,授权自己去实现,你可以直接引用OIDC包,然后对OIDC产生的token进行解析,拿到kc颁发的角色即可
    <PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="3.1.13" />
    
    • OIDC方式,本例子中的实现,当用户没有登录时,会重定向到KC登录页进行认证
            /// <summary>
            /// OIDC认证
            /// </summary>
            /// <param name="services"></param>
            /// <param name="configuration"></param>
            /// <param name="scopes"></param>
            /// <returns></returns>
            public static AuthenticationBuilder addKcOidc(this IServiceCollection services, IConfiguration configuration, ICollection<string> scopes)
            {
                return services.AddAuthentication(options =>
                {
                    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
                })
                  .AddCookie()//开启cookie的支持
                  .AddOpenIdConnect(options =>
                {
                    options.Authority = configuration["Oidc:Authority"];
                    options.ClientId = configuration["Oidc:ClientId"];
                    options.ClientSecret = configuration["Oidc:ClientSecret"];
                    options.SaveTokens = true;
                    options.ResponseType = OpenIdConnectResponseType.CodeIdTokenToken;
                    options.RequireHttpsMetadata = false;
                    options.GetClaimsFromUserInfoEndpoint = true;
                    options.SaveTokens = true;
                    options.Scope.Clear();
                    foreach (var scope in scopes)
                    {
                        options.Scope.Add(scope);
                    }
    
                    options.Events = new OpenIdConnectEvents
                    {
    
                        OnTokenValidated = context =>
                        {
                            //获取到了id_token
                            var identity = context.Principal.Identity as ClaimsIdentity;
    
                            var token = context.ProtocolMessage.AccessToken;
                            if (token != null)
                            {
                                var payload = token.Split(".")[1];
                                string payloadJson = Encoding.UTF8.GetString(Base64UrlDecode(payload));
                                JObject json = JObject.Parse(payloadJson);
    
                                if (json.ContainsKey("realm_access"))
                                {
                                    var access = json["realm_access"].Values();
                                    foreach (var role in access.Values())
                                    {
                                        identity.AddClaim(new Claim(ClaimTypes.Role, role.ToString()));
                                    }
    
                                }
                                // 客户端角色
    
                                if (json.ContainsKey("resource_access"))
                                {
                                    var access = json["resource_access"].Values();
                                    foreach (var role in access["roles"].Values())
                                    {
                                        identity.AddClaim(new Claim(ClaimTypes.Role, role.ToString()));
                                    }
                                }
                            }
    
                            return Task.CompletedTask;
                        },
                        OnTokenResponseReceived = context =>
                        {
                            return Task.CompletedTask;
                        },
                    };
    
    
                });
            }
    
    • UMA远程授权,本例子中,当用户访问接口时,没有带着token,将返回401,如果token没有权限将返回403
    /// <summary>
            /// uma远程资源
            /// </summary>
            /// <param name="services"></param>
            /// <param name="configuration"></param>
            /// <param name="scopes"></param>
            /// <returns></returns>
            public static AuthenticationBuilder addKcUma(this IServiceCollection services, IConfiguration configuration)
            {
                return services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
                {
                    options.Authority = configuration["Oidc:Authority"];
                    options.Audience = configuration["Oidc:ClientId"];
                    options.IncludeErrorDetails = true;
                    options.RequireHttpsMetadata = false;
                    options.SaveToken = true;
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateAudience = false,
                        ValidateIssuer = true,
                        ValidIssuer = configuration["Oidc:Authority"],
                        ValidateLifetime = false
                    };
    
                    options.Events = new JwtBearerEvents
                    {
    
    
                        OnTokenValidated = context =>
                        {
                            //获取到了id_token
                            var identity = context.Principal.Identity as ClaimsIdentity;
    
                            if (context.Request.Headers.ContainsKey(AuthName))
                            {
                                var token = context.Request.Headers[AuthName].ToString();
                                if (token != null)
                                {
                                    var provider = services.BuildServiceProvider();//get an instance of IServiceProvider
                                    var clientFactory = provider.GetService<IHttpClientFactory>();
                                    string uma = getUmaToken(clientFactory, options.Authority, options.Audience, token).Result;
                                    // 客户端所拥有的资源
                                    List<UmaResource> umaResources = getUmaPermissions(uma);
                                    // 本服务器的所有资源
                                    List<string> allResources = getServerUmaPermissions(clientFactory, options.Authority, options.Audience, configuration["Oidc:ClientSecret"]).Result;
                                    // 过滤本服务器的资源
                                    umaResources = umaResources.Where(i => allResources.Contains(i.rsid)).ToList();
                                    // 当前url
                                    string currentUrl = context.Request.Path;
                                    foreach (UmaResource item in umaResources)
                                    {
                                        // 校验当前url是否在客户端授权的url中
                                        List<string> urls = getUmaUrls(clientFactory, options.Authority, item.rsid, token).Result;
                                        foreach (string url in urls)
                                        {
                                            if (url.EndsWith("*"))
                                            {
                                                if (currentUrl.Contains(url.TrimEnd('*')))
                                                    return Task.CompletedTask;
                                            }
                                            else
                                            {
                                                if (url.Equals(currentUrl))
                                                    return Task.CompletedTask;
                                            }
                                        }
    
                                    }
                                }
                            }
                            var payload = JsonConvert.SerializeObject(new { Code = "403", Message = "很抱歉,您无权访问该接口" });
                            context.Response.ContentType = "application/json";
                            context.Response.StatusCode = StatusCodes.Status403Forbidden;
                            context.Response.WriteAsync(payload);
                            return Task.FromResult(0);
                        }
    
    
                    };
    
                });
    
    
            }
    
    
    • 在startup中去注册我们的认证方式
    public void ConfigureServices(IServiceCollection services)
    {
               
                services.AddControllers();
                services.addKcUma(Configuration);
                services.AddAuthorization();
                services.AddHttpClient();
    }
    
    • 未认证的资源将出现403的结果
  • 相关阅读:
    SpringMVC之数据处理及跳转
    SpringMVC之控制器Controller和RestFul风格
    第一个SpringMVC程序
    什么是SpringMVC
    Spring声明式事务
    【嘎】字符串-反转字符串
    【嘎】字符串-字符串中的单词数
    【嘎】二叉树-226. 翻转二叉树
    【嘎】数组-面试题 01.07. 旋转矩阵-看题解
    【嘎】字符串-统计位数为偶数的数字
  • 原文地址:https://www.cnblogs.com/lori/p/15573380.html
Copyright © 2020-2023  润新知