AspNetCore 鉴权 <2.0>

概览

授权(Authentization):通过获取凭证中的基本信息,来判断用户能做什么,不能做什么。

授权也是通过中间件实现的,而且中间件注册时,授权只能放在认证的后面,因为认证其实是给用户颁发票据,其实就是context.user初始化,在授权将检查上一步颁发的票据中对应身份的声明,看是否由权限访问。如果权限出现问题会调用认证模型中的对应方法。所以认证授权是紧密相关的。

授权设计模式

概览

image-20220406221404361

授权的核心是策略(AuthorizationPolicy),授权中间件找到注册的策略并提取Requirement,调用所有的IAuthorizationHandler的HandleAsync方法,获得Result,如果成功调用下一个中间件,失败则调用相应的认证方法。

AuthorizationMiddleware

  • 通过AuthorizationPolicy的CombineAsync获取AuthorizationPolicyr策略
  • 通过policyEvaluator调用AuthenticateAsync方法对策略中认证相关进行认证相关操作,并更新用户票据
  • 通过policyEvaluator调用AuthorizeAsync方法调用IAuthorizationService服务的AuthorizeAsync方法,从而调用所有IAuthorizationHandler的HandleAsync进行鉴权,并获得鉴权结果
  • 通过IAuthorizationMiddlewareResultHandler的HandleAsync方法对鉴权结果进行处理。
  public class AuthorizationMiddleware
  {
    private const string SuppressUseHttpContextAsAuthorizationResource = "Microsoft.AspNetCore.Authorization.SuppressUseHttpContextAsAuthorizationResource";
    private const string AuthorizationMiddlewareInvokedWithEndpointKey = "__AuthorizationMiddlewareWithEndpointInvoked";
    private static readonly object AuthorizationMiddlewareWithEndpointInvokedValue = new object();
    private readonly RequestDelegate _next;
    private readonly IAuthorizationPolicyProvider _policyProvider;

    public AuthorizationMiddleware(
      RequestDelegate next,
      IAuthorizationPolicyProvider policyProvider)
    {
      this._next = next ?? throw new ArgumentNullException(nameof (next));
      this._policyProvider = policyProvider ?? throw new ArgumentNullException(nameof (policyProvider));
    }

    public async Task Invoke(HttpContext context)
    {
      Endpoint endpoint = context != null ? context.GetEndpoint() : throw new ArgumentNullException(nameof (context));
      if (endpoint != null)
        context.Items[(object) "__AuthorizationMiddlewareWithEndpointInvoked"] = AuthorizationMiddleware.AuthorizationMiddlewareWithEndpointInvokedValue;
      Endpoint endpoint1 = endpoint;
      AuthorizationPolicy policy = await AuthorizationPolicy.CombineAsync(this._policyProvider, (IEnumerable<IAuthorizeData>) ((endpoint1 != null ? (object) endpoint1.Metadata.GetOrderedMetadata<IAuthorizeData>() : (object) null) ?? (object) Array.Empty<IAuthorizeData>()));
      IPolicyEvaluator policyEvaluator;
      if (policy == null)
      {
        await this._next(context);
        endpoint = (Endpoint) null;
        policy = (AuthorizationPolicy) null;
        policyEvaluator = (IPolicyEvaluator) null;
      }
      else
      {
        policyEvaluator = context.RequestServices.GetRequiredService<IPolicyEvaluator>();
        AuthenticateResult authenticationResult = await policyEvaluator.AuthenticateAsync(policy, context);
        if (endpoint?.Metadata.GetMetadata<IAllowAnonymous>() != null)
        {
          await this._next(context);
          endpoint = (Endpoint) null;
          policy = (AuthorizationPolicy) null;
          policyEvaluator = (IPolicyEvaluator) null;
        }
        else
        {
          bool isEnabled;
          object resource = !(AppContext.TryGetSwitch("Microsoft.AspNetCore.Authorization.SuppressUseHttpContextAsAuthorizationResource", out isEnabled) & isEnabled) ? (object) context : (object) endpoint;
          PolicyAuthorizationResult authorizeResult = await policyEvaluator.AuthorizeAsync(policy, authenticationResult, context, resource);
          await context.RequestServices.GetRequiredService<IAuthorizationMiddlewareResultHandler>().HandleAsync(this._next, context, policy, authorizeResult);
          endpoint = (Endpoint) null;
          policy = (AuthorizationPolicy) null;
          policyEvaluator = (IPolicyEvaluator) null;
        }
      }
    }
  }

