WEBAPI加入身份认证

一、为什么 WebAPI 使用 Token 认证

传统的 ASP.NET MVC / WebForm 项目通常使用 Session​ 来维护用户的登录状态。但 WebAPI 作为前后端分离架构下的接口层,具有以下特点:

对比项SessionToken
存储位置服务器端(内存/缓存)客户端(LocalStorage / Cookie / Header)
跨域支持差(依赖 Cookie 同源)好(可跨域携带)
服务器状态有状态(需维护 Session)无状态(每次请求自包含凭证)
扩展性多服务器需共享 Session天然支持分布式/负载均衡
移动端适配不便友好(App / 小程序 / SPA)

Token 相当于”入城令牌”,客户端首次登录后由服务器签发,之后每次请求 API 都需携带此令牌,服务器校验通过才允许访问。

Token 的传递方式有多种:

  • URL 参数:如 ?token=xxx(微信公众号开发常用)
  • HTTP Header:如 Authorization: Bearer xxx 或自定义 Header(推荐)

本教程采用 HTTP Header 自定义 Token​ 的方式,并使用 Postman​ 模拟请求。


二、核心思路

  1. 客户端登录成功后,服务器生成 Token 并返回
  2. 客户端将 Token 保存在本地(LocalStorage / Cookie)
  3. 后续每次请求 API 时,将 Token 放入 HTTP Header 中
  4. 服务器端通过 自定义 AuthorizeAttribute​ 拦截请求,从 Header 中提取 Token 并校验
  5. 校验通过 → 允许访问;校验失败 → 返回 401 Unauthorized
  6. 标记 [AllowAnonymous] 的接口跳过验证(如登录接口本身)

三、自定义 TokenAuthAttribute

3.1 完整代码

在项目中创建 App_Start 文件夹(如不存在),添加 TokenAuthAttribute.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Mvc;

namespace Demo.App_Start
{
    /// <summary>
    /// 自定义 Token 认证特性
    /// </summary>
    public class TokenAuthAttribute : AuthorizeAttribute
    {
        /// <summary>
        /// 重写基类的验证方式,加入自定义 Token 验证逻辑
        /// </summary>
        public override void OnAuthorization(HttpActionContext actionContext)
        {
            // 从 HTTP Header 中获取 Token
            // 通过 MS_HttpContext 访问底层 HttpContext
            var content = actionContext.Request.Properties["MS_HttpContext"] as HttpContextBase;
            var token = content.Request.Headers["Token"];

            if (!string.IsNullOrEmpty(token))
            {
                // 校验 Token 是否有效
                if (ValidateTicket(token))
                {
                    // 验证通过,调用基类方法
                    base.IsAuthorized(actionContext);
                }
                else
                {
                    // Token 无效,返回 401
                    HandleUnauthorizedRequest(actionContext);
                }
            }
            else
            {
                // Header 中没有 Token,检查是否允许匿名访问
                var attributes = actionContext.ActionDescriptor
                    .GetCustomAttributes<AllowAnonymousAttribute>()
                    .OfType<AllowAnonymousAttribute>();

                bool isAnonymous = attributes.Any(a => a is AllowAnonymousAttribute);

                if (isAnonymous)
                {
                    // 允许匿名访问,跳过验证
                    base.OnAuthorization(actionContext);
                }
                else
                {
                    // 不允许匿名且未提供 Token,返回 401
                    HandleUnauthorizedRequest(actionContext);
                }
            }
        }

        /// <summary>
        /// 校验 Token(实际项目中应查询数据库/缓存/Redis)
        /// </summary>
        /// <param name="encryptToken">客户端传入的 Token</param>
        /// <returns>是否有效</returns>
        private bool ValidateTicket(string encryptToken)
        {
            bool flag = false;
            try
            {
                // ============================================
                // 实际项目中,这里应:
                // 1. 从数据库/Redis/缓存中查询 Token 是否存在
                // 2. 校验 Token 是否过期
                // 3. 校验 Token 对应的用户状态是否正常
                // ============================================

                // 示例:简单比对(实际项目中不要这样写)
                if ("ddddd" == encryptToken)
                {
                    flag = true;
                }

                // 示例:数据库校验伪代码
                // var dbToken = db.Tokens.FirstOrDefault(t => t.Value == encryptToken);
                // if (dbToken != null && dbToken.ExpireTime > DateTime.Now)
                // {
                //     flag = true;
                // }
            }
            catch (Exception ex)
            {
                // 记录日志
                // Log.Error(ex);
            }
            return flag;
        }
    }
}Code language: HTML, XML (xml)

