最近新开发一个需要给App使用的API项目。开发API肯定会想到JASON Web Token(JWT)和OAuthor2(之前一篇随笔记录过OAuthor2)。

JWT和OAuthor2的比较

  要像比较JWT和OAuthor2,首先要明白一点就是,这是两个完全不同的东西,没有可比性。

  JWT是一种认证协议

    官网:http://jwt.io

    JWT提供了一种用于发布介入灵摆(Access Token),并对发布的签名介入令牌进行验证的方法。令牌(Token)本身包含了一系列声明,应用程序可以根据这些声明限制用户对资源的访问。

    在新开发的API中,我选择的是使用JWT,稍后会简单介绍其在.net core中的使用。

  OAuthor2是一种授权框架

    OAuthor2是一种授权框架,提供了一套详细的授权机制(指导)。用户或应用可以通过公开的或私有的设置,授权第三方应用访问特定资源。

  既然JWT和OAuthor2没有可比性,为什么还要把这两个放在一起说呢?实际中,会有很多人拿JWT和OAuthor2作比较,或者分不清楚。很多情况下,在讨论OAuthor2的实现时,会把JSON Web Token作为一种认证机制使用。这也是为什么他们会经常一起出现。

JSON Web Token(JWT)

  JWT是一种安全标准。基本思路就是用户提供用户名和密码给认证服务器,服务器验证用户提交的信息的合法性,如果认证成功,会产生并返回一个Token(令牌),用户可以使用这个token访问服务器上受保护的资源。

一个token的例子:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEiLCJuYW1lIjoibGl1dGFvIiwicm9sZSI6InNob3BVc2VycyIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvcm9sZSI6InNob3BVc2VycyIsImFjdCI6IjEiLCJuYmYiOjE1NzQyNTAyMTgsImV4cCI6MTU3NTExNDIxOCwiaXNzIjoiWXVZdWUiLCJhdWQiOiJZdVl1ZSJ9.t39iwO-r_YgX5-7XyIV-by2duHfThqTQayI595VtqF

一个token包含三个部分:

header.claims.signature

为了安全的在url中使用,所有部分都base64 URL-safe进行编码处理。

Header头部分

  头部分简单声明了类型(JWT)以及产生签名所使用的的算法。

{
"alg" : "AES256",
"typ" : "JWT"
}

Claims声明

  声明部分是整个token的核心,表示要发送的用户详细信息。游学情况下,我们和有可能要在一个服务器上实现认证,然后访问另一台服务器上的资源,或者,通过单独的接口来生成token,token被保存在应用程序客户端(比如浏览器)使用。

  一个简单的声明(claim)的例子:

{
"sub": "",
"name": "John Doe",
"admin": true
}

Signature签名

  签名的目的是为了保证上边两部分信息不被篡改。如果尝试使用Bas64对解码后的token进行修改,签名信息就会失效。一般使用一个私钥(private key)通过特定算法对Header和Claims进行混淆产生签名信息,所以只有原始的token才能于签名信息匹配。
        这里有一个重要的实现细节。只有获取了私钥的应用程序(比如服务器端应用)才能完全认证token包含声明信息的合法性。所以,永远不要把私钥信息放在客户端(比如浏览器)。

OAuthor2是什么?

  官网:http://oauth.net/2/

  相反,OAuthor2不是一个标准协议,而是一个安全的授权框架,它详细描述了系统中不同角色、用户、服务前端应用(比如API),以及客户端(比如网站或移动APP)之间怎么实现相互认证。

OAuthor2的基本概念,可以去阅读之前的一片随笔。点击此处

使用HTTPS保护用户密码

  在进一步讨论OAuthor2和JWT的实现之前,有必要说一下,两种方案都需要SSL安全保护,也就是对要传输的数据进行加密编码。安全地传输用户提供的私密信息,在任何一个安全的系统里都是必要的。否则任何人都可以通过侵入私人wifi,在用户登录的时候窃取用户的用户名和密码等信息。

