跳到主要内容
版本:4.0-rc

WebAPI 鉴权与授权

一、鉴权与授权

TouchSocket.WebApi 支持多种鉴权和授权方式。

1.1 基于插件的鉴权

使用插件进行全局鉴权:

public class AuthPlugin : PluginBase, IWebApiPlugin
{
public async Task OnWebApiRequest(IWebApiClientBase client, WebApiEventArgs e)
{
var request = e.Request;
var token = request.Headers["Authorization"];

if (string.IsNullOrEmpty(token) || !IsValidToken(token))
{
// 未授权
e.Response.SetStatus(401, "Unauthorized");
await e.Response.AnswerAsync();
e.Handled = true; // 阻止继续处理
return;
}

await e.InvokeNext();
}

private bool IsValidToken(string token)
{
// 验证 token 逻辑
return token == "Bearer valid-token";
}
}

注册插件:

.ConfigurePlugins(a =>
{
a.Add<AuthPlugin>(); // 鉴权插件
a.UseWebApi();
a.UseDefaultHttpServicePlugin();
})

1.2 基于 RPC AOP 的鉴权

使用 RPC 过滤器实现方法级鉴权:

public class AuthorizeAttribute : RpcActionFilterAttribute
{
public override async Task<InvokeResult> ExecutingAsync(
ICallContext callContext,
object[] parameters,
InvokeResult invokeResult)
{
if (callContext is IWebApiCallContext webApiContext)
{
var request = webApiContext.HttpContext.Request;
var token = request.Headers["Authorization"];

if (string.IsNullOrEmpty(token) || !IsValidToken(token))
{
return new InvokeResult
{
Status = InvokeStatus.UnEnable,
Message = "Unauthorized"
};
}
}

return invokeResult;
}

private bool IsValidToken(string token)
{
return token == "Bearer valid-token";
}
}

使用:

public partial class ApiServer : SingletonRpcServer
{
// 公开接口,无需鉴权
[WebApi(Method = HttpMethodType.Get)]
public string PublicApi()
{
return "Public data";
}

// 需要鉴权的接口
[Authorize]
[WebApi(Method = HttpMethodType.Get)]
public string SecureApi()
{
return "Secure data";
}
}

1.3 基于角色的授权

实现基于角色的授权:

public class RoleAuthorizeAttribute : RpcActionFilterAttribute
{
private readonly string[] _roles;

public RoleAuthorizeAttribute(params string[] roles)
{
_roles = roles;
}

public override async Task<InvokeResult> ExecutingAsync(
ICallContext callContext,
object[] parameters,
InvokeResult invokeResult)
{
if (callContext is IWebApiCallContext webApiContext)
{
var request = webApiContext.HttpContext.Request;
var token = request.Headers["Authorization"];

if (!TryGetUserRole(token, out var userRole))
{
return new InvokeResult
{
Status = InvokeStatus.UnEnable,
Message = "Unauthorized"
};
}

if (!_roles.Contains(userRole))
{
return new InvokeResult
{
Status = InvokeStatus.UnEnable,
Message = "Forbidden: Insufficient permissions"
};
}
}

return invokeResult;
}

private bool TryGetUserRole(string token, out string role)
{
role = null;
if (string.IsNullOrEmpty(token)) return false;

// 从 token 中解析角色
if (token == "Bearer admin-token")
{
role = "Admin";
return true;
}
else if (token == "Bearer user-token")
{
role = "User";
return true;
}

return false;
}
}

使用:

public partial class ApiServer : SingletonRpcServer
{
// 仅管理员可访问
[RoleAuthorize("Admin")]
[WebApi(Method = HttpMethodType.Delete)]
public string DeleteUser(int id)
{
return $"Deleted user {id}";
}

// 管理员和普通用户都可访问
[RoleAuthorize("Admin", "User")]
[WebApi(Method = HttpMethodType.Get)]
public string GetUser(int id)
{
return $"User {id} info";
}
}

二、完整示例

// 服务器端
var service = new HttpService();
await service.SetupAsync(new TouchSocketConfig()
.SetListenIPHosts(7789)
.ConfigurePlugins(a =>
{
a.Add<AuthPlugin>(); // 全局鉴权
a.UseWebApi();
a.UseDefaultHttpServicePlugin();
}));

await service.StartAsync();

// API 服务
public partial class SecureApiServer : SingletonRpcServer
{
[WebApi(Method = HttpMethodType.Get)]
public string PublicInfo()
{
return "This is public information";
}

[Authorize]
[WebApi(Method = HttpMethodType.Get)]
public string UserInfo(IWebApiCallContext context)
{
var token = context.HttpContext.Request.Headers["Authorization"];
return $"User info (token: {token})";
}

[RoleAuthorize("Admin")]
[WebApi(Method = HttpMethodType.Post)]
public string AdminAction()
{
return "Admin action executed";
}
}

// 客户端调用
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer valid-token");

var result = await client.GetStringAsync("http://localhost:7789/secureapiserver/userinfo");
Console.WriteLine(result);