AuthorizationPolicy

该对象实际就包装了requirements,并通过AuthorizationPolicyBuilder对象创建自己

  public class AuthorizationPolicy
  {
    public AuthorizationPolicy(
      IEnumerable<IAuthorizationRequirement> requirements,
      IEnumerable<string> authenticationSchemes)
    {
      if (requirements == null)
        throw new ArgumentNullException(nameof (requirements));
      if (authenticationSchemes == null)
        throw new ArgumentNullException(nameof (authenticationSchemes));
      this.Requirements = requirements.Any<IAuthorizationRequirement>() ? (IReadOnlyList<IAuthorizationRequirement>) new List<IAuthorizationRequirement>(requirements).AsReadOnly() : throw new InvalidOperationException(Resources.Exception_AuthorizationPolicyEmpty);
      this.AuthenticationSchemes = (IReadOnlyList<string>) new List<string>(authenticationSchemes).AsReadOnly();
    }

    public IReadOnlyList<IAuthorizationRequirement> Requirements { get; }

    public IReadOnlyList<string> AuthenticationSchemes { get; }

    public static AuthorizationPolicy Combine(
      params AuthorizationPolicy[] policies)
    {
      return policies != null ? AuthorizationPolicy.Combine((IEnumerable<AuthorizationPolicy>) policies) : throw new ArgumentNullException(nameof (policies));
    }

    public static AuthorizationPolicy Combine(
      IEnumerable<AuthorizationPolicy> policies)
    {
      if (policies == null)
        throw new ArgumentNullException(nameof (policies));
      AuthorizationPolicyBuilder authorizationPolicyBuilder = new AuthorizationPolicyBuilder(Array.Empty<string>());
      foreach (AuthorizationPolicy policy in policies)
        authorizationPolicyBuilder.Combine(policy);
      return authorizationPolicyBuilder.Build();
    }

    public static async Task<AuthorizationPolicy?> CombineAsync(
      IAuthorizationPolicyProvider policyProvider,
      IEnumerable<IAuthorizeData> authorizeData)
    {
      if (policyProvider == null)
        throw new ArgumentNullException(nameof (policyProvider));
      if (authorizeData == null)
        throw new ArgumentNullException(nameof (authorizeData));
      bool flag1 = false;
      if (authorizeData is IList<IAuthorizeData> authorizeDataList)
        flag1 = authorizeDataList.Count == 0;
      AuthorizationPolicyBuilder policyBuilder = (AuthorizationPolicyBuilder) null;
      if (!flag1)
      {
        foreach (IAuthorizeData authorizeDatum in authorizeData)
        {
          if (policyBuilder == null)
            policyBuilder = new AuthorizationPolicyBuilder(Array.Empty<string>());
          bool flag2 = true;
          if (!string.IsNullOrWhiteSpace(authorizeDatum.Policy))
          {
            policyBuilder.Combine(await policyProvider.GetPolicyAsync(authorizeDatum.Policy) ?? throw new InvalidOperationException(Resources.FormatException_AuthorizationPolicyNotFound((object) authorizeDatum.Policy)));
            flag2 = false;
          }
          string[] source = authorizeDatum.Roles?.Split(',');
          if (source != null && source.Length != 0)
          {
            policyBuilder.RequireRole(((IEnumerable<string>) source).Where<string>((Func<string, bool>) (r => !string.IsNullOrWhiteSpace(r))).Select<string, string>((Func<string, string>) (r => r.Trim())));
            flag2 = false;
          }
          string[] strArray = authorizeDatum.AuthenticationSchemes?.Split(',');
          if (strArray != null && strArray.Length != 0)
          {
            foreach (string str in strArray)
            {
              if (!string.IsNullOrWhiteSpace(str))
                policyBuilder.AuthenticationSchemes.Add(str.Trim());
            }
          }
          if (flag2)
          {
            AuthorizationPolicyBuilder authorizationPolicyBuilder = policyBuilder;
            authorizationPolicyBuilder.Combine(await policyProvider.GetDefaultPolicyAsync());
            authorizationPolicyBuilder = (AuthorizationPolicyBuilder) null;
          }
        }
      }
      if (policyBuilder == null)
      {
        AuthorizationPolicy fallbackPolicyAsync = await policyProvider.GetFallbackPolicyAsync();
        if (fallbackPolicyAsync != null)
          return fallbackPolicyAsync;
      }
      return policyBuilder?.Build();
    }
  }

AuthorizationPolicyBuilder

  public class AuthorizationPolicyBuilder
  {
    public AuthorizationPolicyBuilder(params string[] authenticationSchemes) => this.AddAuthenticationSchemes(authenticationSchemes);

    public AuthorizationPolicyBuilder(AuthorizationPolicy policy) => this.Combine(policy);

    public IList<IAuthorizationRequirement> Requirements { get; set; } = (IList<IAuthorizationRequirement>) new List<IAuthorizationRequirement>();

    public IList<string> AuthenticationSchemes { get; set; } = (IList<string>) new List<string>();

    public AuthorizationPolicyBuilder AddAuthenticationSchemes(
      params string[] schemes)
    {
      foreach (string scheme in schemes)
        this.AuthenticationSchemes.Add(scheme);
      return this;
    }

    public AuthorizationPolicyBuilder AddRequirements(
      params IAuthorizationRequirement[] requirements)
    {
      foreach (IAuthorizationRequirement requirement in requirements)
        this.Requirements.Add(requirement);
      return this;
    }

    public AuthorizationPolicyBuilder Combine(AuthorizationPolicy policy)
    {
      if (policy == null)
        throw new ArgumentNullException(nameof (policy));
      this.AddAuthenticationSchemes(policy.AuthenticationSchemes.ToArray<string>());
      this.AddRequirements(policy.Requirements.ToArray<IAuthorizationRequirement>());
      return this;
    }

    public AuthorizationPolicyBuilder RequireClaim(
      string claimType,
      params string[] allowedValues)
    {
      return claimType != null ? this.RequireClaim(claimType, (IEnumerable<string>) allowedValues) : throw new ArgumentNullException(nameof (claimType));
    }

    public AuthorizationPolicyBuilder RequireClaim(
      string claimType,
      IEnumerable<string> allowedValues)
    {
      if (claimType == null)
        throw new ArgumentNullException(nameof (claimType));
      this.Requirements.Add((IAuthorizationRequirement) new ClaimsAuthorizationRequirement(claimType, allowedValues));
      return this;
    }

    public AuthorizationPolicyBuilder RequireClaim(string claimType)
    {
      if (claimType == null)
        throw new ArgumentNullException(nameof (claimType));
      this.Requirements.Add((IAuthorizationRequirement) new ClaimsAuthorizationRequirement(claimType, (IEnumerable<string>) null));
      return this;
    }

    public AuthorizationPolicyBuilder RequireRole(params string[] roles) => roles != null ? this.RequireRole((IEnumerable<string>) roles) : throw new ArgumentNullException(nameof (roles));

    public AuthorizationPolicyBuilder RequireRole(
      IEnumerable<string> roles)
    {
      if (roles == null)
        throw new ArgumentNullException(nameof (roles));
      this.Requirements.Add((IAuthorizationRequirement) new RolesAuthorizationRequirement(roles));
      return this;
    }

    public AuthorizationPolicyBuilder RequireUserName(string userName)
    {
      if (userName == null)
        throw new ArgumentNullException(nameof (userName));
      this.Requirements.Add((IAuthorizationRequirement) new NameAuthorizationRequirement(userName));
      return this;
    }

    public AuthorizationPolicyBuilder RequireAuthenticatedUser()
    {
      this.Requirements.Add((IAuthorizationRequirement) new DenyAnonymousAuthorizationRequirement());
      return this;
    }

    public AuthorizationPolicyBuilder RequireAssertion(
      Func<AuthorizationHandlerContext, bool> handler)
    {
      if (handler == null)
        throw new ArgumentNullException(nameof (handler));
      this.Requirements.Add((IAuthorizationRequirement) new AssertionRequirement(handler));
      return this;
    }

    public AuthorizationPolicyBuilder RequireAssertion(
      Func<AuthorizationHandlerContext, Task<bool>> handler)
    {
      if (handler == null)
        throw new ArgumentNullException(nameof (handler));
      this.Requirements.Add((IAuthorizationRequirement) new AssertionRequirement(handler));
      return this;
    }

    public AuthorizationPolicy Build() => new AuthorizationPolicy((IEnumerable<IAuthorizationRequirement>) this.Requirements, this.AuthenticationSchemes.Distinct<string>());
  }

IPolicyEvaluator

通过这两个接口分别处理策略的认证和授权

  public interface IPolicyEvaluator
  {
    Task<AuthenticateResult> AuthenticateAsync(
      AuthorizationPolicy policy,
      HttpContext context);

    Task<PolicyAuthorizationResult> AuthorizeAsync(
      AuthorizationPolicy policy,
      AuthenticateResult authenticationResult,
      HttpContext context,
      object? resource);
  }

PolicyEvaluator

  public class PolicyEvaluator : IPolicyEvaluator
  {
    private readonly IAuthorizationService _authorization;

    public PolicyEvaluator(IAuthorizationService authorization) => this._authorization = authorization;

    public virtual async Task<AuthenticateResult> AuthenticateAsync(
      AuthorizationPolicy policy,
      HttpContext context)
    {
      if (policy.AuthenticationSchemes == null || policy.AuthenticationSchemes.Count <= 0)
        return context.User?.Identity?.IsAuthenticated.GetValueOrDefault() ? AuthenticateResult.Success(new AuthenticationTicket(context.User, "context.User")) : AuthenticateResult.NoResult();
      ClaimsPrincipal newPrincipal = (ClaimsPrincipal) null;
      foreach (string authenticationScheme in (IEnumerable<string>) policy.AuthenticationSchemes)
      {
        AuthenticateResult authenticateResult = await context.AuthenticateAsync(authenticationScheme);
        if (authenticateResult != null && authenticateResult.Succeeded)
          newPrincipal = SecurityHelper.MergeUserPrincipal(newPrincipal, authenticateResult.Principal);
      }
      if (newPrincipal != null)
      {
        context.User = newPrincipal;
        return AuthenticateResult.Success(new AuthenticationTicket(newPrincipal, string.Join(";", (IEnumerable<string>) policy.AuthenticationSchemes)));
      }
      context.User = new ClaimsPrincipal((IIdentity) new ClaimsIdentity());
      return AuthenticateResult.NoResult();
    }

    public virtual async Task<PolicyAuthorizationResult> AuthorizeAsync(
      AuthorizationPolicy policy,
      AuthenticateResult authenticationResult,
      HttpContext context,
      object? resource)
    {
      if (policy == null)
        throw new ArgumentNullException(nameof (policy));
      AuthorizationResult authorizationResult = await this._authorization.AuthorizeAsync(context.User, resource, policy);
      return !authorizationResult.Succeeded ? (authenticationResult.Succeeded ? PolicyAuthorizationResult.Forbid(authorizationResult.Failure) : PolicyAuthorizationResult.Challenge()) : PolicyAuthorizationResult.Success();
    }
  }

IAuthorizationService

  public interface IAuthorizationService
  {
    Task<AuthorizationResult> AuthorizeAsync(
      ClaimsPrincipal user,
      object? resource,
      IEnumerable<IAuthorizationRequirement> requirements);

    Task<AuthorizationResult> AuthorizeAsync(
      ClaimsPrincipal user,
      object? resource,
      string policyName);
  }

