在《再谈使用IdentityServer实现ASP.NET Core Web API的认证与授权》一文中,我又重新总结了IdentityServer中ApiScope和ApiResource的用法,通过对两者的设置,实现了基于Claim的API授权。今天,我们更进一步,引入Ocelot API网关,并由Ocelot API网关来传递Claims实现授权。
理论上讲,在引入Ocelot API网关后,API的访问授权可以有以下三种模式:
- 完全由Ocelot托管,只需要在配置中设置RouteClaimsRequirement即可
- 由API自己处理,使用Identity中的Claims信息来设置授权策略
- 由API自己处理,Ocelot仅将Claims信息传递给API,API使用自定义逻辑完成授权
这里我们讨论第二种模式。
首先引入Ocelot API网关,可以参考我前面写的《ASP.NET Core中Ocelot的使用:API网关的应用》一文。之后,配置Ocelot API网关,使其使用IdentityServer的身份认证:
builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer("management-provider-key", options => { options.Authority = idsAuthority; options.Audience = "management"; });
当然,这里有必要列出所使用的Ocelot的配置文件:
{ "Routes": [ { "DownstreamPathTemplate": "/api/{everything}", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 9002 } ], "UpstreamPathTemplate": "/meeting-room-service/{everything}", "UpstreamHttpMethod": [ "Get", "Post", "Put", "Patch", "Delete", "Options" ], "SwaggerKey": "MeetingRoomService", "AuthenticationOptions": { "AuthenticationProviderKey": "management-provider-key", "AllowedScopes": [ "management.read", "management.create" ] }, "AddClaimsToRequest": { "scope": "Claims[scope] > value" } } ] }
此处定义了一个API端点的路由逻辑,并且在AuthenticationOptions配置中,设置了以下几个参数:
- AuthenticationProviderKey:与上文C#代码中AddJwtBearer方法调用的第一个参数匹配,表示这个API端点将使用之前所设置的那个Jwt Bearer认证方式相匹配,而在AddJwtBearer方法中,已经说明了Audience为management
- AllowedScopes表示该API所允许的ApiScope有哪些,可以参考前一篇文章来了解细节
此外,我们还使用了AddClaimsToRequest这个配置 ,表示希望Ocelot能够将User Claims传递到下游API中。
接下来,我们在上文中所使用的API中,将认证与授权相关的代码全部注释掉,直接同时运行Ocelot API网关、IdentityServer和API,然后调试GET请求,可以看到,虽然我们在Ocelot的配置中启用了AddClaimsToRequest,但在User Identity中并不能取得它的值:
这里就需要在API中,对获取的Access Token进行解析,由于我们已经获得了Access Token,所以,从流程上看,并不需要API再去访问IdentityServer来验证Access Token,只需要解析就可以了。在API中添加下面的代码:
builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(options => { options.RequireHttpsMetadata = false; options.SaveToken = true; options.TokenValidationParameters = CreateTokenValidationParameters(); }); app.UseAuthentication(); static TokenValidationParameters CreateTokenValidationParameters() => new() { ValidateIssuer = false, ValidateAudience = false, ValidateIssuerSigningKey = false, SignatureValidator = delegate (string token, TokenValidationParameters parameters) { var jwt = new JwtSecurityToken(token); return jwt; }, RequireExpirationTime = true, ValidateLifetime = true, ClockSkew = TimeSpan.Zero, RequireSignedTokens = false };
此时再次启动调试,查看User Claims,可以看到,我们已经能够拿到正确的值:
注意其中的scope Claim,也正是我们在进行基于ApiScope授权时所需要的。
重新启用API中被注释掉的授权相关的代码,再次调试API,如果在获取Access Token时,包含了management.read的Scope:
则可以访问GET方法:
但如果在请求Access Token的时候,没有包含management.read Scope:
则访问API就会得到403 Forbidden的错误: