ASP.NET Core 认证与授权[6]:授权策略是怎么执行的?
原文:ASP.NET Core 认证与授权[6]:授权策略是怎么执行的?

在上一章中,详细介绍了 ASP.NET Core 中的授权策略,在需要授权时,只需要在对应的Controler或者Action上面打上[Authorize]特性,并指定要执行的策略名称即可,但是,授权策略是怎么执行的呢?怀着一颗好奇的心,忍不住来探索一下它的执行流程。

目录

  1. MVC中的授权

    • AuthorizationApplicationModelProvider
    • AuthorizeFilter
  2. IPolicyEvaluator
    • AuthenticateAsync(AuthenticationSchemes)
    • AuthorizeAsync(Requirements)
  3. IAuthorizationService
    • IAuthorizationPolicyProvider
    • IAuthorizationHandlerContextFactory
    • IAuthorizationHandlerProvider
    • IAuthorizationEvaluator

在《(上一章》中提到,AuthorizeAttribute只是一个简单的实现了IAuthorizeData接口的特性,并且在 ASP.NET Core 授权系统中并没有使用到它。我们知道在认证中,还有一个UseAuthentication扩展方法来激活认证系统,但是在授权中并没有类似的机制。

这是因为当我们使用[Authorize]通常是在MVC中,由MVC来负责激活授权系统。本来在这个系列的文章中,我并不想涉及到MVC的知识,但是为了能更好的理解授权系统的执行,就来简单介绍一下MVC中与授权相关的知识。

MVC中的授权

当我们使用MVC时,首先会调用MVC的AddMvc扩展方法,用来注册一些MVC相关的服务:

public static IMvcBuilder AddMvc(this IServiceCollection services)
{var builder = services.AddMvcCore();builder.AddAuthorization();...
}public static IMvcCoreBuilder AddAuthorization(this IMvcCoreBuilder builder)
{AddAuthorizationServices(builder.Services);return builder;
}internal static void AddAuthorizationServices(IServiceCollection services)
{services.AddAuthenticationCore();services.AddAuthorization();services.AddAuthorizationPolicyEvaluator();services.TryAddEnumerable(ServiceDescriptor.Transient<IApplicationModelProvider, AuthorizationApplicationModelProvider>());
}

在上面AddAuthorizationServices中的前三个方法都属于 ASP.NET Core 《Security》项目中提供的扩展方法,其中前两个在前面几章已经介绍过了,对于AddAuthorizationPolicyEvaluator放到后面再来介绍,我们先来看一下MVC中的AuthorizationApplicationModelProvider

AuthorizationApplicationModelProvider

在MVC中有一个ApplicationModel的概念,它用来封装Controller, Filter, ApiExplorer等。对应的,在MVC中还提供了一系列的ApplicationModelProvider来初始化ApplicationModel的各个部分,而AuthorizationApplicationModelProvider就是用来初始化与授权相关的部分。

public class AuthorizationApplicationModelProvider : IApplicationModelProvider
{public void OnProvidersExecuting(ApplicationModelProviderContext context){foreach (var controllerModel in context.Result.Controllers){var controllerModelAuthData = controllerModel.Attributes.OfType<IAuthorizeData>().ToArray();if (controllerModelAuthData.Length > 0){controllerModel.Filters.Add(GetFilter(_policyProvider, controllerModelAuthData));}foreach (var attribute in controllerModel.Attributes.OfType<IAllowAnonymous>()){controllerModel.Filters.Add(new AllowAnonymousFilter());}foreach (var actionModel in controllerModel.Actions){var actionModelAuthData = actionModel.Attributes.OfType<IAuthorizeData>().ToArray();if (actionModelAuthData.Length > 0){actionModel.Filters.Add(GetFilter(_policyProvider, actionModelAuthData));}foreach (var attribute in actionModel.Attributes.OfType<IAllowAnonymous>()){actionModel.Filters.Add(new AllowAnonymousFilter());}}}}
}

如上,首先查找每个Controller中实现了IAuthorizeData接口的特性,然后将其转化为AuthorizeFilter并添加到Controller的Filter集合中,紧接着再查找实现了IAllowAnonymous接口的特性,将其转化为AllowAnonymousFilter过滤器也添加到Filter集合中,然后以同样的逻辑查找Action上的特性并添加到Action的Filter集合中。

其中的关键点就是将IAuthorizeData(也就是通过我们熟悉的[Authorize]特性)转化为MVC中的AuthorizeFilter过滤器:

public static AuthorizeFilter GetFilter(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authData)
{if (policyProvider.GetType() == typeof(DefaultAuthorizationPolicyProvider)){var policy = AuthorizationPolicy.CombineAsync(policyProvider, authData).GetAwaiter().GetResult();return new AuthorizeFilter(policy);}else{return new AuthorizeFilter(policyProvider, authData);}
}

CombineAsync在上一章的《AuthorizationPolicy》中已经介绍过了,我们往下看看AuthorizeFilter的实现。

AuthorizeFilter

在MVC中有一个AuthorizeFilter过滤器,类似我们在ASP.NET 4.x中所熟悉的[Authorize],它实现了IAsyncAuthorizationFilter接口,定义如下:

public class AuthorizeFilter : IAsyncAuthorizationFilter, IFilterFactory
{public AuthorizeFilter(AuthorizationPolicy policy) {}public AuthorizeFilter(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authorizeData) : this(authorizeData) {}public AuthorizeFilter(IEnumerable<IAuthorizeData> authorizeData) {}public IEnumerable<IAuthorizeData> AuthorizeData { get; }public AuthorizationPolicy Policy { get; }public virtual async Task OnAuthorizationAsync(AuthorizationFilterContext context){var effectivePolicy = Policy;if (effectivePolicy == null){effectivePolicy = await AuthorizationPolicy.CombineAsync(PolicyProvider, AuthorizeData);}var policyEvaluator = context.HttpContext.RequestServices.GetRequiredService<IPolicyEvaluator>();var authenticateResult = await policyEvaluator.AuthenticateAsync(effectivePolicy, context.HttpContext);if (context.Filters.Any(item => item is IAllowAnonymousFilter)){return;}var authorizeResult = await policyEvaluator.AuthorizeAsync(effectivePolicy, authenticateResult, context.HttpContext, context);... // 如果授权失败,返回ChallengeResult或ForbidResult}
}

AuthorizeFilter的OnAuthorizationAsync方法会在Action执行之前触发,其调用IPolicyEvaluator来完成授权,将执行流程切回到 ASP.NET Core 授权系统中。关于MVC中IApplicationModelProvider以及Filter的概念,在以后MVC系列的文章中再来详细介绍,下面就继续介绍 ASP.NET Core 的授权系统,也就是《Security》项目。

IPolicyEvaluator

IPolicyEvaluator是MVC调用授权系统的入口点,其定义如下:

public interface IPolicyEvaluator
{Task<AuthenticateResult> AuthenticateAsync(AuthorizationPolicy policy, HttpContext context);Task<PolicyAuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy, AuthenticateResult authenticationResult, HttpContext context, object resource);
}

在上面介绍的AddMVC中,调用了AddAuthorizationPolicyEvaluator扩展方法,它有如下定义:

public static class PolicyServiceCollectionExtensions
{public static IServiceCollection AddAuthorizationPolicyEvaluator(this IServiceCollection services){services.TryAdd(ServiceDescriptor.Transient<IPolicyEvaluator, PolicyEvaluator>());return services;}
}

由此可知IPolicyEvaluator的默认实现为PolicyEvaluator,我们就从它入手,来一步一步解剖 ASP.NET Core 授权系统的执行步骤。

AuthorizeFilter中,依次调到了AuthenticateAsyncAuthorizeAsync方法,我们就一一来看。

AuthenticateAsync(AuthenticationSchemes)

为什么还有一个AuthenticateAsync方法呢,这不是在认证阶段执行的吗?我们看下它的实现:

public class PolicyEvaluator : IPolicyEvaluator
{public virtual async Task<AuthenticateResult> AuthenticateAsync(AuthorizationPolicy policy, HttpContext context){if (policy.AuthenticationSchemes != null && policy.AuthenticationSchemes.Count > 0){ClaimsPrincipal newPrincipal = null;foreach (var scheme in policy.AuthenticationSchemes){var result = await context.AuthenticateAsync(scheme);if (result != null && result.Succeeded){newPrincipal = SecurityHelper.MergeUserPrincipal(newPrincipal, result.Principal);}}if (newPrincipal != null){context.User = newPrincipal;return AuthenticateResult.Success(new AuthenticationTicket(newPrincipal, string.Join(";", policy.AuthenticationSchemes)));}else{context.User = new ClaimsPrincipal(new ClaimsIdentity());return AuthenticateResult.NoResult();}}return (context.User?.Identity?.IsAuthenticated ?? false) ? AuthenticateResult.Success(new AuthenticationTicket(context.User, "context.User")): AuthenticateResult.NoResult();}
}

在《上一章》中,我们知道在AuthorizationPolicy中有AuthenticationSchemesIAuthorizationRequirement两个属性,并详细介绍介绍了Requirement,但是没有提到AuthenticationSchemes的调用。

那么,看到这里,也就大概明白了,它与Requirements的执行是完全独立的,并在它之前执行,用于重置Claims,那么为什么要重置呢?

在认证的章节介绍过,在认证阶段,只会执行默认的认证Scheme,context.User就是使用context.AuthenticateAsync(DefaultAuthenticateScheme)来赋值的,当我们希望使用非默认的Scheme,或者是想合并多个认证Scheme的Claims时,就需要使用基于Scheme的授权来重置Claims了。

它的实现也很简单,直接使用我们在授权策略中指定的Schemes来依次调用认证服务的AuthenticateAsync方法,并将生成的Claims合并,最后返回我们熟悉的AuthenticateResult认证结果。

AuthorizeAsync(Requirements)

接下来再看一下PolicyEvaluatorAuthorizeAsync方法:

public class PolicyEvaluator : IPolicyEvaluator
{private readonly IAuthorizationService _authorization;public PolicyEvaluator(IAuthorizationService authorization){_authorization = authorization;}public virtual async Task<PolicyAuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy, AuthenticateResult authenticationResult, HttpContext context, object resource){var result = await _authorization.AuthorizeAsync(context.User, resource, policy);if (result.Succeeded) return PolicyAuthorizationResult.Success();return (authenticationResult.Succeeded) ? PolicyAuthorizationResult.Forbid() : PolicyAuthorizationResult.Challenge();}
}