DefaultAuthorizationService

  public class DefaultAuthorizationService : IAuthorizationService
  {
    private readonly AuthorizationOptions _options;
    private readonly IAuthorizationHandlerContextFactory _contextFactory;
    private readonly IAuthorizationHandlerProvider _handlers;
    private readonly IAuthorizationEvaluator _evaluator;
    private readonly IAuthorizationPolicyProvider _policyProvider;
    private readonly ILogger _logger;

    public DefaultAuthorizationService(
      IAuthorizationPolicyProvider policyProvider,
      IAuthorizationHandlerProvider handlers,
      ILogger<DefaultAuthorizationService> logger,
      IAuthorizationHandlerContextFactory contextFactory,
      IAuthorizationEvaluator evaluator,
      IOptions<AuthorizationOptions> options)
    {
      if (options == null)
        throw new ArgumentNullException(nameof (options));
      if (policyProvider == null)
        throw new ArgumentNullException(nameof (policyProvider));
      if (handlers == null)
        throw new ArgumentNullException(nameof (handlers));
      if (logger == null)
        throw new ArgumentNullException(nameof (logger));
      if (contextFactory == null)
        throw new ArgumentNullException(nameof (contextFactory));
      if (evaluator == null)
        throw new ArgumentNullException(nameof (evaluator));
      this._options = options.Value;
      this._handlers = handlers;
      this._policyProvider = policyProvider;
      this._logger = (ILogger) logger;
      this._evaluator = evaluator;
      this._contextFactory = contextFactory;
    }

    public virtual async Task<AuthorizationResult> AuthorizeAsync(
      ClaimsPrincipal user,
      object? resource,
      IEnumerable<IAuthorizationRequirement> requirements)
    {
      if (requirements == null)
        throw new ArgumentNullException(nameof (requirements));
      AuthorizationHandlerContext authContext = this._contextFactory.CreateContext(requirements, user, resource);
      foreach (IAuthorizationHandler authorizationHandler in await this._handlers.GetHandlersAsync(authContext))
      {
        await authorizationHandler.HandleAsync(authContext);
        if (!this._options.InvokeHandlersAfterFailure)
        {
          if (authContext.HasFailed)
            break;
        }
      }
      AuthorizationResult authorizationResult1 = this._evaluator.Evaluate(authContext);
      if (authorizationResult1.Succeeded)
        this._logger.UserAuthorizationSucceeded();
      else
        this._logger.UserAuthorizationFailed(authorizationResult1.Failure);
      AuthorizationResult authorizationResult2 = authorizationResult1;
      authContext = (AuthorizationHandlerContext) null;
      return authorizationResult2;
    }

    public virtual async Task<AuthorizationResult> AuthorizeAsync(
      ClaimsPrincipal user,
      object? resource,
      string policyName)
    {
      DefaultAuthorizationService service = this;
      if (policyName == null)
        throw new ArgumentNullException(nameof (policyName));
      AuthorizationPolicy policyAsync = await service._policyProvider.GetPolicyAsync(policyName);
      if (policyAsync == null)
        throw new InvalidOperationException("No policy found: " + policyName + ".");
      return await service.AuthorizeAsync(user, resource, policyAsync);
    }
  }

IAuthorizationHandlerContextFactory

在调用IAuthorizationHandler之前会构建上下文环境,通过AuthorizationHandlerContextFactory创建一个AuthorizationHandlerContext对象。工厂方法

  public interface IAuthorizationHandlerContextFactory
  {
    AuthorizationHandlerContext CreateContext(
      IEnumerable<IAuthorizationRequirement> requirements,
      ClaimsPrincipal user,
      object? resource);
  }

DefaultAuthorizationHandlerContextFactory

  public class DefaultAuthorizationHandlerContextFactory : IAuthorizationHandlerContextFactory
  {
    public virtual AuthorizationHandlerContext CreateContext(
      IEnumerable<IAuthorizationRequirement> requirements,
      ClaimsPrincipal user,
      object? resource)
    {
      return new AuthorizationHandlerContext(requirements, user, resource);
    }
  }

AuthorizationHandlerContext

该上下文保存着用户,requirements其是从policy中取的,

  public class AuthorizationHandlerContext
  {
    private HashSet<IAuthorizationRequirement> _pendingRequirements;
    private bool _failCalled;
    private bool _succeedCalled;

    public AuthorizationHandlerContext(
      IEnumerable<IAuthorizationRequirement> requirements,
      ClaimsPrincipal user,
      object? resource)
    {
      this.Requirements = requirements != null ? requirements : throw new ArgumentNullException(nameof (requirements));
      this.User = user;
      this.Resource = resource;
      this._pendingRequirements = new HashSet<IAuthorizationRequirement>(requirements);
    }

    public virtual IEnumerable<IAuthorizationRequirement> Requirements { get; }

    public virtual ClaimsPrincipal User { get; }

    public virtual object? Resource { get; }

    public virtual IEnumerable<IAuthorizationRequirement> PendingRequirements => (IEnumerable<IAuthorizationRequirement>) this._pendingRequirements;

    public virtual bool HasFailed => this._failCalled;

    public virtual bool HasSucceeded => !this._failCalled && this._succeedCalled && !this.PendingRequirements.Any<IAuthorizationRequirement>();

    public virtual void Fail() => this._failCalled = true;

    public virtual void Succeed(IAuthorizationRequirement requirement)
    {
      this._succeedCalled = true;
      this._pendingRequirements.Remove(requirement);
    }
  }

