ASP.NET Core 使用 JWT 搭建分布式无状态身份验证系统
升级到 Asp.Net Core 2.0 (2017/08/29 更新)
为什么使用 Jwt
最近,移动开发的劲头越来越足,学校搞的各种比赛都需要用手机 APP 来撑场面,所以,作为写后端的,很有必要改进一下以往的基于 Session 的身份认证方式了,理由如下:
- 移动端经常要保持长时间(1 到 2 星期)在线,但是 Session 却不好在服务端保存这么久,虽然可以持久化到数据库,但是还是挺费资源
- 移动端往往不是使用的网页技术,所以藏在 Cookie 里面的 SessionId 不是很方便的传递给服务端
- 服务端暴露给客户端的接口往往是 RESTful 风格的,这是一种无状态的 API 风格,所以身份认证的方式最好也是无状态的才好
所以我选择了使用 Jwt (Json Web Token) 这个技术。Jwt 是一种无状态的分布式的身份验证方式,与 Session 相反,Jwt 将用户信息存放在 Token 的 payload 字段保存在客户端,通过 RSA 加密的方式,保证数据不会被篡改,验证数据有效性。
下面是一个使用 Jwt 的系统的身份验证流程:
可以看出,用户的信息保存在 Token 中,而 Token 分布在用户的设备中,所以服务端不再需要在内存中保存用户信息了
身份认证的 Token 传递时以一种相当简单的格式保存在 header 中,方便客户端对其进行操作
Jwt 简介
Jwt 形式的 token 一般分为 3 个部分,分别是 Header,Payload,Signature,这三个部分使用 .
分隔。其中前两部分使用 Base64 编码,未经加密处理,第三个部分使用 RSA 加密。
所以一个 Jwt 看起来大概是这个样子:
header.payload.signature
下面是一个真实的 Jwt:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6InplZWtvIiwicm9sZSI6IiIsIm5hbWVpZCI6MSwianRpIjoiNjNjN2Q3OWY2N2VhMDhjYjRiYzNjMmNkOTJiY2JkNTgiLCJuYmYiOjE0OTQ0MDMwMjQsImV4cCI6MTQ5NTAwNzgyMywiaWF0IjoxNDk0NDAzMDI0LCJpc3MiOiJUZXN0SXNzdWVyIiwiYXVkIjoiVGVzdEF1ZGllbmNlIn0.V7Mfi3FGOTLYV0O5DmOWju7LkDJwZNO6HZN19CHb3ekYxcoVbP51YjYAr0fUHc3RPIp3gxITzziHY-07xZ2swCaV0K-hiF5IbwpDuvyxsnlgaRxS94wKDGKSJkArC82KukCtm7IuFBxnNr6kxe7tGcebVhqtaqgnxEUg5lKtDtVI85kd17YtzBp9Vxnc3Ie0r-6KPgUa2HacCf2Pc3hkvY7tZdWZ6ininZlZ-EbcyZI2KTx-vOqdK63MS2JYSw7W2qwf89tsRsORwbB2P4dOBBFK8YSXJpeyGeJWFEMjAMkiH3AeMmW2w_H7r_6Pn-jh5gozzBei4JoHTU6RVDUg1A
Header
Header 部分一般用来记录加密算法跟 Token 类型
举个例子:
{
"alg": "HS256",
"typ": "JWT"
}
Payload
Payload 存放的是一些不敏感的用户数据,因为这一部分仅仅只是使用 Base64 加密,所以不应该用来保存用户的密码之类的信息。
一个例子:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
Signature
这一部分是 Jwt 最重要的部分,使用 header 中记录的算法进行了加密,加密方式如下:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
所以这个部分可以用来保证用户信息不被篡改,起到验证用户身份的作用
在开发过程中,可以访问 https://jwt.io 来调试 Token
当然,为了更快的访问速度,还可以使用 这个网站
在 ASP.NET Core 中使用 Jwt
因为 Jwt 本身的特点,所以用来签发 Token 的服务器可以跟应用服务器不是同一台,这样就可以搞微服务之类的东西(反正我不懂。。。)
因此,在这篇博客中,将会创建两个 Web 应用:
- JwtIssuer // 用来签发 Token
- JwtAudience // 应用服务器,提供 API 的
首先来搭建我们的 Token 签发服务器吧!
签发第一个 Token
由于要使用到 RSA 加密,所以先创建一个辅助类来帮助简化调用:
RSAUtils.cs
using System.IO;
using System.Security.Cryptography;
using Newtonsoft.Json;
namespace JwtUtils
{
public static class RSAUtils
{
/// <summary>
/// 从本地文件中读取用来签发 Token 的 RSA Key
/// </summary>
/// <param name="filePath">存放密钥的文件夹路径</param>
/// <param name="withPrivate"></param>
/// <param name="keyParameters"></param>
/// <returns></returns>
public static bool TryGetKeyParameters(string filePath, bool withPrivate, out RSAParameters keyParameters)
{
string filename = withPrivate ? "key.json" : "key.public.json";
keyParameters = default(RSAParameters);
if (Directory.Exists(filePath) == false) return false;
keyParameters = JsonConvert.DeserializeObject<RSAParameters>(File.ReadAllText(Path.Combine(filePath, filename)));
return true;
}
/// <summary>
/// 生成并保存 RSA 公钥与私钥
/// </summary>
/// <param name="filePath">存放密钥的文件夹路径</param>
/// <returns></returns>
public static RSAParameters GenerateAndSaveKey(string filePath)
{
RSAParameters publicKeys, privateKeys;
using (var rsa = new RSACryptoServiceProvider(2048))
{
try
{
privateKeys = rsa.ExportParameters(true);
publicKeys = rsa.ExportParameters(false);
}
finally
{
rsa.PersistKeyInCsp = false;
}
}
File.WriteAllText(Path.Combine(filePath, "key.json"), JsonConvert.SerializeObject(privateKeys));
File.WriteAllText(Path.Combine(filePath, "key.public.json"), JsonConvert.SerializeObject(publicKeys));
return privateKeys;
}
}
}
这个工具类能够帮助我们生成 RSA 密钥,并把生成的私钥跟公钥保存在两个文件中,还能从文件中读取密钥。
然后定义一个数据类,用来帮助我们在应用的各个地方获取加密相关的信息:
JWTTokenOptions.cs
using Microsoft.IdentityModel.Tokens;
namespace JwtUtils
{
public class JWTTokenOptions
{
public string Audience { get; set; }
public RsaSecurityKey Key { get; set; }
public SigningCredentials Credentials { get; set; }
public string Issuer { get; set; }
}
}
接下来在 Startup.cs 中配置 Jwt 的加密选项:
public void ConfigureServices(IServiceCollection services)
{
// 省略了其他的东西
// 从文件读取密钥
string keyDir = PlatformServices.Default.Application.ApplicationBasePath;
if (RSAUtils.TryGetKeyParameters(keyDir, true, out RSAParameters keyParams) == false)
{
keyParams = RSAUtils.GenerateAndSaveKey(keyDir);
}
_tokenOptions.Key = new RsaSecurityKey(keyParams);
_tokenOptions.Issuer = "TestIssuer"; // 签发者名称
_tokenOptions.Credentials = new SigningCredentials(_tokenOptions.Key, SecurityAlgorithms.RsaSha256Signature);
// 添加到 IoC 容器
services.AddSingleton(_tokenOptions);
services.AddMvc();
}
接下来创建一个控制器,用来提供签发 Token 的 API
TokenController.cs
namespace JwtIssuer.Controllers
{
[Route("api/[controller]")]
public class TokenController : Controller
{
private readonly JWTTokenOptions _tokenOptions;
private readonly AuthDbContext _dbContext;
public TokenController(JWTTokenOptions tokenOptions, AuthDbContext dbContext)
{
_tokenOptions = tokenOptions;
_dbContext = dbContext;
}
/// <summary>
/// 生成一个新的 Token
/// </summary>
/// <param name="user">用户信息实体</param>
/// <param name="expire">token 过期时间</param>
/// <param name="audience">Token 接收者</param>
/// <returns></returns>
private string CreateToken(User user, DateTime expire, string audience)
{
var handler = new JwtSecurityTokenHandler();
string jti = audience + user.Username + expire.GetMilliseconds();
jti = jti.GetMd5(); // Jwt 的一个参数,用来标识 Token
var claims = new[]
{
new Claim(ClaimTypes.Role, user.Role ?? string.Empty), // 添加角色信息
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString(), // 用户 Id ClaimValueTypes.Integer32),
new Claim("jti",jti,ClaimValueTypes.String) // jti,用来标识 token
};
ClaimsIdentity identity = new ClaimsIdentity(new GenericIdentity(user.Username, "TokenAuth"), claims);
var token = handler.CreateEncodedJwt(new SecurityTokenDescriptor
{
Issuer = "TestIssuer", // 指定 Token 签发者,也就是这个签发服务器的名称
Audience = audience, // 指定 Token 接收者
SigningCredentials = _tokenOptions.Credentials,
Subject = identity,
Expires = expire
});
return token;
}
/// <summary>
/// 用户登录
/// </summary>
/// <param name="user">用户登录信息</param>
/// <param name="audience">要访问的网站</param>
/// <returns></returns>
[HttpPost("{audience}")]
public IActionResult Post([FromBody]User user, string audience)
{
DateTime expire = DateTime.Now.AddDays(7);
// 在这里来验证用户的用户名、密码
var result = _dbContext.Users.First(u => u.Username == user.Username && u.Password == user.Password);
if (result == null)
{
return Json(new { Error = "用户名或密码错误" });
}
return Json(new { Token = CreateToken(result, expire, audience) });
}
}
}
现在,访问这个 API(http://localhost:port/api/token/TestAudience) 就可以获取用户的 Token 了
在应用服务器验证 Token
在 Startup.cs 中注册 Jwt 相关的服务:
public void ConfigureServices(IServiceCollection services)
{
// 省略了其他的内容
// 从文件读取密钥
string keyDir = PlatformServices.Default.Application.ApplicationBasePath;
if (RSAUtils.TryGetKeyParameters(keyDir, false, out RSAParameters keyparams) == false)
{
_tokenOptions.Key = default(RsaSecurityKey);
}
else
{
_tokenOptions.Key = new RsaSecurityKey(keyparams);
}
_tokenOptions.Issuer = "TestIssuer"; // 设置签发者
_tokenOptions.Audience = "TestAudience"; // 设置签收者,也就是这个应用服务器的名称
_tokenOptions.Credentials = new SigningCredentials(_tokenOptions.Key, SecurityAlgorithms.RsaSha256Signature);
services.AddAuthorization(auth =>
{
auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build());
});
// Add framework services.
services.AddMvc();
}
然后在 Startup.cs 添加 Jwt 认证中间件:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// 省略了其他的内容
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKey = _tokenOptions.Key,
ValidAudience = _tokenOptions.Audience, // 设置接收者必须是 TestAudience
ValidIssuer = _tokenOptions.Issuer, // 设置签发者必须是 TestIssuer
ValidateLifetime = true
}
});
}
接着随便创建一个 API 控制器
namespace JwtAudience.Controllers
{
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
[Authorize]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
}
首先编译一下应用服务器,但是不要急着运行。因为应用服务器验证 Token 是需要公钥的,所以现在去之前的签发服务器的 build 目录
可以看到生成了两个json文件,将其中的 key.public.json 拷贝到应用服务器的对应的目录下面,然后运行应用服务器。
如果我们直接访问应用服务器的 API,就会被挡在外面:
所以现在去把之前拿到的 token 复制出来,然后给这个请求加个请求头——Authorization
值是 Bearer 你的Token
这样,基本的身份验证就完成了~
有兴趣的话还可以把这个 Token 放在前面提到的用来调试 Jwt 网站上,我的 Token 的解析结果是:
这里面的 iss 指的就是签发者,aud 指的是接收者,对于我们的应用服务器来说,这两个参数错了任意一个都将无法通过验证(这里就不演示了,等会儿会有测试代码~)
真的足够安全?
至此,我们已经把 Jwt 的身份认证基本实现了,但是仔细想想,却发现存在一个很严重的问题————用户的 Token 在过期时间之内根本无法手动设置失效,随之而来的还有重放攻击等等问题!
Jwt官方也没有提供很好的应对方法,现在就只有一条路可以走,就是把失效的 Token 加入黑名单。只要能够让 Token 失效,之后应对这些安全问题就只是策略上的选择。
在 Jwt 的官方说明中,jti
这个参数就是用来标识 Token 的。所以,让一个 Token 失效只需要把这个 Token 中的 jti
加入应用服务器的数据库的黑名单就好了。
得益于微软对 Identity 良好的设计,我们可以很容易的拓展默认的 Jwt 认证规则
首先创建一个 ValidJtiRequirement 类
public class ValidJtiRequirement : IAuthorizationRequirement
{
}
嗯,他的结构就是这么简单。。。
然后创建一个用来验证这个 Requirement 的 ValidJtiHandler
public class ValidJtiHandler : AuthorizationHandler<ValidJtiRequirement>
{
private readonly AudienceDbContext _dbContext;
public ValidJtiHandler(AudienceDbContext dbContext)
{
_dbContext = dbContext;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ValidJtiRequirement requirement)
{
// 检查 Jti 是否存在
var jti = context.User.FindFirst("jti")?.Value;
if (jti == null)
{
context.Fail(); // 显式的声明验证失败
return Task.CompletedTask;
}
// 检查 jti 是否在黑名单
var tokenExists = _dbContext.BlackRecords.Any(r => r.Jti == jti);
if (tokenExists)
{
context.Fail();
}
else
{
context.Succeed(requirement); // 显式的声明验证成功
}
return Task.CompletedTask;
}
}
最后,稍微的修改一下注册服务时的代码
services.AddAuthorization(auth =>
{
auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.AddRequirements(new ValidJtiRequirement()) // 添加上面的验证要求
.Build());
});
// 注册验证要求的处理器,可通过这种方式对同一种要求添加多种验证
services.AddSingleton<IAuthorizationHandler, ValidJtiHandler>();
最后再来提供一个使 Token 失效的 API
namespace JwtAudience.Controllers
{
[Route("api/[controller]")]
public class TokenController : Controller
{
private readonly AudienceDbContext _dbContext;
public TokenController(AudienceDbContext dbContext)
{
_dbContext = dbContext;
}
[HttpGet]
public IActionResult Get() => Json(_dbContext.BlackRecords);
/// <summary>
/// 使用户的 Token 失效
/// </summary>
/// <returns></returns>
[Authorize("Bearer")]
[HttpDelete]
public IActionResult Delete()
{
// 从 payload 中提取 jti 字段
var jti = User.FindFirst("jti")?.Value;
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (jti == null)
{
HttpContext.Response.StatusCode = 400;
return Json(new { Result = false });
}
// 把这个 jti 加入数据库
_dbContext.BlackRecords.Add(new BlackRecord { Jti = jti, UserId = userId });
_dbContext.SaveChanges();
return Json(new {Result = true});
}
}
}
这里需要注意的是,因为拓展了默认的验证策略,所以需要在 Authorize
这个特性钦定使用 Bearer
策略:
[Authorize("Bearer")]
但是这样就容易在编码的时候出现拼写错误,所以来创建一个继承自这个特性的BearerAuthorize
类。
namespace JwtAudience
{
/// <summary>
/// Jwt 验证
/// </summary>
public class BearerAuthorizeAttribute : AuthorizeAttribute
{
public BearerAuthorizeAttribute() : base("Bearer") { }
}
}
现在我们就可以使用[BearerAuthorize]
来替代[Authorize]
至此,使 token 失效的能力就具备了。
然后附带一份测试代码,用来检验认证过程是否符合我们的预期:
https://coding.net/u/zeeko/p/JwtApplication/git/blob/master/Test/Test.cs
升级到 Asp.Net Core 2.0 (2017/08/29)
花了一天时间来把项目升级到 2.0,并不是因为 API 变化很大,而是之前的 bug 有些多,修起来有些慢。
首先要升级 Program.cs 里面的 Main
函数:
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
看起来更简短了一些。
接下来升级认证配置,按照官方的说明,所有的 app.UseXxxAuthentication
方法都变成了 service.AddAuthentication(XxxSchema).AddXxx()
,所以改动不是很大:
JwtIssuer/Startup.cs/ConfigureServices
services.AddAuthentication().AddJwtBearer(jwtOptions =>
{
jwtOptions.TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKey = _tokenOptions.Key,
ValidAudience = _tokenOptions.Audience,
ValidIssuer = _tokenOptions.Issuer,
ValidateLifetime = true
};
});
JwtAudience/Startup.cs/ConfigureServices
services.AddAuthentication().AddJwtBearer(jwtOptions =>
{
jwtOptions.TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKey = _tokenOptions.Key,
ValidAudience = _tokenOptions.Audience,
ValidIssuer = _tokenOptions.Issuer,
ValidateLifetime = true
};
});
至此,需要升级的地方就修改好了,但是到目前为止还是无法运行,因为有些在 1.0 里面没有严格检验的地方开始报错了。
第一个地方是 ValidJtiHandler
,之前在注册的时候,生命周期选的是单例并没有报错,但是因为这个类依赖了一个生命周期是 Scoped 的对象—— AudienceDbContext
,这会引发一个异常,解决方法是把 ValidJtiHandler
也改成 Scoped:
services.AddScoped<IAuthorizationHandler, ValidJtiHandler>();
第二个地方是 RSAParameters
在 2.x 里面,它的私钥属性不能被 Json.Net 序列化,解决方法也很简单,加一个对应的类似 DTO 的类:
class RsaParameterStorage
{
public byte[] D { get; set; }
public byte[] DP { get; set; }
public byte[] DQ { get; set; }
public byte[] Exponent { get; set; }
public byte[] InverseQ { get; set; }
public byte[] Modulus { get; set; }
public byte[] P { get; set; }
public byte[] Q { get; set; }
}
然后在导出私钥前将 RSAParameters
映射成一个 RsaParameterStorage
对象,然后使用 Json.Net 来序列化,映射使用的是我自己写的一个 Mapper(所以升级项目只花了几十分钟,调教 Mapper 花了一天),代码更改如下:
// 转换成 json 字符串
static string ToJsonString(this RSAParameters parameters)
{
var content = parameters.Map().To<RsaParameterStorage>();
return JsonConvert.SerializeObject(content);
}
// 从文件中读取
keyParameters = JsonConvert.DeserializeObject<RsaParameterStorage>(File.ReadAllText(filePath)).Map().To<RSAParameters>();
ASP.NET Core 使用 JWT 搭建分布式无状态身份验证系统的更多相关文章
- 教你如何实现微信小程序与.net core应用服务端的无状态身份验证
随着.net core2的发布,越来越多人使用.net core2开发各种应用服务端,下面我就结合自己最近开发的一款小程序,给大家分享下,怎么使用小程序登录后,小程序与服务端交互的权限控制. .net ...
- 在ASP.NET Core 2.0中使用Facebook进行身份验证
已经很久没有更新自己的技术博客了,自从上个月末来到天津之后把家安顿好,这个月月初开始找工作,由于以前是做.NET开发的,所以找的还是.NET工作,但是天津这边大多还是针对to B(企业)进行定制开发的 ...
- ASP.NET Core的无状态身份认证框架IdentityServer4
Identity Server 4是IdentityServer的最新版本,它是流行的OpenID Connect和OAuth Framework for .NET,为ASP.NET Core和.NE ...
- ASP.NET Core 基于JWT的认证(一)
ASP.NET Core 基于JWT的认证(一) Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计 ...
- asp.net core 集成JWT(一)
[什么是JWT] JSON Web Token(JWT)是目前最流行的跨域身份验证解决方案. JWT的官网地址:https://jwt.io/ 通俗地来讲,JWT是能代表用户身份的令牌,可以使用JWT ...
- asp.net core 集成JWT(二)token的强制失效,基于策略模式细化api权限
[前言] 上一篇我们介绍了什么是JWT,以及如何在asp.net core api项目中集成JWT权限认证.传送门:https://www.cnblogs.com/7tiny/p/11012035.h ...
- ASP.NET Core 使用 Redis 实现分布式缓存:Docker、IDistributedCache、StackExchangeRedis
ASP.NET Core 使用 Redis 实现分布式缓存:Docker.IDistributedCache.StackExchangeRedis 前提:一台 Linux 服务器.已安装 Docker ...
- ASP.NET Core 基于JWT的认证(二)
ASP.NET Core 基于JWT的认证(二) 上一节我们对 Jwt 的一些基础知识进行了一个简单的介绍,这一节我们将详细的讲解,本次我们将详细的介绍一下 Jwt在 .Net Core 上的实际运用 ...
- Asp.Net Core基于JWT认证的数据接口网关Demo
近日,应一位朋友的邀请写了个Asp.Net Core基于JWT认证的数据接口网关Demo.朋友自己开了个公司,接到的一个升级项目,客户要求用Aps.Net Core做数据网关服务且基于JWT认证实现对 ...
随机推荐
- nginx+lua安装配置
1.选定源码目录选定目录 /usr/local/ cd /usr/local/ 2.安装PCRE库cd /usr/local/wget ftp://ftp.csx.cam.ac.uk/pub/soft ...
- 自己开发图表插件,脱离echart
前言 由于公司业务需要做一些图标来展示一些数据,之前都是用百度的echart.js.这次放弃使用它转而自己开发是有几个原因1.echart文件太大,有些功能用不到2.echart样式不易扩展3.需求简 ...
- react+webpack开发环境配置
react是目前非常热门的前端框架,提倡组件化开发.所谓的组件,简单理解,就是一个独立的页面部件(包括页面模版,样式,逻辑等),它是一个独立的整体. webpack,是一个模块打包工具,其主要功能,就 ...
- dotweb——go语言的一个微型web框架(二)启动dotweb
以上的代码截图表示启动一个dotweb服务,在浏览器里输入127.0.0.1:8080,将会得到一个"index"的页面. app := dotweb.New() dotweb.N ...
- C++ 拷贝构造函数、拷贝赋值运算符、析构函数
每一次都会忘,做个笔记吧.想到哪里写到哪里. 拷贝构造函数 第一个参数必须是自身类类型的引用,且任何额外参数都有默认值.(为什么必须是引用?见后解释) 合成拷贝构造函数:如果我们没有为一个类定义拷贝构 ...
- 【Java基础 】Java7 NIO Files,Path 操作文件
从Java1.0到1.3,我们在开发需要I/O支持的应用时,要面临以下问题: 没有数据缓冲区或通道的概念,开发人员要编程处理很多底层细节 I/O操作会被阻塞,扩展能力有限 所支持的字符集编码有限,需要 ...
- Unity 3D Framework Designing(8)——使用ServiceLocator实现对象的注入
对象的 『注入』 是企业级软件开发经常听到的术语.如果你是一个 Java 程序员,一定对注入有着深刻的映像.不管是SSH框架还是SSM框架,Spring 全家桶永远是绕不过去的弯.通过依赖注入,可以有 ...
- css远距离链接
远距离链接主要运用了hover伪类,但是运用了两次 <!DOCTYPE html> <html lang="en"> <head> <me ...
- IOS开发创建开发证书及发布App应用(一)——流程说明
之前在自己做的博客网站上面发布了这个系列的文章,当时还是不错的,帮助了很多跟我一样的新手朋友,不过由于服务器出现问题,丢失了一年了,现在终于找到了,所以发到博客园给大家共享一下,也是为我自己做个参考 ...
- [认证授权] 2.OAuth2(续) & JSON Web Token
0. RFC6749还有哪些可以完善的? 0.1. 撤销Token 在上篇[认证授权] 1.OAuth2授权中介绍到了OAuth2可以帮我们解决第三方Client访问受保护资源的问题,但是只提供了如何 ...