该方法会根据Requirements来完成授权,具体的实现是通过调用IAuthorizationService来实现的。

最终返回的是一个PolicyAuthorizationResult对象,并在授权失败时,根据认证结果来返回Forbid(未授权)Challenge(未登录)

public class PolicyAuthorizationResult
{private PolicyAuthorizationResult() { }public bool Challenged { get; private set; }public bool Forbidden { get; private set; }public bool Succeeded { get; private set; }
}

IAuthorizationService

然后就到了授权的核心对象AuthorizationService,也可以称为授权的外交官,我们也可以直接在应用代码中调用该对象来实现授权,它有如下定义:

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

AuthorizeAsync中还涉及到一个resource对象,用来实现面向资源的授权,放在《下一章》中再来介绍,而在本章与《前一章》的示例中,该值均为null

ASP.NET Core 中还为IAuthorizationService提供了几个扩展方法:

public static class AuthorizationServiceExtensions
{public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService service, ClaimsPrincipal user, string policyName) {}public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService service, ClaimsPrincipal user, AuthorizationPolicy policy) {}public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService service, ClaimsPrincipal user, object resource, IAuthorizationRequirement requirement) {}public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService service, ClaimsPrincipal user, object resource, AuthorizationPolicy policy) {}
}

其默认实现为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;public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName){        var policy = await _policyProvider.GetPolicyAsync(policyName);return await this.AuthorizeAsync(user, resource, policy);}public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements){var authContext = _contextFactory.CreateContext(requirements, user, resource);var handlers = await _handlers.GetHandlersAsync(authContext);foreach (var handler in handlers){await handler.HandleAsync(authContext);if (!_options.InvokeHandlersAfterFailure && authContext.HasFailed){break;}}return _evaluator.Evaluate(authContext);}
}

