如何在dotnet core IdentityModel中使用scopes保护API端点
概述
我有一个使用OAuth2Introspection保护的dotNet Web API,授权逻辑由该Web API验证第三方签发的reference token完成。该Web API包含多个端点,部分端点需要不同的scope(例如read和readwrite)。
问题
我要如何通过scopes控制每个端点的访问权限?我希望实现类似如下代码的效果:
using System; using System.Collections.Generic; using System.Data.Odbc; using System.Data.SqlClient; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace API.Controllers { [ApiController] [Route("api/[controller]")] public class WeatherController : ControllerBase { private readonly ILogger<WeatherController> _logger; private readonly IConfiguration _config; public WeatherController(ILogger<WeatherController> logger, IConfiguration config) { _logger = logger; _config = config; } [HttpGet] [Authorize] [ScopeAuthorize("weather_read")] public ActionResult GetWeather() { return Ok(); } [HttpPost] [Authorize] [ScopeAuthorize("weather_readwrite")] public ActionResult SetWeather(string weather) { return Ok(); } } }
实现方案
你可以通过自定义授权策略+动态策略提供器+自定义特性的方式实现需求,全程不需要预先定义每个scope的固定策略,完全符合你想要的代码写法,步骤如下:
- 第一步:定义Scope授权要求类,继承
IAuthorizationRequirement
using Microsoft.AspNetCore.Authorization; public class ScopeRequirement : IAuthorizationRequirement { public string RequiredScope { get; } public ScopeRequirement(string requiredScope) { RequiredScope = requiredScope ?? throw new ArgumentNullException(nameof(requiredScope)); } }
- 第二步:实现Scope授权处理程序,从Token解析出的声明中校验所需scope
using System.Security.Claims; using Microsoft.AspNetCore.Authorization; public class ScopeHandler : AuthorizationHandler<ScopeRequirement> { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ScopeRequirement requirement) { // 兼容不同授权服务器返回的scope声明类型 var scopes = context.User.Claims .Where(c => c.Type == "scope" || c.Type == ClaimTypes.Scope) .SelectMany(c => c.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries)) .ToList(); if (scopes.Contains(requirement.RequiredScope)) { context.Succeed(requirement); } return Task.CompletedTask; } }
- 第三步:实现动态授权策略提供器,无需预先定义所有scope对应的策略
using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Options; public class ScopeAuthorizationPolicyProvider : IAuthorizationPolicyProvider { private const string POLICY_PREFIX = "Scope:"; private readonly DefaultAuthorizationPolicyProvider _defaultProvider; public ScopeAuthorizationPolicyProvider(IOptions<AuthorizationOptions> options) { _defaultProvider = new DefaultAuthorizationPolicyProvider(options); } public Task<AuthorizationPolicy> GetDefaultPolicyAsync() => _defaultProvider.GetDefaultPolicyAsync(); public Task<AuthorizationPolicy?> GetFallbackPolicyAsync() => _defaultProvider.GetFallbackPolicyAsync(); public Task<AuthorizationPolicy?> GetPolicyAsync(string policyName) { if (policyName.StartsWith(POLICY_PREFIX, StringComparison.OrdinalIgnoreCase)) { var scope = policyName.Substring(POLICY_PREFIX.Length); var policy = new AuthorizationPolicyBuilder() .AddRequirements(new ScopeRequirement(scope)) .Build(); return Task.FromResult<AuthorizationPolicy?>(policy); } return _defaultProvider.GetPolicyAsync(policyName); } }
- 第四步:实现你需要的
ScopeAuthorize自定义特性
using Microsoft.AspNetCore.Authorization; public class ScopeAuthorizeAttribute : AuthorizeAttribute { public ScopeAuthorizeAttribute(string scope) { Policy = $"Scope:{scope}"; } }
- 第五步:在Program.cs(.NET 6+)中注册相关服务,原有OAuth2内省配置保留即可
// 注册动态策略提供器 builder.Services.AddSingleton<IAuthorizationPolicyProvider, ScopeAuthorizationPolicyProvider>(); // 注册Scope授权处理程序 builder.Services.AddSingleton<IAuthorizationHandler, ScopeHandler>(); // 你原有配置保持不变即可 builder.Services.AddAuthentication(OAuth2IntrospectionDefaults.AuthenticationScheme) .AddOAuth2Introspection(options => { options.Authority = "你的授权服务地址"; options.ClientId = "你的API客户端ID"; options.ClientSecret = "你的API客户端密钥"; // 其余原有配置保留 }); builder.Services.AddAuthorization();
配置完成后你原有Controller里的写法完全可以正常运行,调用接口时如果Token不包含对应scope会直接返回403状态码。
内容的提问来源于stack exchange,提问作者user1913559
相关产品推荐
相关产品推荐

