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编程——接口安全与角色控制的更多相关文章

  1. ASP.NET Web API编程——路由

    路由过程大致分为三个阶段: 1)请求URI匹配已存在路由模板 2)选择控制器 3)选择操作 1匹配已存在的路由模板 路由模板 在WebApiConfig.Register方法中定义路由,例如模板默认生 ...

  2. ASP.NET Web API编程——序列化与内容协商

    1 多媒体格式化器 多媒体类型又叫MIME类型,指示了数据的格式.在HTTP协议中多媒体类型描述了消息体的格式.一个多媒体类型包括两个字符串:类型和子类型. 例如: text/html.image/p ...

  3. ASP.NET Web API编程——构建api帮助文档

    1 概要 创建ASP.NET Web Api 时模板自带Help Pages框架. 2 问题 1)使用VS创建Web Api项目时,模板将Help Pages框架自动集成到其中,使得Web Api项目 ...

  4. ASP.NET Web Api 2 接口API文档美化之Swagger

    使用第三方提供的swgger ui 可有效提高 web api 接口列表的阅读性,并且可以在页面中测试服务接口. 但本人在查阅大量资料并进行编码测试后,发现大部分的swagger实例并不能有效运行.例 ...

  5. ASP.NET Web API编程——模型验证与绑定

    1.模型验证 使用特性约束模型属性 可以使用System.ComponentModel.DataAnnotations提供的特性来限制模型. 例如,Required特性表示字段值不能为空,Range特 ...

  6. ASP.NET Web API编程——异常捕获

    1 向客户端发送错误消息 使用throw new HttpResponseException()向客户端抛出错误信息. HttpResponseException包含两个重载的构造函数,其中一个是构造 ...

  7. ASP.NET Web API编程——文件下载

    断点续传基本原理 HTTP协议中与断点续传相关的HTTP头为:Range和Content-Range标头,断点续传实现流程: 1)客户端请求下载一个文件,文件的总长度为n:已经下载了一部分文件,长度为 ...

  8. ASP.NET Web API编程——版本控制

    版本控制   版本控制的方法有很多,这里提供一种将Odata与普通web api版本控制机制统一的方法,但也可以单独控制,整合控制与单独控制主要的不同是:整合控制通过VersionController ...

  9. ASP.NET Web API编程——文件上传

    首先分别介绍正确的做法和错误的做法,然后分析他们的不同和错误之处,以便读者在实现此功能时可避开误区 1正确的做法 public class AvaterController : BaseApiCont ...

随机推荐

  1. 西数常用TREX命令

    西数常用TREX命令 trex命令:dut1 简便找盘idp或info did查看硬盘信息chkresfall检测固件smart 查看SMART表clrsmart 清SMART表svmod 0x.. ...

  2. [shell]管理 Sphinx 启动|停止|重新生成索引的脚本

    对于启动sphinx的服务,可以直接输入如下命令 /usr/bin/searchd -c /etc/sphinx/sphinx.conf <!-- /usr/local/bin/searchd  ...

  3. java通过超链接和servlet配置实现服务器文件下载

    1.在页面上面我们可以简单的写成: <td align="center""> <a href="<c:url value="/ ...

  4. C Primer Plus note5

    error: expected '=', ',', ';', 'asm' or '__attribute__' before '{' token| 遇到这种情况,不要看这里显示了三个错误,就很着急.静 ...

  5. JS里的居民们7-对象和数组转换

    编码 学习通用的数据用不同的数据结构进行存储,以及相互的转换 对象转为数组: var scoreObject = { "Tony": { "Math": 95, ...

  6. Makefile一 头文件及库搜索路径

    头文件及库搜索路径 头文件的搜索路径: 头文件的搜索规则是:找到就使用,停止继续往下寻找 1: #include “mytest.h” 搜索的顺序为: (1)先搜索当前目录 (2)然后搜索编译时 -I ...

  7. 为什么canvas宽高要设置在标签内>>宽高设置在style和设置在canvas的区别

    一直很困惑为什么canvas标签和其他标签不一样,宽高需要设置在canvas标签里,设置在style里就会有问题. 纯粹个人理解,有错误欢迎指出. > 结论写在头 设置在style里有问题其实是 ...

  8. C# 索引器的使用

    索引器允许类或者结构的实例按照与数组相同的方式进行索引取值,索引器与属性类似,不同的是索引器的访问是带参的. 索引器和数组比较: (1)索引器的索引值(Index)类型不受限制 (2)索引器允许重载 ...

  9. [转载]hive中order by,sort by, distribute by, cluster by作用以及用法

    1. order by     Hive中的order by跟传统的sql语言中的order by作用是一样的,会对查询的结果做一次全局排序,所以说,只有hive的sql中制定了order by所有的 ...

  10. 常用APDU指令错误码

    状态码 性质 错误解释 9000 正常 成功执行 6200 警告 信息未提供 6281 警告 回送数据可能出错 6282 警告 文件长度小于Le 6283 警告 选中的文件无效 6284 警告 FCI ...