JWT和OAuthor2应该如何选择

  在做选择之前,参考一下下边提到的几点。

  1、时间投入

    OAuthor2是一个安全框架,描述了在各种不同场景下,多个应用之间的授权问题。有海量的资料需要学习,要完全理解需要花费大量时间。甚至对于一些有经验的开发工程师来说,也会需要大概一个月的时间来深入理解OAuth2。 这是个很大的时间投入。相反,JWT是一个相对轻量级的概念。可能花一天时间深入学习一下标准规范,就可以很容易地开始具体实施。

  2、出现错误的风险

    OAuth2不像JWT一样是一个严格的标准协议,因此在实施过程中更容易出错。尽管有很多现有的库,但是每个库的成熟度也不尽相同,同样很容易引入各种错误。在常用的库中也很容易发现一些安全漏洞。当然,如果有相当成熟、强大的开发团队来持续OAuth2实施和维护,可以一定成都上避免这些风险。

  3、社交登录的好处

    在很多情况下,使用用户在大型社交网站的已有账户来认证会方便。如果期望你的用户可以直接使用Facebook或者Gmail之类的账户,使用现有的库会方便得多。

JWT的使用场景

无状态的分布式API

  JWT的主要优势在于使用无状态、可扩展的方式处理应用中的用户会话。服务端可以通过内嵌的声明信息,很容易地获取用户的会话信息,而不需要去访问用户或会话的数据库。在一个分布式的面向服务的框架中,这一点非常有用。但是,如果系统中需要使用黑名单实现长期有效的token刷新机制,这种无状态的优势就不明显了。

优势:

  1、快速开发

  2、不需要cookie

  3、JSON在移动端的广泛应用

  4、不依赖与社交登录

  5、相对简单的概念理解

限制

  1、token有长度限制

  2、token不能撤销

  3、需要token有失效时间限制(exp)

OAuthor2使用场景

外包认证服务器

  上边已经讨论过,如果不介意API的使用依赖于外部的第三方认证提供者,你可以简单地把认证工作留给认证服务商去做。也就是常见的,去认证服务商(比如facebook)那里注册你的应用,然后设置需要访问的用户信息,比如电子邮箱、姓名等。当用户访问站点的注册页面时,会看到连接到第三方提供商的入口。用户点击以后被重定向到对应的认证服务商网站,获得用户的授权后就可以访问到需要的信息,然后重定向回来。

优势:

  1、快速开发

  2、实施代码量小

  3、维护工作减少

大型企业解决方案

  如果设计的API要被不同的App使用,并且每个App使用的方式也不一样,使用OAuth2是个不错的选择。考虑到工作量,可能需要单独的团队,针对各种应用开发完善、灵活的安全策略。当然需要的工作量也比较大!

优势

  1、灵活的实现方式

  2、可以和JWT同时使用

  3、可以针对不同的应用扩展

简单介绍下在.net core的项目中是如何使用JWT的。

首先,我们的服务是基于组件化的,当然需要先把身份认证的服务注册进来。在Startup类中的ConfigureServices()方法中:

  services.AddSingleton<ITokenHelper, TokenHelper>();
  // configure strongly typed settings objects
  var jwtConfigSection = Configuration.GetSection("Authentication:JwtBearer");
  services.Configure<JWTConfig>(jwtConfigSection);

  // configure jwt authentication
  var jwtConfig = jwtConfigSection.Get<JWTConfig>();

services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddCookie(AdminUserAccountConst.AdminUserCookie, options =>
{
options.Cookie.Name = AdminUserAccountConst.AdminUserCookieName;
options.Cookie.HttpOnly = true;
options.LoginPath = AdminUserAccountConst.AdminUserLoginPath;
options.AccessDeniedPath = AdminUserAccountConst.AdminUserLoginPath;
}).AddJwtBearer(AdminUserAccountConst.AdminUserJwt, o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role,
ValidateLifetime = false,
ValidIssuer = Configuration["Authentication:JwtBearer:Issuer"],
ValidAudience = Configuration["Authentication:JwtBearer:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration["Authentication:JwtBearer:SecurityKey"]))
};
o.ForwardChallenge = AdminUserAccountConst.AdminUserCookie;
});

