一站式WebAPI与认证授权服务
保护WEBAPI有哪些方法?
微软官方文档推荐了好几个:
- Azure Active Directory
- Azure Active Directory B2C (Azure AD B2C)]
- IdentityServer4
前面两个看着就觉得搞不太明白,第三个倒是非常常见,相关的文章也很多。不过这个东西是独立部署的,太重了,如果我就想写一个简单一点的API,把认证给包括的,是不是有好办法?
准备
假设你的WEBAPI使用JWT TOKEN来保存你的认证信息,并且通过JWT TOKEN进行保护。那么我们可以设计一个集成有认证授权的WEBAPI服务,一站式解决问题,代码简单且方便自行修改。
要点:
- 使用类似[Authorize]的授权,需要基于token中
role这个Claim来实现。 - 密码的保存需要进行特别设计。
- 用户对象返回需要避免password和passwordhash的传递。
项目特点:
- RESTful设计(正常来说api的资源应该是复数userinfos,但是info应该就是不可数的,不纠结了。)
- 集成Swagger
- ASP.NET Core 3.1
- nullable设计
- EF Core
- 用户权限控制
- 密码安全存储
- Token实现与API集成
- 简单易于理解
用户实体类
所有认证之类的工作都在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。
展望
- token刷新与吊销。
- 注册与手机/Email验证。
参考资料
一站式WebAPI与认证授权服务的更多相关文章
- Spring Cloud 微服务中搭建 OAuth2.0 认证授权服务
在使用 Spring Cloud 体系来构建微服务的过程中,用户请求是通过网关(ZUUL 或 Spring APIGateway)以 HTTP 协议来传输信息,API 网关将自己注册为 Eureka ...
- Spring Cloud Zuul 网关使用与 OAuth2.0 认证授权服务
API 网关的出现的原因是微服务架构的出现,不同的微服务一般会有不同的服务地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求,如果让客户端直接与各个微服务通信,会有以下的问题: 客户端会 ...
- 基于OWIN ASP.NET WebAPI 使用OAUTH2授权服务的几点优化
前面在ASP.NET WEBAPI中集成了Client Credentials Grant与Resource Owner Password Credentials Grant两种OAUTH2模式,今天 ...
- 基于OWIN WebAPI 使用OAuth授权服务【客户端验证授权(Resource Owner Password Credentials Grant)】
适用范围 前面介绍了Client Credentials Grant ,只适合客户端的模式来使用,不涉及用户相关.而Resource Owner Password Credentials Grant模 ...
- Owin中间件搭建OAuth2.0认证授权服务体会
继两篇转载的Owin搭建OAuth 2.0的文章,使用Owin中间件搭建OAuth2.0认证授权服务器和理解OAuth 2.0之后,我想把最近整理的资料做一下总结. 前两篇主要是介绍概念和一个基本的D ...
- (转)基于OWIN WebAPI 使用OAuth授权服务【客户端模式(Client Credentials Grant)】
适应范围 采用Client Credentials方式,即应用公钥.密钥方式获取Access Token,适用于任何类型应用,但通过它所获取的Access Token只能用于访问与用户无关的Open ...
- 基于OWIN WebAPI 使用OAuth授权服务【客户端模式(Client Credentials Grant)】
适应范围 采用Client Credentials方式,即应用公钥.密钥方式获取Access Token,适用于任何类型应用,但通过它所获取的Access Token只能用于访问与用户无关的Open ...
- 微服务下前后端分离的统一认证授权服务,基于Spring Security OAuth2 + Spring Cloud Gateway实现单点登录
1. 整体架构 在这种结构中,网关就是一个资源服务器,它负责统一授权(鉴权).路由转发.保护下游微服务. 后端微服务应用完全不用考虑权限问题,也不需要引入spring security依赖,就正常的 ...
- 基于OWIN WebAPI 使用OAUTH2授权服务【授权码模式(Authorization Code)】
之前已经简单实现了OAUTH2的授权码模式(Authorization Code),但是基于JAVA的,今天花了点时间调试了OWIN的实现,基本就把基于OWIN的OAUHT2的四种模式实现完了.官方推 ...
随机推荐
- 后端开发使用pycharm的技巧
后端开发使用pycharm的技巧 目录 后端开发使用pycharm的技巧 1.使用说明 2.database 3.HTTP Client 1.使用说明 首先说明,本文所使用的功能为pycharm专业版 ...
- 题解 P1278 【单词游戏】
前言 首先,看到这道题目,我首先想到的是暴搜,通过\(vector\)来搞,代码也是很短的. 这里用了一个类似于分治的思想 把一个大问题转化为小问题 先枚举第一个单词,之后把能拼接在它后面的单词都一个 ...
- Linux命令ip addr详解
熟悉Linux操作系统的同学对于ip addr命令应该不陌生,知道它是用来查看本地IP地址的,除了IP地址,其它额外的信息有必要了解一下. root@test:~# ip addr1: lo: < ...
- OpenCV-Python 绘图功能 | 七
目标 学习使用OpenCV绘制不同的几何形状 您将学习以下功能:cv.line(),cv.circle(),cv.rectangle(),cv.ellipse(),cv.putText()等. 代码 ...
- 多GPU使用详解
目录: 介绍 记录设备状态 手动分配状态 允许GPU内存增长 在多GPU系统是使用单个GPU 使用多个 GPU 一.介绍 在一个典型的系统中,有多个计算设备.在 TensorFlow 中支持的设备类型 ...
- class字节码的结构
class字节码的结构 使用javap -verbose 命令分析一个.class字节码文件时(以下简称字节码文件),将会分析该字节码文件的魔数,版本号,常量池,类信息,类的构造方法,类中的方法信息, ...
- POJ 3461 Oulipo KMP算法(模板)
题意: 给两组字符串a和b,求a在b中出现的次数 关于KMP: 马拉车算法是处理回文串,而KMP是处理前后缀的相同字符串的最长长度. a | a | b | a | a | f | a | a 数组 ...
- Sublimeの虚拟环境(Venv)设置
这里主要介绍,在使用 Python 虚拟环境(Venv)时,SublimeText 该怎么设置 为什么使用虚拟环境(Venv) 因为,我有洁癖! 我就是喜欢看到,pip list 命令下什么 Pack ...
- Python python 数据类型--集
# set 集 '''Python还包括集合的数据类型.集合是无序集合,没有重复元素. 基本用途包括成员资格测试和消除重复条目. 集合对象还支持数学运算,如并集,交集,差异和对称差异. ''' nam ...
- python学习要点(一)
我的个人博客排版更舒服: https://www.luozhiyun.com/archives/264 列表和元组 列表是动态的,长度大小不固定,可以随意地增加.删减或者改变元素(mutable). ...