IAuthorizationMiddlewareResultHandler

通过其处理结果并调用认证相关接口

  public interface IAuthorizationMiddlewareResultHandler
  {
    Task HandleAsync(
      RequestDelegate next,
      HttpContext context,
      AuthorizationPolicy policy,
      PolicyAuthorizationResult authorizeResult);
  }

AuthorizationMiddlewareResultHandler

  public class AuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler
  {
    public async Task HandleAsync(
      RequestDelegate next,
      HttpContext context,
      AuthorizationPolicy policy,
      PolicyAuthorizationResult authorizeResult)
    {
      if (authorizeResult.Challenged)
      {
        if (policy.AuthenticationSchemes.Count > 0)
        {
          foreach (string authenticationScheme in (IEnumerable<string>) policy.AuthenticationSchemes)
            await context.ChallengeAsync(authenticationScheme);
        }
        else
          await context.ChallengeAsync();
      }
      else if (authorizeResult.Forbidden)
      {
        if (policy.AuthenticationSchemes.Count > 0)
        {
          foreach (string authenticationScheme in (IEnumerable<string>) policy.AuthenticationSchemes)
            await context.ForbidAsync(authenticationScheme);
        }
        else
          await context.ForbidAsync();
      }
      else
        await next(context);
    }
  }

依赖注入

AuthorizationServiceCollectionExtensions

  public static class AuthorizationServiceCollectionExtensions
  {
    public static IServiceCollection AddAuthorizationCore(
      this IServiceCollection services)
    {
      if (services == null)
        throw new ArgumentNullException(nameof (services));
      services.AddOptions();
      services.TryAdd(ServiceDescriptor.Transient<IAuthorizationService, DefaultAuthorizationService>());
      services.TryAdd(ServiceDescriptor.Transient<IAuthorizationPolicyProvider, DefaultAuthorizationPolicyProvider>());
      services.TryAdd(ServiceDescriptor.Transient<IAuthorizationHandlerProvider, DefaultAuthorizationHandlerProvider>());
      services.TryAdd(ServiceDescriptor.Transient<IAuthorizationEvaluator, DefaultAuthorizationEvaluator>());
      services.TryAdd(ServiceDescriptor.Transient<IAuthorizationHandlerContextFactory, DefaultAuthorizationHandlerContextFactory>());
      services.TryAddEnumerable(ServiceDescriptor.Transient<IAuthorizationHandler, PassThroughAuthorizationHandler>());
      return services;
    }

    public static IServiceCollection AddAuthorizationCore(
      this IServiceCollection services,
      Action<AuthorizationOptions> configure)
    {
      if (services == null)
        throw new ArgumentNullException(nameof (services));
      if (configure == null)
        throw new ArgumentNullException(nameof (configure));
      services.Configure<AuthorizationOptions>(configure);
      return services.AddAuthorizationCore();
    }
  }

PolicyServiceCollectionExtensions

  public static class PolicyServiceCollectionExtensions
  {
    public static IServiceCollection AddAuthorizationPolicyEvaluator(
      this IServiceCollection services)
    {
      if (services == null)
        throw new ArgumentNullException(nameof (services));
      services.TryAddSingleton<AuthorizationPolicyMarkerService>();
      services.TryAddTransient<IPolicyEvaluator, PolicyEvaluator>();
      services.TryAddTransient<IAuthorizationMiddlewareResultHandler, AuthorizationMiddlewareResultHandler>();
      return services;
    }

    public static IServiceCollection AddAuthorization(
      this IServiceCollection services)
    {
      if (services == null)
        throw new ArgumentNullException(nameof (services));
      services.AddAuthorizationCore();
      services.AddAuthorizationPolicyEvaluator();
      return services;
    }

    public static IServiceCollection AddAuthorization(
      this IServiceCollection services,
      Action<AuthorizationOptions> configure)
    {
      if (services == null)
        throw new ArgumentNullException(nameof (services));
      services.AddAuthorizationCore(configure);
      services.AddAuthorizationPolicyEvaluator();
      return services;
    }
  }
posted @ 2022-04-07 08:33  阿杜聊编程  阅读(175)  评论(0编辑  收藏  举报