通过上面代码可以看出,在《上一章》中介绍的授权策略,在这里获取到它的Requirements,后续便不再需要了。而在AuthorizationService中是通过调用四大核心对象来完成授权,我们一一来看。

IAuthorizationPolicyProvider

由于在[Authorize]中,我们指定的是策略的名称,因此需要使用IAuthorizationPolicyProvider来根据名称获取到策略对象,默认实现为DefaultAuthorizationPolicyProvider

public class DefaultAuthorizationPolicyProvider : IAuthorizationPolicyProvider
{private readonly AuthorizationOptions _options;public Task<AuthorizationPolicy> GetDefaultPolicyAsync(){return Task.FromResult(_options.DefaultPolicy);}public virtual Task<AuthorizationPolicy> GetPolicyAsync(string policyName){return Task.FromResult(_options.GetPolicy(policyName));}
}

在上一章中介绍过,我们定义的策略都保存在《AuthorizationOptions》的字典中,因此在这里只是简单的将AuthorizationOptions中的同名方法异步化。

IAuthorizationHandlerContextFactory

授权上下文是我们接触较多的对象,当我们自定义授权Handler时就会用到它,它是使用简单工厂模式来创建的:

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

授权上下文中主要包含用户的Claims和授权策略的Requirements

public class AuthorizationHandlerContext
{private HashSet<IAuthorizationRequirement> _pendingRequirements;private bool _failCalled;private bool _succeedCalled;public AuthorizationHandlerContext(IEnumerable<IAuthorizationRequirement> requirements, ClaimsPrincipal user, object resource){Requirements = requirements; User = user; Resource = resource;_pendingRequirements = new HashSet<IAuthorizationRequirement>(requirements);}public virtual bool HasFailed { get { return _failCalled; } }public virtual bool HasSucceeded => !_failCalled && _succeedCalled && !_pendingRequirements.Any();public virtual void Fail(){_failCalled = true;}public virtual void Succeed(IAuthorizationRequirement requirement){_succeedCalled = true;_pendingRequirements.Remove(requirement);}
}