下面是上面所需要用到一些自定义类型:

AdminUserAccountConst

public class AdminUserAccountConst
{
public const string AdminUserCookie = "AdminUserCookies"; public const string AdminUserCookieName = "AdminUserCookieName"; public const string AdminUserLoginPath = "/account/login"; public const string AdminUserJwt = "AdminUserJwt"; public const string AdminUserRole = "adminuser";
}

JWTConfig

public class JWTConfig
{
public string Issuer { get; set; }
public string Audience { get; set; }
public string IssuerSigningKey { get; set; }
public int AccessTokenExpiresMinutes { get; set; } public string RefreshTokenAudience { get; set; }
public int RefreshTokenExpiresMinutes { get; set; }
}

至于这些类型的字段,可以自行在appsettings.json中去赋值。

"Authentication": {
"JwtBearer": {
"Issuer": "Bingle",
"Audience": "Bingle",
"IssuerSigningKey": "Bingle_C421AAEE0D114EAAACVD",
"AccessTokenExpiresMinutes": "", "RefreshTokenAudience": "RefreshTokenAudience",
"RefreshTokenExpiresMinutes": "" //60*24*30
}
},

ITokenHelper与TokenHepler

 public interface ITokenHelper
{
ComplexToken CreateToken(User user);
ComplexToken CreateToken(Claim[] claims);
(Result result, string userCode) ConfirmRefreshToken(string refreshToken);
} public class TokenHelper : ITokenHelper
{
private readonly IOptions<JWTConfig> _options;
public TokenHelper(IOptions<JWTConfig> options)
{
_options = options;
} public ComplexToken CreateToken(User user)
{
Claim[] claims = new Claim[]
{
new Claim(JwtClaimTypes.Id, user.UserCode),
new Claim(JwtClaimTypes.Name, user.UserName),
new Claim(JwtClaimTypes.Role, user.UserRole.GetExtendDescription()),
new Claim(ClaimTypes.Role, user.UserRole.GetExtendDescription()),
new Claim(JwtClaimTypes.Actor, user.PartyId)
};
return CreateToken(claims);
} public ComplexToken CreateToken(Claim[] claims)
{
return new ComplexToken
{
AccessToken = CreateToken(claims, TokenType.AccessToken),
RefreshToken = CreateToken(new Claim[]{claims.First(x=>x.Type == JwtClaimTypes.Id)}, TokenType.RefreshToken)
};
} /// <summary>
/// 用于创建AccessToken和RefreshToken。
/// 这里AccessToken和RefreshToken只是过期时间不同,【实际项目】中二者的claims内容可能会不同。
/// 因为RefreshToken只是用于刷新AccessToken,其内容可以简单一些。
/// 而AccessToken可能会附加一些其他的Claim。
/// </summary>
/// <param name="claims"></param>
/// <param name="tokenType"></param>
/// <returns></returns>
private Token CreateToken(Claim[] claims, TokenType tokenType)
{
var now = DateTime.Now;
var expires = now.Add(TimeSpan.FromMinutes(tokenType.Equals(TokenType.AccessToken) ? _options.Value.AccessTokenExpiresMinutes : _options.Value.RefreshTokenExpiresMinutes));
var token = new JwtSecurityToken(
issuer: _options.Value.Issuer,
audience: tokenType.Equals(TokenType.AccessToken) ? _options.Value.Audience : _options.Value.RefreshTokenAudience,
claims: claims,
notBefore: now,
expires: expires,
signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_options.Value.IssuerSigningKey)), SecurityAlgorithms.HmacSha256));
return new Token { TokenContent = new JwtSecurityTokenHandler().WriteToken(token), Expires = expires };
} public (Result result, string userCode) ConfirmRefreshToken(string refreshToken)
{
var tokenHandler = new JwtSecurityTokenHandler();
if (!tokenHandler.CanReadToken(refreshToken))
return (Result.FromCode(ResultCode.InvalidToken, "RefreshToken不正确"), null);
var jwtSecurityToken = tokenHandler.ReadJwtToken(refreshToken);
if (jwtSecurityToken.Issuer != _options.Value.Issuer || !jwtSecurityToken.Audiences.Contains(_options.Value.RefreshTokenAudience))
return (Result.FromCode(ResultCode.InvalidToken, "RefreshToken不正确"), null);
if (jwtSecurityToken.ValidTo < DateTime.Now)
return (Result.FromCode(ResultCode.InvalidToken, "RefreshToken已经过期了"), null); return (Result.Ok(), jwtSecurityToken.Claims.First(x => x.Type == JwtClaimTypes.Id).Value);
} }

