保护WEBAPI有哪些方法?

微软官方文档推荐了好几个:

  • Azure Active Directory
  • Azure Active Directory B2C (Azure AD B2C)]
  • IdentityServer4

前面两个看着就觉得搞不太明白,第三个倒是非常常见,相关的文章也很多。不过这个东西是独立部署的,太重了,如果我就想写一个简单一点的API,把认证给包括的,是不是有好办法?

准备

假设你的WEBAPI使用JWT TOKEN来保存你的认证信息,并且通过JWT TOKEN进行保护。那么我们可以设计一个集成有认证授权的WEBAPI服务,一站式解决问题,代码简单且方便自行修改。

要点:

  1. 使用类似[Authorize]的授权,需要基于token中role这个Claim来实现。
  2. 密码的保存需要进行特别设计。
  3. 用户对象返回需要避免password和passwordhash的传递。

项目特点:

  1. RESTful设计(正常来说api的资源应该是复数userinfos,但是info应该就是不可数的,不纠结了。)
  2. 集成Swagger
  3. ASP.NET Core 3.1
  4. nullable设计
  5. EF Core
  6. 用户权限控制
  7. 密码安全存储
  8. Token实现与API集成
  9. 简单易于理解

用户实体类

所有认证之类的工作都在API这边实现,因此我们需要一个userinfo类来进行处理。

[DataContract]
[Table("userinfo")]
public class UserInfo
{
[DataMember]
[Key]
public string UserId { get; set; } = default!;
//传输的过程中会用到密码,但是这个密码不应该被存入数据库中。
[NotMapped]
[DataMember]
public string? Password { get; set; }
//传输的过程中不会用到密码哈希值,但是哈希值需要存入数据库中。
[IgnoreDataMember]
public string? PasswordHash { get; set; }
[DataMember]
public string? Role { get; set; } public static string GetRole(string? role)
{
if (string.IsNullOrWhiteSpace(role)) return "User";
return role.ToLower() switch
{
"administrator" => "Administrator",
"supervisor" => "Supervisor",
_ => "User"
};
}
}
  • 使用json进行序列化,[DataContract]不是必须的,我一般是不喜欢写这个东西,不写的话,那么所有的public属性和字段都会被序列化;如果标记了[DataContract],那么只有标记有[DataMember]的会被序列化,使用[IgnoreDataMember]可以阻止序列化。
  • 使用了EF Core用来持久化,标记[NotMapped]指示属性不被映射到数据库中,一般来说,数据库不应该直接保存密码。

令牌发放

具体实现TokenController如下。

[AllowAnonymous]
[HttpPost]
public ActionResult Post(UserInfo login)
{
ActionResult response = BadRequest("登录失败,请检查用户名和密码");
var user = AuthenticateUser(login); if (user != null)
{
var tokenString = GenerateJSONWebToken(user);
response = Ok(new { access_token = tokenString, role = user.Role });
} return response;
} private string GenerateJSONWebToken(UserInfo userInfo)
{
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); var claims = new[] {
new Claim(JwtRegisteredClaimNames.Sub, userInfo.UserName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(ClaimTypes.Role, userInfo.Role),
}; var token = new JwtSecurityToken(null,
null,
claims,
expires: DateTime.Now.AddMinutes(120),
signingCredentials: credentials); return new JwtSecurityTokenHandler().WriteToken(token);
} private UserInfo? AuthenticateUser(UserInfo login)
{
UserInfo? user = null;
if (string.IsNullOrWhiteSpace(login.Password)) return user; using (var context = new ManageDataContext())
{
var result = context.UserInfos.Where(w => w.UserName.ToLower() == login.UserName.ToLower()).FirstOrDefault();
if (result != null)
if (PasswordStorage.VerifyPassword(login.Password, result.PasswordHash!)) user = result;
} return user;
}

上面的类标志有AllowAnonymous,表示这个类是可以匿名访问的,用户先请求post请求token,然后再携带token访问其他API。

上面用到一个PasswordStorage的库,这个库使用了加盐哈希的形式存储了密码,实践上比较可靠。值得一提的是它的VerifyPassword()函数,使用的比较算法很巧妙,我贴在了文末,推荐大家阅读。

