保护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. 后端开发使用pycharm的技巧

    后端开发使用pycharm的技巧 目录 后端开发使用pycharm的技巧 1.使用说明 2.database 3.HTTP Client 1.使用说明 首先说明,本文所使用的功能为pycharm专业版 ...

  2. 题解 P1278 【单词游戏】

    前言 首先,看到这道题目,我首先想到的是暴搜,通过\(vector\)来搞,代码也是很短的. 这里用了一个类似于分治的思想 把一个大问题转化为小问题 先枚举第一个单词,之后把能拼接在它后面的单词都一个 ...

  3. Linux命令ip addr详解

    熟悉Linux操作系统的同学对于ip addr命令应该不陌生,知道它是用来查看本地IP地址的,除了IP地址,其它额外的信息有必要了解一下. root@test:~# ip addr1: lo: < ...

  4. OpenCV-Python 绘图功能 | 七

    目标 学习使用OpenCV绘制不同的几何形状 您将学习以下功能:cv.line(),cv.circle(),cv.rectangle(),cv.ellipse(),cv.putText()等. 代码 ...

  5. 多GPU使用详解

    目录: 介绍 记录设备状态 手动分配状态 允许GPU内存增长 在多GPU系统是使用单个GPU 使用多个 GPU 一.介绍 在一个典型的系统中,有多个计算设备.在 TensorFlow 中支持的设备类型 ...

  6. class字节码的结构

    class字节码的结构 使用javap -verbose 命令分析一个.class字节码文件时(以下简称字节码文件),将会分析该字节码文件的魔数,版本号,常量池,类信息,类的构造方法,类中的方法信息, ...

  7. POJ 3461 Oulipo KMP算法(模板)

    题意: 给两组字符串a和b,求a在b中出现的次数 关于KMP: 马拉车算法是处理回文串,而KMP是处理前后缀的相同字符串的最长长度. a | a | b | a | a | f | a | a 数组 ...

  8. Sublimeの虚拟环境(Venv)设置

    这里主要介绍,在使用 Python 虚拟环境(Venv)时,SublimeText 该怎么设置 为什么使用虚拟环境(Venv) 因为,我有洁癖! 我就是喜欢看到,pip list 命令下什么 Pack ...

  9. Python python 数据类型--集

    # set 集 '''Python还包括集合的数据类型.集合是无序集合,没有重复元素. 基本用途包括成员资格测试和消除重复条目. 集合对象还支持数学运算,如并集,交集,差异和对称差异. ''' nam ...

  10. python学习要点(一)

    我的个人博客排版更舒服: https://www.luozhiyun.com/archives/264 列表和元组 列表是动态的,长度大小不固定,可以随意地增加.删减或者改变元素(mutable). ...