还要在Configure方法中使用中间件:

app.UseAuthentication();

首先,定义一个API的基类,后面的API继承此基类就可以了

[Route("[controller]/[action]")]
[ApiController]
[Authorize(
AuthenticationSchemes = AdminUserAccountConst.AdminUserCookie,
Roles = AdminUserAccountConst.AdminUserRole)]
public class BasicAdminController : ControllerBase
{
}

现在新建一个用户登录和退出的APIController继承与上面那个基类就可以了。这里简化 了代码

[HttpPost]
[AllowAnonymous]
[ProducesResponseType(typeof(Result<TokenResultDto>), )]
public JsonResult Login([FromBody]LoginDto model)
{
var user = new User();//这里需要去数据库中进行校验
if (user == null)
return Json(new {IsSuccess=false,Msg="参数错误"});
var result = _tokenHelper.CreateToken(new User
{
UserCode = user.UserCode,
UserName = user.UserName,
Telphone = user.Telphone,
PartyId = user.ShopCode,
UserRole = UserRoleEnum.user,
}); user.RefreshToken = result.RefreshToken.TokenContent;
return Json(new TokenResultDto
{
AccessToken = result.AccessToken.TokenContent,
Expires = result.AccessToken.Expires,
RefreshToken = result.RefreshToken.TokenContent,
});
}

这里使用AllowAnonymous标签,是因为登录并不需要进行身份验证。当需要授权才能访问的接口,不需要加上这个标签。