受保护的API

被保护的用户管理API如下,只贴了一小部分:

[EnableCors("AllowAll")]
[Route("api/[controller]")]
//只有角色为Admin可以访问
[Authorize(Roles = "Admin")]
//如果需要增加种子数据,可以注释上面这行,取消注释下面这一行
//[AllowAnonymous]
[ApiController]
public class UserInfoController : ControllerBase
{
private readonly ManageDataContext _context;
public UserInfoController(ManageDataContext context)
{
_context = context;
} /// <summary>
/// 有参GET请求
/// </summary>
/// <param name="id">用户编号id</param>
/// <returns></returns>
[HttpGet("{id}")]
[ProducesResponseType(typeof(UserInfo), Status200OK)]
[ProducesResponseType(typeof(string), Status404NotFound)]
public async Task<ActionResult> Get(string id)
{
var res = await _context.UserInfos.FindAsync(id);
if (res != null) return Ok(res);
else return NotFound("Cannot find key.");
}
}

启动配置

Startup.cs注意一下顺序的问题。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//实际测试,这个UseCors如果在UseAuthentication和UseAuthorization的后面,可能会导致vue.js访问问题。
app.UseCors("AllowAll"); app.UseAuthentication();
app.UseAuthorization();
} public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
//使用AddNewtonsoftJson为了避免json的严格检查。
services.AddControllers().AddNewtonsoftJson();
services.AddDbContext<ManageDataContext>();
//后面还有不贴了
}

在ConfigureServices里面,调用了AddNewtonsoftJson()。之所以没有使用到默认的System.Text.Json,是因为它对客户端上传的信息要求太严格,如果是integer类型的值,上传使用了string就不能正确识别对象,而Newtonsoft.Json没有这个问题。

也可以修改System.Text.Json的默认行为,但是总是没有那么方便了。

调用方法

请求令牌

POST请求,api/token,设置header:Content-Type为application/json。body内容如下:

{
"userName": "admin",
"password": "123"
}

调用即可返回access_token与role。

调用被保护的API

需要设置header:

  • Authorization值为Bearer [获取到的token]
  • Content-Type为application/json

    然后就可以自由调用自己有权访问的API了。

总结

零零散散写了这么些,直接贴上代码,项目是基于asp.net core 3.1与swagger的,本项目也可以作为一些小型项目的模板。

需要新建用户的话,可以注释掉[Authorize]或者我已经准备了一个用户admin,密码是123。

如果需要在windows上进行服务部署,可以参考我之前写的TopShelf的文章

Github项目地址,欢迎Fork或者Star。

展望

  1. token刷新与吊销。
  2. 注册与手机/Email验证。

参考资料

  1. 密码哈希指南
  2. 加盐哈希指南
  3. password-hashing