如上,_pendingRequirements中保存着所有待验证的Requirements,验证成功的Requirement则从中移除。

IAuthorizationHandlerProvider

兜兜转转,终于进入到了授权的最终验证逻辑中了,首先,使用IAuthorizationHandlerProvider来获取到所有的授权Handler

IAuthorizationHandlerProvider的默认实现为DefaultAuthorizationHandlerProvider:

public class DefaultAuthorizationHandlerProvider : IAuthorizationHandlerProvider
{private readonly IEnumerable<IAuthorizationHandler> _handlers;public DefaultAuthorizationHandlerProvider(IEnumerable<IAuthorizationHandler> handlers){_handlers = handlers;}public Task<IEnumerable<IAuthorizationHandler>> GetHandlersAsync(AuthorizationHandlerContext context)=> Task.FromResult(_handlers);
}

在《上一章》中,我们还介绍到,我们定义的Requirement,可以直接实现IAuthorizationHandler接口,也可以单独定义Handler,但是需要注册到DI系统中去。

在默认的AuthorizationHandlerProvider中,会从DI系统中获取到我们注册的所有Handler,最终调用其HandleAsync方法。

我们在实现IAuthorizationHandler接口时,通常是继承自AuthorizationHandler<TRequirement>来实现,它有如下定义:

