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 ...
随机推荐
- Layer UI 模块化的用法(转)
此文章适合入门的同学查看,之前因为项目的原因,在网上找了一套Layer UI做的后台管理系统模板,完全不懂LayUI里面的JS用法,看了官方文档和其它资料后才明白怎么去实现模块化这个例子,但是还是感觉 ...
- PLC编程逻辑思路
PLC编程逻辑思路 在整个执行过程的流程中,都是在不断地找启动条件,停止条件以及输出结果.当条件不够时,就得想办法如果添加标志位,根据已有条件去构造条件:当结果开发耦合时,就制造中间继电器去除耦合. ...
- 总结—angularjs项目
我毕业了-------有点期待生活,又点害怕生活. 总结下最近一个月做的这个项目,项目的开发形式也比较新颖,采用的是前后端分离的形式.我负责前端的管理系统开发,另一个哥们负责利用ABP创建接口,整合后 ...
- 【转】.NET 4.5 使用async和await关键字调用异步方法
async和await关键字是.NET 4.5新增加的异步编程方式,通过使用这两个关键字可以轻松便捷的编写异步方法.使用async关键字声明异步方法,使用await关键字等待和获取异步方法返回的结果. ...
- 如何删除eclipse中已经保存的svn密码
一.打开eclipse--->点击Window--->点击Perference,打开eclipse配置,输入svn,然后点击svn,找到下方svn接口,查看下svn是什么类型的接口,如果是 ...
- Python基础学习总结(四)
6.高阶特性 6.1迭代 如果给定一个list或tuple,我们可以通过for循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration).在Python中,迭代是通过for ... ...
- Linux_vim文本编辑器指令整理
一般指令模式 : 可以移动光标,可以删除字符和删除整列,可以复制粘贴 编辑模式 : 按下"i, I, o, O, a, A, r, R"任意一个字母时进入;按下ESC退出编辑模式 ...
- Uva 1378 - A Funny Stone Game
1378 - A Funny Stone Game Time limit: 3.000 seconds The funny stone game is coming. There are n pile ...
- Tomcat Post请求大小限制
理论上讲,POST是没有大小限制的.HTTP协议规范也没有进行大小限制,起限制作用的是服务器的处理程序的处理能力. 如:在Tomcat下取消POST大小的限制(Tomcat默认2M): 打开tomca ...
- 打印thinkphp中的sql语句
var_dump($repair->fetchSql(true)->where(array('cuername' =>$cuername))->order('applytime ...