3.2 代码解析

步骤说明
actionContext.Request.Properties["MS_HttpContext"]获取底层 HttpContextBase,用于访问 Request Header
content.Request.Headers["Token"]从 Header 中读取名为 Token 的自定义字段
ValidateTicket()校验 Token 有效性(查库、比对、过期检查)
AllowAnonymousAttribute检查接口是否标记了 [AllowAnonymous],允许匿名访问
HandleUnauthorizedRequest()返回 HTTP 401 未授权状态码

四、在 Controller 中使用

4.1 全局注册(推荐)

App_Start/WebApiConfig.cs 中全局注册,所有 API 默认需要 Token:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // 全局 Token 认证
        config.Filters.Add(new TokenAuthAttribute());

        // Web API 路由
        config.MapHttpAttributeRoutes();
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}Code language: PHP (php)

4.2 控制器/方法级别使用

如果不全局注册,可以在 Controller 或 Action 上单独标记:

using Demo.App_Start;
using System.Web.Http;
using System.Web.Mvc;

public class ValuesController : ApiController
{
    /// <summary>
    /// 需要 Token 认证的接口
    /// </summary>
    [TokenAuth]
    public string Get(int id)
    {
        return "value";
    }

    /// <summary>
    /// 允许匿名访问的接口(如登录)
    /// </summary>
    [AllowAnonymous]
    public string Login(string username, string password)
    {
        // 校验用户名密码,成功后返回 Token
        if (username == "admin" && password == "123456")
        {
            return "ddddd"; // 返回 Token
        }
        return "登录失败";
    }
}Code language: HTML, XML (xml)

五、使用 Postman 测试

5.1 不带 Token 请求(预期 401)

步骤操作
请求方式GET
URLhttp://localhost:端口/api/values/1
Header不添加 Token
结果返回 401 Unauthorized

5.2 带 Token 请求(预期 200)

步骤操作
请求方式GET
URLhttp://localhost:端口/api/values/1
Header添加 Token: ddddd
结果返回 "value"

5.3 登录获取 Token

步骤操作
请求方式GET
URLhttp://localhost:端口/api/values/login?username=admin&password=123456
Header
结果返回 "ddddd"(Token 值)

六、完整可运行示例(含登录签发 Token)

using Demo.App_Start;
using System;
using System.Web.Http;
using System.Web.Mvc;

public class AuthController : ApiController
{
    /// <summary>
    /// 登录接口(匿名访问)
    /// </summary>
    [AllowAnonymous]
    [System.Web.Http.HttpPost]
    public object Login([FromBody] LoginModel model)
    {
        if (model.UserName == "admin" && model.Password == "123456")
        {
            // 生成 Token(实际项目中应使用 GUID / JWT / 加密字符串)
            string token = Guid.NewGuid().ToString("N");

            // 将 Token 存入数据库或缓存(此处省略)
            // CacheHelper.Set(token, model.UserName, expireMinutes: 120);

            return new
            {
                Success = true,
                Token = token,
                Message = "登录成功"
            };
        }

        return new
        {
            Success = false,
            Message = "用户名或密码错误"
        };
    }

    /// <summary>
    /// 获取用户信息(需 Token)
    /// </summary>
    [TokenAuth]
    [System.Web.Http.HttpGet]
    public object GetUserInfo()
    {
        return new
        {
            UserName = "admin",
            Role = "Administrator",
            LoginTime = DateTime.Now
        };
    }
}

public class LoginModel
{
    public string UserName { get; set; }
    public string Password { get; set; }
}Code language: HTML, XML (xml)

七、使用要点小结

功能实现方式
Token 传递HTTP Header 自定义字段 Token
自定义验证继承 AuthorizeAttribute,重写 OnAuthorization
获取 HeaderactionContext.Request.Properties["MS_HttpContext"]HttpContextBase
匿名跳过[AllowAnonymous] 特性
返回 401HandleUnauthorizedRequest(actionContext)
全局注册WebApiConfig.csconfig.Filters.Add(new TokenAuthAttribute())
测试工具Postman 在 Header 中添加 Token: xxx

八、实际项目改进建议

项目建议
Token 生成使用 Guid.NewGuid() 或 JWT(JSON Web Token)
Token 存储数据库 / Redis / MemoryCache,记录用户ID、过期时间
Token 过期设置过期时间,过期后要求重新登录
Token 刷新提供 RefreshToken 机制
Header 规范使用标准 Authorization: Bearer xxx 替代自定义 Token
HTTPS生产环境强制 HTTPS,防止 Token 被窃听
加密Token 可加密签名,防止篡改

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注