6

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

 2 years ago
source link: https://www.cnblogs.com/lori/p/15573380.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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的结果

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK