ASP.NET Web API编程——接口安全与角色控制
1 API接口验证与授权
JWT
JWT定义,它包含三部分:header,payload,signature;每一部分都是使用Base64编码的JSON字符串。之间以句号分隔。signature是”header.payload”经加密后的字符串。
采用JWT实现验证与授权检验机制,JWT格式为:
header :
{
"typ": "JWT",
"alg": "HS256"
}
payload:appid为GUID,timestamp为unix时间戳
{
"appid": GUID,
"timestamp": Unix time
}
Signature:使用HS256(HMAC SHA-256,SHA Secure Hash Algorithm,安全散列算法)对header和payload以‘.’连接的字符串进行签名。
对JWT加密:采用RSA加密算法对其进行加密。
密钥发放
发放给客户端的参数:appId、appSecret、publicKey、privateKeyId。其中publicKey为RSA公钥,privateKeyId为服务端私钥Id。服务端或根据privateKeyId在缓存(本地或Redis等)中查找RSA私钥。
合成accessToken:header、payload与上述相同,签名密钥为appSecret。合成以后,使用publicKey对其进行加密。
合成headerJson:由accessToken和privateKeyId构成的Json字符串,然后将字符串用Base64编码方式编码。
验证流程
客户端将上述headerB64放入请求头,向服务端发起请求,服务端从请求头中拿到headerJson并解码headerJson,进而从中得到accessToken和privateKeyId,服务端根据privateKeyId找到privateKey,使用privateKey对accessToken解密,根据payload中的timestamp验证过期,若未过期,那么进行签名校验,验证通过授权用户端。
示例代码(关键性代码)
public abstract class BasicAuthenticationAttribute : Attribute, IAuthenticationFilter
{ public async Task AuthenticateAsync(HttpAuthenticationContext context, System.Threading.CancellationToken cancellationToken)
{
await Task.Factory.StartNew(()=>
{
//解析头信息,获得appid和timestamp
var header = ...
//如果未获得上述信息
if (header == null)
{
context.ErrorResult = new AuthenticationFailureResult(requestHeaderAnalysis.ExecStatus, context.Request);
return;
} //从缓存中获得RSA私钥
string privateKey= ...
if (String.IsNullOrWhiteSpace(privateKey))
{
context.ErrorResult = new AuthenticationFailureResult(StatusCodeManager.GetStatusInfo("B07", "a001"), context.Request);
return;
} //使用RSA私钥对AccessToken解密
string accessToken = Decrypt(requestHeaderInfo.AccessToken, privateKey);
if (String.IsNullOrWhiteSpace(accessToken))
{//验证凭据是空,设置错误信息
context.ErrorResult = new AuthenticationFailureResult(StatusCodeManager.GetStatusInfo("B05", "a001"), context.Request);
return;
} //从AccessToken的payload中获得appKey和timestamp(时间戳)
var payloadDict = JsonWebToken.DecodeToObject(accessToken);
string appKey = Convert.ToString(payloadDict["appKey"]);
string timestamp = Convert.ToString(payloadDict["timestamp"]); //在服务端数据库中,根据appKey查找appSecret
ApiAccount apiAccount = GetApiAccount(appKey);
if (apiAccount==null||string.IsNullOrWhiteSpace(apiAccount.AppSecret))
{
context.ErrorResult = new AuthenticationFailureResult(StatusCodeManager.GetStatusInfo("B05", "a001"), context.Request);
return;
} //验证是否超时,签名是否被篡改
try
{
//允许的时间段(小时转化为秒)
JsonWebToken.Validate(accessToken, apiAccount.AppSecret, (int)AppSettings.TokenTimeout.TotalSeconds);
}
catch (TokenExpiredException ex)
{
context.ErrorResult = new AuthenticationFailureResult(StatusCodeManager.GetStatusInfo("B03", "a001"), context.Request);
return;
}
catch (SignatureVerificationException ex)
{
context.ErrorResult = new AuthenticationFailureResult(StatusCodeManager.GetStatusInfo("B02", "a001"), context.Request);
return;
} });
//其他验证逻辑
await AuthenticateHockAsync(context, cancellationToken);
} //// <summary>
/// 子类中重写
/// 实现他验证逻辑
/// </summary>
protected abstract Task AuthenticateHockAsync(HttpAuthenticationContext context, System.Threading.CancellationToken cancellationToken); /// <summary>
/// 设置principal
/// </summary>
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, System.Threading.CancellationToken cancellationToken)
{
return Task.FromResult();
}
public bool AllowMultiple
{
get { return true; }
}
}
2 用户授权
某些数据只有用户登陆了才能够获得,并且不同的用户对数据的访问级别也不一样,为实现登陆验证与角色控制,采用以下方式。
在上述实现API接入权限验证的基础上,为headerJson增加一个字段:loginToken;和accessToken相似,loginToken也是JWT标准字符串,不同的是loginToken的payload部分,loginToken的payload结构为:
{
"identifyingCode": GUID,
"account":userAccount
"timestamp": Unix time
}
其中:identifyingCode值为GUID,account为用户账号,timestamp是UNIX时间戳。
客户端不生成loginToken,在客户端合成accessToken后,调用服务端的登陆方法,成功登陆后获得loginToken。
服务端验证流程
客户端调用登陆方法的同时,如果登陆成功,服务端会将登陆信息存储到缓存中,主要的就是loginToken,根据业务需要可以增加其他信息。每一个loginToken对应了一个键值,这里使用useAccount,即用户账号作为键值。服务端获得loginToken后,根据privateKeyId(headerJson字段之一)获得privateKey对loginToken解密,根据payload中的timestamp验证是否过期,然后验证签名是否正确,接着根据account找到上次登陆时服务端缓存中存储的loginToken,比较本次loginToken中的identifyingCode是否与上次一样,不一样表明,其在另一台设备登陆过。
单设备登陆:
某些情形下,不允许多设备同时使用同一账号登陆或多人同时使用同一账号,上述方法采用loginToken中添加identifyingCode字段来控制多设备同时使用同一账号的情形。
示例代码(关键性代码)
public class LoginAuthenticationAttribute : BasicAuthenticationAttribute
{
protected override async Task AuthenticateHockAsync(HttpAuthenticationContext context, System.Threading.CancellationToken cancellationToken)
{
await Task.Factory.StartNew(() =>
{
//解析头信息,获得appid和timestamp
var header = ...
//如果未获得上述信息
if (header == null)
{
context.ErrorResult = new AuthenticationFailureResult(...);
return;
} //获得LoginToken
if (String.IsNullOrWhiteSpace(requestHeaderInfo.LoginToken))
{ //验证凭据是空,设置错误信息
context.ErrorResult = new AuthenticationFailureResult(StatusCodeManager.GetStatusInfo("B01", "a002"), context.Request);
return;
} //从loginToken的payload中获得account,timestamp(时间戳)
var payloadDict = JsonWebToken.DecodeToObject(requestHeaderInfo.LoginToken);
string identifyingCode = Convert.ToString(payloadDict["identifyingCode"]);
string account = Convert.ToString(payloadDict["account"]);
string timestamp = Convert.ToString(payloadDict["timestamp"]);
//从缓存中获得LoginToken
LoginInfoDAL loginInfoDAL = new LoginInfoDAL(AppSettings.TokenTimeout);
LoginCacheModel loginInfo = loginInfoDAL.GetLoginInfo(account);
if (loginInfo == null)
{
context.ErrorResult = new AuthenticationFailureResult(StatusCodeManager.GetStatusInfo("C13", "a002"), context.Request);
return;
} //比较客户端传入LoginToken和缓存中的LoginToken的userId
var payloadDictCache = JsonWebToken.DecodeToObject(loginInfo.LoginToken);
string identifyingCodeCache = Convert.ToString(payloadDictCache["identifyingCode"]); if (identifyingCodeCache != identifyingCode)
{//不相等,提示在另一台设备登陆
context.ErrorResult = new AuthenticationFailureResult(StatusCodeManager.GetStatusInfo("C08", "a002"), context.Request);
return;
} //得到密钥
TokenKeyDAL tokenKeyDAL = new TokenKeyDAL(AppSettings.TokenTimeout);
string loginTokenKey = tokenKeyDAL.GetTokenKey(account);
if (string.IsNullOrWhiteSpace(loginTokenKey))
{
context.ErrorResult = new AuthenticationFailureResult(StatusCodeManager.GetStatusInfo("B04","a002"), context.Request);
return;
} //验证是否超时,LoginToken是否被篡改
try
{
//允许的时间段(小时转化为秒)
int allowSpan = (int)AppSettings.TokenTimeout.TotalSeconds;
JsonWebToken.Validate(requestHeaderInfo.LoginToken, loginTokenKey, allowSpan);
}
catch (TokenExpiredException ex)
{
context.ErrorResult = new AuthenticationFailureResult(StatusCodeManager.GetStatusInfo("B03", "a002"), context.Request);
}
catch (SignatureVerificationException ex)
{
context.ErrorResult = new AuthenticationFailureResult(StatusCodeManager.GetStatusInfo("B02", "a002"), context.Request);
}
});
}
}
ASP.NET Web API编程——接口安全与角色控制的更多相关文章
- ASP.NET Web API编程——路由
路由过程大致分为三个阶段: 1)请求URI匹配已存在路由模板 2)选择控制器 3)选择操作 1匹配已存在的路由模板 路由模板 在WebApiConfig.Register方法中定义路由,例如模板默认生 ...
- ASP.NET Web API编程——序列化与内容协商
1 多媒体格式化器 多媒体类型又叫MIME类型,指示了数据的格式.在HTTP协议中多媒体类型描述了消息体的格式.一个多媒体类型包括两个字符串:类型和子类型. 例如: text/html.image/p ...
- ASP.NET Web API编程——构建api帮助文档
1 概要 创建ASP.NET Web Api 时模板自带Help Pages框架. 2 问题 1)使用VS创建Web Api项目时,模板将Help Pages框架自动集成到其中,使得Web Api项目 ...
- ASP.NET Web Api 2 接口API文档美化之Swagger
使用第三方提供的swgger ui 可有效提高 web api 接口列表的阅读性,并且可以在页面中测试服务接口. 但本人在查阅大量资料并进行编码测试后,发现大部分的swagger实例并不能有效运行.例 ...
- ASP.NET Web API编程——模型验证与绑定
1.模型验证 使用特性约束模型属性 可以使用System.ComponentModel.DataAnnotations提供的特性来限制模型. 例如,Required特性表示字段值不能为空,Range特 ...
- ASP.NET Web API编程——异常捕获
1 向客户端发送错误消息 使用throw new HttpResponseException()向客户端抛出错误信息. HttpResponseException包含两个重载的构造函数,其中一个是构造 ...
- ASP.NET Web API编程——文件下载
断点续传基本原理 HTTP协议中与断点续传相关的HTTP头为:Range和Content-Range标头,断点续传实现流程: 1)客户端请求下载一个文件,文件的总长度为n:已经下载了一部分文件,长度为 ...
- ASP.NET Web API编程——版本控制
版本控制 版本控制的方法有很多,这里提供一种将Odata与普通web api版本控制机制统一的方法,但也可以单独控制,整合控制与单独控制主要的不同是:整合控制通过VersionController ...
- ASP.NET Web API编程——文件上传
首先分别介绍正确的做法和错误的做法,然后分析他们的不同和错误之处,以便读者在实现此功能时可避开误区 1正确的做法 public class AvaterController : BaseApiCont ...
随机推荐
- ASP.NET Web API根据代码注释生成Help文档
使用Visual Studio新建一个ASP.NET Web API项目,直接运行,查看Help文档可以看到如下的API帮助说明 如何在Description中显示描述. 1. 打开Controlle ...
- Query performance optimization of Vertica
Don't fetch any data that you don't need,or don't fetch any columns that you don't need. Because ret ...
- 灵感一:搜索型APP,帮助读书爱好者,搜索某本书的关键字
灵感来自生活,我在查询 javascript 高级程序设计 3的时候,由于我记不清楚,该关键字在书的某个地方,我就想,有没有这么一款APP,可以通过扫描一本书的二维码,自动下载该书的内容,然后再通过搜 ...
- golang 生成图表
golang 支持生产图片的源码在下面地址可以看到: https://github.com/vdobler/chart 这个项目 的 example 目录下编译后,执行下面命令就可以生产所有它支持的图 ...
- Linux pip 安装模块时,一直黄字错误:Could not find a version that satisfies the requirement
参考原文:https://blog.csdn.net/u012592062/article/details/51966649 这时我们用国内的镜像源来加速 pip install 包名-i http: ...
- 无法解析 id,或者它不是字段
解决方法:首先,看下R文件,有没有你上面的ID.没有的话,点项目-clean . 有的话,估计你是导了android里面的那个R包了,你看看你导的包有木有 “import android.R”有的话去 ...
- 执行shell脚本报错:syntax error near unexpected token `或syntax error: unexpected end of file
引起此问题最可能的原因是: 在windows下编写的文件上传到linux执行. 我是在notepad++上编写的代码,之后上传到linux执行,报此错误.仔细检查,语法方面没有错误.上网查了一下,发现 ...
- python文件操作各种模式和常用方法总结r r+ rb r+b
1.读,r r+ rb r+b read() 全部读取 read(n) 读取一部分 r模式下:n 个字符. rb模式下:n个 字节. readline() 按行读取. readlines() 返回个列 ...
- sql server or Oracle: table MS_Description
--SQL Server表描述 及 字段描述的增.删.改.查询 --sql server 2000系统表sysproperties在SQL 2008中无效的问题 今天无意中在网上发现Sqlserver ...
- EasyUI combobox 多选及回显赋值
multiple boolean 决定是否支持多项选择. $('#cc').combobox({ url:'combobox_data.json', multiple:true, //支持多选 val ...