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的结果
「其他文章」
- keycloak~自定义directgrant直接认证
- java~RMI引起的log4j漏洞
- k8s~Endpoints的使用之负载均衡
- keycloak~uma远程资源授权对接asp.net core
- es~存储部分字段
- maven编译后复制到目标位置
- keycloak~缓存的使用
- keycloak~授权功能的使用
- java~并行计算~大集合的并行处理
- keycloak~使用JDBC_PING实现k8s里的高可用
- keycloak~为认证提供者添加配置项
- skywalking的介绍
- springboot~disruptor异步队列
- keycloak~自定义rest接口
- springcloud~feign POST form-url-encoded data
- js~ajax获取后端HTTP状态的几种情况
- java~jackson实现接口的反序列化
- springboot~uaa~scope对实体的字段添加限制
- jenkins~pipeline~修改文件里的版本
- Activiti~相关概念