一站式WebAPI与认证授权服务的更多相关文章

  1. Spring Cloud 微服务中搭建 OAuth2.0 认证授权服务

    在使用 Spring Cloud 体系来构建微服务的过程中,用户请求是通过网关(ZUUL 或 Spring APIGateway)以 HTTP 协议来传输信息,API 网关将自己注册为 Eureka ...

  2. Spring Cloud Zuul 网关使用与 OAuth2.0 认证授权服务

    API 网关的出现的原因是微服务架构的出现,不同的微服务一般会有不同的服务地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求,如果让客户端直接与各个微服务通信,会有以下的问题: 客户端会 ...

  3. 基于OWIN ASP.NET WebAPI 使用OAUTH2授权服务的几点优化

    前面在ASP.NET WEBAPI中集成了Client Credentials Grant与Resource Owner Password Credentials Grant两种OAUTH2模式,今天 ...

  4. 基于OWIN WebAPI 使用OAuth授权服务【客户端验证授权(Resource Owner Password Credentials Grant)】

    适用范围 前面介绍了Client Credentials Grant ,只适合客户端的模式来使用,不涉及用户相关.而Resource Owner Password Credentials Grant模 ...

  5. Owin中间件搭建OAuth2.0认证授权服务体会

    继两篇转载的Owin搭建OAuth 2.0的文章,使用Owin中间件搭建OAuth2.0认证授权服务器和理解OAuth 2.0之后,我想把最近整理的资料做一下总结. 前两篇主要是介绍概念和一个基本的D ...

  6. (转)基于OWIN WebAPI 使用OAuth授权服务【客户端模式(Client Credentials Grant)】

    适应范围 采用Client Credentials方式,即应用公钥.密钥方式获取Access Token,适用于任何类型应用,但通过它所获取的Access Token只能用于访问与用户无关的Open ...

  7. 基于OWIN WebAPI 使用OAuth授权服务【客户端模式(Client Credentials Grant)】

    适应范围 采用Client Credentials方式,即应用公钥.密钥方式获取Access Token,适用于任何类型应用,但通过它所获取的Access Token只能用于访问与用户无关的Open ...

  8. 微服务下前后端分离的统一认证授权服务,基于Spring Security OAuth2 + Spring Cloud Gateway实现单点登录

    1.  整体架构 在这种结构中,网关就是一个资源服务器,它负责统一授权(鉴权).路由转发.保护下游微服务. 后端微服务应用完全不用考虑权限问题,也不需要引入spring security依赖,就正常的 ...

  9. 基于OWIN WebAPI 使用OAUTH2授权服务【授权码模式(Authorization Code)】

    之前已经简单实现了OAUTH2的授权码模式(Authorization Code),但是基于JAVA的,今天花了点时间调试了OWIN的实现,基本就把基于OWIN的OAUHT2的四种模式实现完了.官方推 ...

随机推荐

  1. matplotlib 的几种柱状图

    1.x 表示数量,y 表示名字 import matplotlib.pyplot as plt dic = {'a': 22, 'b': 10, 'c': 6, 'd': 4, 'e': 2, 'f' ...

  2. Django 配置使用日志

    一. Django中使用日志 Django中使用日志其实非常简单,只需要在项目使用的配置文件中(如果没有自定义,那么就是settings.py中)加以下设置即可,同时可以根据自己的需求进行修改: # ...

  3. 我是如何用IDEA调试BUG的?

    最近小明的bug有点多,忙的连王者荣耀都顾不上玩了,导致现在不得不抽点时间研究一下作为当前大多Java程序员开发工具的IDEA DEBUG功能,以提高效率. 一.条件断点 场景:我们在遍历某个集合,期 ...

  4. OpenCV-Python 图像平滑 | 十六

    目标 学会: 使用各种低通滤镜模糊图像 将定制的滤镜应用于图像(2D卷积) 2D卷积(图像过滤) 与一维信号一样,还可以使用各种低通滤波器(LPF),高通滤波器(HPF)等对图像进行滤波.LPF有助于 ...

  5. WeChat-SmallProgram:组件的业务 slot 的使用

    1.调用组件向自定义组件插入内容,使用  slot 在自定义模板中有一对 <view><slot></slot></view> 这里是干什么用的呢? 在 ...

  6. Sprinboot 整合 RabbitMQ,RabbitMQ 消息重试机制

    当消费者消费消息的时候,出现错误,RabbitMQ 本身会有

  7. Api接口幂等设计

    1,Api接口幂等设计,也就是要保证数据的唯一性,不允许有重复. 例如:rpc 远程调用,因为网络延迟,出现了调用了2次的情况. 表单连续点击,出现了重复提交. 接口暴露之后,会被模拟请求工具(Jem ...

  8. python—json

    一.json数据类型:是字符串 # json通用数据类型,所有语言都可以用 # {k-v}形式存在,里面只能用双引号"" # 定义json字符串,要用'''{}'''格式 #htt ...

  9. MySQL出现的问题

    错误展示 今天还是老样子照常启动MySQL WorkBench的时候出了错误,无法连接服务器 CMD登陆也不行 发现mysql的服务都没启动,于是点击启动,却又报这个错 cmd查看MySQL的日志,想 ...

  10. git本地库中配置多个sshkey

    一般来说正常情况下本地库中配置一个sshkey就可以适应开发了,但是生活中总会有那么一点点的小惊喜.由于公司业务的发展,现在需要本地库中再配置一个sshkey,下面记录下配置的全过程: 生成并添加第一 ...