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. ASP.NET Web API根据代码注释生成Help文档

    使用Visual Studio新建一个ASP.NET Web API项目,直接运行,查看Help文档可以看到如下的API帮助说明 如何在Description中显示描述. 1. 打开Controlle ...

  2. 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 ...

  3. 灵感一:搜索型APP,帮助读书爱好者,搜索某本书的关键字

    灵感来自生活,我在查询 javascript 高级程序设计 3的时候,由于我记不清楚,该关键字在书的某个地方,我就想,有没有这么一款APP,可以通过扫描一本书的二维码,自动下载该书的内容,然后再通过搜 ...

  4. golang 生成图表

    golang 支持生产图片的源码在下面地址可以看到: https://github.com/vdobler/chart 这个项目 的 example 目录下编译后,执行下面命令就可以生产所有它支持的图 ...

  5. Linux pip 安装模块时,一直黄字错误:Could not find a version that satisfies the requirement

    参考原文:https://blog.csdn.net/u012592062/article/details/51966649 这时我们用国内的镜像源来加速 pip install 包名-i http: ...

  6. 无法解析 id,或者它不是字段

    解决方法:首先,看下R文件,有没有你上面的ID.没有的话,点项目-clean . 有的话,估计你是导了android里面的那个R包了,你看看你导的包有木有 “import android.R”有的话去 ...

  7. 执行shell脚本报错:syntax error near unexpected token `或syntax error: unexpected end of file

    引起此问题最可能的原因是: 在windows下编写的文件上传到linux执行. 我是在notepad++上编写的代码,之后上传到linux执行,报此错误.仔细检查,语法方面没有错误.上网查了一下,发现 ...

  8. python文件操作各种模式和常用方法总结r r+ rb r+b

    1.读,r r+ rb r+b read() 全部读取 read(n) 读取一部分 r模式下:n 个字符. rb模式下:n个 字节. readline() 按行读取. readlines() 返回个列 ...

  9. sql server or Oracle: table MS_Description

    --SQL Server表描述 及 字段描述的增.删.改.查询 --sql server 2000系统表sysproperties在SQL 2008中无效的问题 今天无意中在网上发现Sqlserver ...

  10. EasyUI combobox 多选及回显赋值

    multiple boolean 决定是否支持多项选择. $('#cc').combobox({ url:'combobox_data.json', multiple:true, //支持多选 val ...