public abstract class AuthorizationHandler<TRequirement> : IAuthorizationHandler where TRequirement : IAuthorizationRequirement
{public virtual async Task HandleAsync(AuthorizationHandlerContext context){foreach (var req in context.Requirements.OfType<TRequirement>()){await HandleRequirementAsync(context, req);}}protected abstract Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement);
}

如上,首先会在HandleAsync过滤出与Requirement对匹配的Handler,然后再调用其HandleRequirementAsync方法。

那我们定义的直接实现IAuthorizationHandler了接口的Requirement又是如何执行的呢?

AddAuthorization扩展方法中可以看到,默认还为IAuthorizationHandler注册了一个PassThroughAuthorizationHandler,定义如下:

public class PassThroughAuthorizationHandler : IAuthorizationHandler
{public async Task HandleAsync(AuthorizationHandlerContext context){foreach (var handler in context.Requirements.OfType<IAuthorizationHandler>()){await handler.HandleAsync(context);}}
}

它负责调用该策略中所有实现了IAuthorizationHandler接口的Requirement

IAuthorizationEvaluator

最后,通过调用IAuthorizationEvaluator接口,来完成最终的授权结果,默认实现为DefaultAuthorizationEvaluator:

public class DefaultAuthorizationEvaluator : IAuthorizationEvaluator
{public AuthorizationResult Evaluate(AuthorizationHandlerContext context)=> context.HasSucceeded? AuthorizationResult.Success(): AuthorizationResult.Failed(context.HasFailed? AuthorizationFailure.ExplicitFail(): AuthorizationFailure.Failed(context.PendingRequirements));
}

当我们在一个策略中指定多个Requirement时,只有全部验证通过时,授权上下文中的HasSucceeded才会为True,而HasFailed代表授权结果的显式失败。

这里根据授权上下文的验证结果来生成授权结果:

public class AuthorizationResult
{public bool Succeeded { get; private set; }public AuthorizationFailure Failure { get; private set; }public static AuthorizationResult Success() => new AuthorizationResult { Succeeded = true };public static AuthorizationResult Failed(AuthorizationFailure failure) => new AuthorizationResult { Failure = failure };public static AuthorizationResult Failed() => new AuthorizationResult { Failure = AuthorizationFailure.ExplicitFail() };
}public class AuthorizationFailure
{private AuthorizationFailure() { }public bool FailCalled { get; private set; }public IEnumerable<IAuthorizationRequirement> FailedRequirements { get; private set; }public static AuthorizationFailure ExplicitFail(){return new AuthorizationFailure { FailCalled = true, FailedRequirements = new IAuthorizationRequirement[0] };}public static AuthorizationFailure Failed(IEnumerable<IAuthorizationRequirement> failed)=> new AuthorizationFailure { FailedRequirements = failed };}

整个授权流程的结构大致如下:

总结

通过对 ASP.NET Core 授权系统执行流程的探索,可以看出授权是主要是通过调用IAuthorizationService来完成的,而授权策略的本质是提供 Requirement ,我们完全可以使用它们两个来完成各种灵活的授权方式,而不用局限于策略。在 ASP.NET Core 中,还提供了基于资源的授权,放在《下一章》中来介绍,并会简单演示一下在一个通用权限管理系统中如何来授权。

posted on 2019-01-08 08:55 NET未来之路 阅读(...) 评论(...) 编辑 收藏

转载于:https://www.cnblogs.com/lonelyxmas/p/10236987.html

ASP.NET Core 认证与授权[6]:授权策略是怎么执行的?相关推荐

  1. ASP.NET Core 认证与授权[2]:Cookie认证

    ASP.NET Core 认证与授权[2]:Cookie认证 原文:ASP.NET Core 认证与授权[2]:Cookie认证 由于HTTP协议是无状态的,但对于认证来说,必然要通过一种机制来保存用 ...

  2. ASP.NET Core 认证与授权[7]:动态授权

    基于资源的授权 有些场景下,授权需要依赖于要访问的资源,例如:每个资源通常会有一个创建者属性,我们只允许该资源的创建者才可以对其进行编辑,删除等操作,这就无法通过[Authorize]特性来指定授权了 ...

  3. ASP.NET Core 认证与授权[5]:初识授权

    经过前面几章的姗姗学步,我们了解了在 ASP.NET Core 中是如何认证的,终于来到了授权阶段.在认证阶段我们通过用户令牌获取到用户的Claims,而授权便是对这些的Claims的验证,如:是否拥 ...

  4. ASP.NET Core 认证与授权[3]:OAuth OpenID Connect认证

    在上一章中,我们了解到,Cookie认证是一种本地认证方式,通常认证与授权都在同一个服务中,也可以使用Cookie共享的方式分开部署,但局限性较大,而如今随着微服务的流行,更加偏向于将以前的单体应用拆 ...

  5. 使用JWT的ASP.NET CORE令牌身份验证和授权(无Cookie)——第1部分

    目录 介绍 JWT(JSON Web令牌) ASP.NET Core中的JWToken配置 用户模型类 创建令牌 第1步 第2步 第4步 令牌存储 中间件 自定义中间件app.Use() 中间件app ...

  6. ASP.NET Core 认证与授权[1]:初识认证

    在ASP.NET 4.X 中,我们最常用的是Forms认证,它既可以用于局域网环境,也可用于互联网环境,有着非常广泛的使用.但是它很难进行扩展,更无法与第三方认证集成,因此,在 ASP.NET Cor ...

  7. ASP.NET Core 认证与授权[4]:JwtBearer认证

    Bearer认证 HTTP提供了一套标准的身份验证框架:服务器可以用来针对客户端的请求发送质询(challenge),客户端根据质询提供身份验证凭证.质询与应答的工作流程如下:服务器端向客户端返回40 ...

  8. asp.net core 认证及简单集群

    众所周知,在Asp.net WebAPI中,认证是通过AuthenticationFilter过滤器实现的,我们通常的做法是自定义AuthenticationFilter,实现认证逻辑,认证通过,继续 ...

  9. Asp.net Core认证和授权:JWT认证和授权

    JWT验证一般用户移动端,因为它不像cookie验证那样,没有授权跳转到登陆页面 JWT是json web token的简称,在  jwt.io 网址可以看到 新建一个API项目,通过postman ...

最新文章

  1. ECSHOP删除云服务
  2. 9岁去读博!全球最年轻的大学毕业生火了:4岁上小学,8岁进入荷兰“中科大”,智商145...
  3. R语言应用实战系列(一)-基于R对QQ群聊天记录数据分析
  4. Nginx源代码安装
  5. jQuery 侧栏菜单点击body消失
  6. final修饰的是引用还是引用的对象
  7. C++ 面向对象的三大特性和五个原则?
  8. 《算法竞赛入门经典》第一章1.5
  9. 使用POI生成Excel文件,可以自动调整excel列宽
  10. webpack静态资源地址注入html,Webpack4+ 多入口程序构建
  11. 一个电脑安装多个系统的几个办法
  12. 百度开源的依存句法分析系统
  13. corosync配置与详解
  14. Linux网卡名改eth0方法
  15. C++的类型转换操作符
  16. Intel APIC Configuration
  17. 关于eclipse IDE安装Darkest Dark Theme主题插件
  18. PostgreSQL中的索引—9(BRIN)
  19. CAN 总线的常用拓扑
  20. 计算机 难题_寻找免费难题的最佳网站

热门文章

  1. XCTF-Reverse:re1
  2. 区块链——配置和使用geth
  3. ubuntu 终端内查找/搜索
  4. DM368开发 -- 硬件认识
  5. 【译】BINDER TRANSACTIONS IN THE BOWELS OF THE LINUX KERNEL
  6. 《微服务:从设计到部署》中文版
  7. 【译】Private Image Analysis with MPC Training CNNs on Sensitive Data
  8. Android SystemServiceManager分析
  9. Tensorflow Day19 Denoising Autoencoder
  10. jwt如何防止token被窃取_JWT令牌