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 ...
随机推荐
- 西数常用TREX命令
西数常用TREX命令 trex命令:dut1 简便找盘idp或info did查看硬盘信息chkresfall检测固件smart 查看SMART表clrsmart 清SMART表svmod 0x.. ...
- [shell]管理 Sphinx 启动|停止|重新生成索引的脚本
对于启动sphinx的服务,可以直接输入如下命令 /usr/bin/searchd -c /etc/sphinx/sphinx.conf <!-- /usr/local/bin/searchd ...
- java通过超链接和servlet配置实现服务器文件下载
1.在页面上面我们可以简单的写成: <td align="center""> <a href="<c:url value="/ ...
- C Primer Plus note5
error: expected '=', ',', ';', 'asm' or '__attribute__' before '{' token| 遇到这种情况,不要看这里显示了三个错误,就很着急.静 ...
- JS里的居民们7-对象和数组转换
编码 学习通用的数据用不同的数据结构进行存储,以及相互的转换 对象转为数组: var scoreObject = { "Tony": { "Math": 95, ...
- Makefile一 头文件及库搜索路径
头文件及库搜索路径 头文件的搜索路径: 头文件的搜索规则是:找到就使用,停止继续往下寻找 1: #include “mytest.h” 搜索的顺序为: (1)先搜索当前目录 (2)然后搜索编译时 -I ...
- 为什么canvas宽高要设置在标签内>>宽高设置在style和设置在canvas的区别
一直很困惑为什么canvas标签和其他标签不一样,宽高需要设置在canvas标签里,设置在style里就会有问题. 纯粹个人理解,有错误欢迎指出. > 结论写在头 设置在style里有问题其实是 ...
- C# 索引器的使用
索引器允许类或者结构的实例按照与数组相同的方式进行索引取值,索引器与属性类似,不同的是索引器的访问是带参的. 索引器和数组比较: (1)索引器的索引值(Index)类型不受限制 (2)索引器允许重载 ...
- [转载]hive中order by,sort by, distribute by, cluster by作用以及用法
1. order by Hive中的order by跟传统的sql语言中的order by作用是一样的,会对查询的结果做一次全局排序,所以说,只有hive的sql中制定了order by所有的 ...
- 常用APDU指令错误码
状态码 性质 错误解释 9000 正常 成功执行 6200 警告 信息未提供 6281 警告 回送数据可能出错 6282 警告 文件长度小于Le 6283 警告 选中的文件无效 6284 警告 FCI ...