设计安全的API-JWT与OAuthor2的更多相关文章

  1. OAuth 2和JWT - 如何设计安全的API?

    OAuth 2和JWT - 如何设计安全的API? Moakap译,原文 OAuth 2 VS JSON Web Tokens: How to secure an API 本文会详细描述两种通用的保证 ...

  2. 使用JWT设计SpringBoot项目api接口安全服务

    转载直: 使用JWT设计SpringBoot项目api接口安全服务

  3. OAuth2和JWT - 如何设计安全的API?

    JWT和OAuth2比较? 要比较JWT和OAuth2?首先要明白一点就是,这两个根本没有可比性,是两个完全不同的东西. JWT是一种认证协议        JWT提供了一种用于发布接入令牌(Acce ...

  4. 如何构建和设计以确保 API 的安全性

    如何构建和设计以确保 API 的安全性 面对常见的OWASP十大威胁.未经授权的访问.拒绝服务攻击.以及窃取机密数据等类型的攻击,企业需要使用通用的安全框架,来保护其REST API,并保证良好的用户 ...

  5. Atitit.论图片类型 垃圾文件的识别与清理  流程与设计原则 与api概要设计 v2 pbj

    Atitit.论图片类型 垃圾文件的识别与清理  流程与设计原则 与api概要设计 v2 pbj 1. 俩个问题::识别垃圾文件与清理策略1 2. 如何识别垃圾图片1 2.1. 体积过小文件<1 ...

  6. 如何设计优秀的API(转)

    到目前为止,已经负责API接近两年了,这两年中发现现有的API存在的问题越来越多,但很多API一旦发布后就不再能修改了,即时升级和维护是必须的.一旦API发生变化,就可能对相关的调用者带来巨大的代价, ...

  7. 队列链式存储 - 设计与实现 - API函数

    队列相关基础内容参我的博文:队列顺序存储 - 设计与实现 - API函数 队列也是一种特殊的线性表:可以用线性表链式存储来模拟队列的链式存储. 主要代码: // linkqueue.h // 队列链式 ...

  8. 队列顺序存储 - 设计与实现 - API函数

    队列是一种特殊的线性表 队列仅在线性表的两端进行操作 队头(Front):取出数据元素的一端 队尾(Rear):插入数据元素的一端 队列不允许在中间部位进行操作! queue常用操作 销毁队列 清空队 ...

  9. ATITIT.翻译模块的设计与实现 api attilax 总结

    ATITIT.翻译模块的设计与实现 api attilax 总结 1. 翻译原理1 2. TMX格式是国际通用格式(xml)1 2.1. 方法/步骤2 3. TRADOS2 4. ATITIT.翻译软 ...

  10. 如何为非常不确定的行为(如并发)设计安全的 API,使用这些 API 时如何确保安全

    原文:如何为非常不确定的行为(如并发)设计安全的 API,使用这些 API 时如何确保安全 .NET 中提供了一些线程安全的类型,如 ConcurrentDictionary<TKey, TVa ...

随机推荐

  1. mac本地搭建svn

    mac系统默认已经安装了svn,我们只需要配置并开启就可以了. 首先我们可以验证一下是否安装了svn,打开终端,输入命令 svnserve —version

  2. 学习索引结构的一些案例——Jeff Dean在SystemML会议上发布的论文(下)

    [摘要] 除了范围索引之外,点查找的Hash Map在DBMS中起着类似或更重要的作用. 从概念上讲,Hash Map使用Hash函数来确定性地将键映射到数组内的随机位置(参见图[9 ],只有4位开销 ...

  3. shell 脚本运行 hive sql

    #!/b START=$(date +%s); datebegin=`date -d "$1" "+%Y%m%d"` dateend=`date -d &quo ...

  4. Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: REFERENCES command denied to user 'nali'@'localhost' for table 'dbs'

    按照教程 Install hive on Mac with Homebrew,在 mac 上安装 Hive 时, 最后执行 hive 命令后,出现错误: Exception in thread &qu ...

  5. Json schema 以及在python中的jsonschema

    目录 1. JSON Schema简介 2. JSON Schema关键字详解 2.1 $schema 2.2 title和description 2.3 type 3 type常见取值 3.1 当t ...

  6. Pandas里面常用的一些数据分析函数总结

    import pandas as pdimport numpy as np pandas 有两个主要的数据结构:Series 和 DataFrame:Series 是一个一维数组对象 ,它包含一组索引 ...

  7. ARTS-S golang定义类

    package main import "fmt" type Student struct { Num int64 Name string } func(s *Student) s ...

  8. uni-app微信小程序开发之引入腾讯视频小程序播放插件

    登录微信小程序管理后台添加腾讯视频播放插件: 正式开始使用腾讯视频小程序插件之前需先在微信公众平台 -> 第三方设置 -> 插件管理处添加插件,如下图所示: 在uni-app中引入插件代码 ...

  9. 【CSS】381- 提升你的CSS选择器技巧

    我已经使用CSS多年了,但直到最近我才深入研究了一下CSS选择器. 我为什么要这样做呢?我们都知道选择器,但麻烦的是随着时间的推移,很容易习惯于在每个项目中使用相同的可信任选择器来实现你需要做的事情. ...

  10. LAMP两种模式

    [LAMP] Linux(Centos)LAMP环境搭建,LAMP源码安装及LAMP架构原理详解 Wish_亮关注2人评论9469人阅读2018-08-20 01:33:10   本章blog主要介绍 ...