grant_type
- client_credentials 客户端凭证
- password 密码模式 用于资源所有者密码凭据
- token 隐藏式 、 简化式 简化模式又称为隐式授权码模式,它是授权码模式的一个简化版本
- authorization_code 授权码
A. 第三方程序向资源拥有者(用户)发送授权请求,这个过程既可以通过客户端直接向用户请求,也可以通过授权服务器作为中介来完成请求。(注:对于授权请求这个概念相当于用户登录,应用程序可以直接显示一个登录页面,也可以跳转到验证服务器的统一登录页面)
B. 用户将授权相关信息“提交”给第三方程序,在OAuth中有4种不同的权限授予方式,每种方式需要的数据不同,如基于用户密码的授权方式就需要用户名和密码。
C. 第三方程序将用户的授权信息提交到授权服务器,请求一个Access Token。
D. 授权服务器验证完成用户的授权信息后,将Access Token发放到第三方程序。
E. 第三方程序携带Access Token访问被保护的资源。
F. 资源服务器验证Access Token有效后,将资源返回到第三方程序。
● Authorization Code(授权码模式):该模式的核心是客户端通过一个授权码来向授权服务器申请Access Token。是一种基于重定向的授权模式,授权服务器作为用户和第三方应用(Client)的中介,当用户访问第三方应用是,第三方应用跳转到授权服务器引导用户完成身份验证,生成Authorization Code并转交到第三方应用,以便于第三方应用根据这个授权码完成后续的Access Token获取。
● Implicit(简化模式):简化模式是一种简化的授权码模式,授权码模式在首次访问第三方应用时跳转到授权服务器进行身份验证返回授权码,而简化模式在跳转到授权服务器后直接返回Access Token,这种模式减少了获取Access Token的请求次数。
● Resource Owner Password Credentials(用户密码模式):通过资源拥有者(用户)的用户名和密码来直接获取Access Token的一种方法,这种方法要求第三方应用(Client)是高度可信任的,并且其它授权方式不可用的情况下使用。
● Client Credentials(客户端模式):该模式是通过第三方应用(Client)发送一个自己的凭证到授权服务器获得Access Token,这种模式的使用要求该Client已经被授权服务器管理并限制其对被保护资源的访问范围。另外这种模式下Client应该就是一个资源拥有者(用户),如微服务程序。
》》》
四个模式中,只有【客户端模式】不需要用户输入用户名和密码,因为 客户端模式,不是用户名义请求的,是客户端本身名义请求的,所以需要后台提供 client_id 和 client_secret,
根据这个两个去认证服务器【Authorization server】 获取access_token.
》》适用于没有前端的命令行应用,即在命令行下请求令牌。一般用来提供给我们完全信任的服务器端服务。
如果是第一方应用(自己开发的应用)一般我们都认为要安全一些,因为不会故意的去泄露访问resource的access token, 所以一般第一方应用我们可以使用简单的【密码模式】, 这种节省了通过code去交换access token这一步骤(实际上就是节省了一次网络请求来回),直接通过用户名,密码去获取access token。而第三方应用我们需要采用更加安全的 【授权码模式】和【简单模式】
【授权码模式】和【简单模式】、【密码模式】 都需要用户录入用户名和密码, 但【授权码模式】和【简单模式】 是认证服务器【authorization server】提供的界面录入的,【密码模式】是客户端提供的界面录入的 ,所以认证服务器提供的界面更加安全些
【简单模式】是没有授权码【code】和刷新token【refresh_code】
>>>如果有人很容易的拿到code 或 refresh token,那么就基本上可以随意随时的去访问你的resource了,因为他可以不断的通过refresh token 去刷新access token。 而为什么第三方的SPA使用的是implicit flow 而第三方的Native App却使用的是authorization code flow? 理论上第三方应用都应该使用【授权码模式】,但是如果你仔细看下,【简化模式】中是没有code 和 refresh token的,而SPA应用(本质上是web,需要通过浏览器的)更加容易去暴露code 和 refresh token, 所以才在第三方的SPA应用中使用了【简单模式】,而只给了access token,
》》》
安装四个包
客户端模式 又称简化模式
客户端模式(Client Credentials Grant)指客户端以自己的名义,而不是以用户的名义,向"服务提供商"进行 授权。
适用于没有前端的命令行应用,即在命令行下请求令牌。一般用来提供给我们完全信任的服务器端服务。
》》》》 用webapi 做案例 ,新建项目 【webapi】
》》》删除自动的Global.asax, 这个文件是程序的入口,删除之后要创建一个 OWIN Startup 命名为 Startup。
using System;
using System.Threading.Tasks;
using Microsoft.Owin;
using Owin;
using System.Web.Http;
using WebApplication3.App_Start;
using Microsoft.Owin.Cors;[assembly: OwinStartup(typeof(WebApplication3.Startup))]namespace WebApplication3
{public class Startup{public void Configuration(IAppBuilder app){ HttpConfiguration configuration = new HttpConfiguration();//注册Swagger//SwaggerConfig.Register(configuration);//注册WebAPIWebApiConfig.Register(configuration);//注册授权服务AuthorizationConfig.Register(app);//注册Json的数据展示格式JsonFormatConfig.Register(configuration);//跨域配置app.UseCors(CorsOptions.AllowAll);app.UseWebApi(configuration);}}
}
》》》 新建类 AuthorizationConfig
using Microsoft.Owin.Security.OAuth;
using Owin;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using WebApplication3.Provider;namespace WebApplication3.App_Start
{public class AuthorizationConfig{public static void Register(Owin.IAppBuilder app){OAuthAuthorizationServerOptions options = new OAuthAuthorizationServerOptions(){AllowInsecureHttp = true,//允许http而非https访问TokenEndpointPath = new Microsoft.Owin.PathString(value: "/access_token"),//Token 请求地址AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),//Token的过期时间Provider = new OpenAuthorizationServerProvider(),//生成Token 配置RefreshTokenProvider = new OpenRefreshTokenProvider(),//生成RefreshToken的配置};app.UseOAuthAuthorizationServer(options);app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());}}
}
》》》新建类 JsonFormatConfig
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;namespace WebApplication3.App_Start
{public class JsonFormatConfig{public static void Register(HttpConfiguration configuration){configuration.Formatters.JsonFormatter.SerializerSettings = new Newtonsoft.Json.JsonSerializerSettings(){ContractResolver = new CamelCasePropertyNamesContractResolver(),//小驼峰命名DateFormatString = "yyyy-MM-dd HH:mm:ss" //日期格式化};}}
}
》》》新建文件夹Provider
》》》新建类 OpenAuthorizationServerProvider 生成 access_token
using Microsoft.Owin.Security.OAuth;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Threading.Tasks;
using System.Security.Claims;
using System.Security.Principal;namespace WebApplication3.Provider
{/// <summary>/// 授权服务器配置/// </summary>public class OpenAuthorizationServerProvider:OAuthAuthorizationServerProvider{/// <summary>/// 验证客户端信息/// </summary>/// <param name="context"></param>/// <returns></returns>public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context){//如果是刷新token,且不要验证。if (context.Parameters.Get("refresh_token") == null){//获取clientId,ClientSecretstring clientId, clientSecret;if (!context.TryGetBasicCredentials(out clientId, out clientSecret)){context.TryGetFormCredentials(out clientId, out clientSecret);}//对客户端Id和客户端密码进行校验 是与数据库进行比对if (clientId == "zen" && clientSecret == "123456"){//通过客户端认证context.Validated(clientId);}else{context.Rejected();}}else{ // 通过客户端认证context.Validated();}return base.ValidateClientAuthentication(context);}/// <summary>/// 生成客户端模式Access_Token/// 还需要将对应的客户端信息存储在web中/// </summary>/// <param name="context"></param>/// <returns></returns>public override Task GrantClientCredentials(OAuthGrantClientCredentialsContext context){//以下即为认证成功//var identity = new ClaimsIdentity(context.Options.AuthenticationType);ClaimsIdentity identity = new GenericIdentity( name: context.ClientId, type: OAuthDefaults.AuthenticationType);//通过查数据库,得到一些用户的信息int userid = 13;string role = "管理员";string scope = "权限1,权限2"; identity.AddClaim(new Claim("userid", userid.ToString()));identity.AddClaim(new Claim("role", role));identity.AddClaim(new Claim("scope", scope));context.Validated(identity);return base.GrantClientCredentials(context);}}
}
》》》新建类 OpenRefreshTokenProvider 刷新token
using Microsoft.Owin.Security.Infrastructure;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Web;namespace WebApplication3.Provider
{/// <summary>/// 刷新Token配置/// </summary>public class OpenRefreshTokenProvider:AuthenticationTokenProvider{private static ConcurrentDictionary<string, string> _refreshTokens = new ConcurrentDictionary<string, string>();/// <summary>/// 生成Refresh_token/// </summary>/// <param name="context"></param>public override void Create(AuthenticationTokenCreateContext context){context.Ticket.Properties.IssuedUtc = DateTime.UtcNow;context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddDays(30);context.SetToken(tokenValue:Guid.NewGuid().ToString(format:"N")+Guid.NewGuid().ToString(format:"N"));_refreshTokens[context.Token] = context.SerializeTicket();//base.Create(context); }/// <summary>/// 使用Refresh_token 请求Access_Token/// </summary>/// <param name="context"></param>public override void Receive(AuthenticationTokenReceiveContext context){string value;if (_refreshTokens.TryRemove(context.Token,out value)){context.DeserializeTicket(value);}base.Receive(context);}}
}
》》》 新建控制器
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Web;
using System.Web.Http;namespace WebApplication3.Controllers
{public class HomeController : ApiController{// GET: Home[Authorize]public string Get(){var clientId = HttpContext.Current.User.Identity.Name;Dictionary<string,string> lst = new Dictionary<string,string>();foreach (Claim item in (this.User.Identity as ClaimsIdentity).Claims){lst.Add(item.Type,item.Value);}return "我是Get方法";}// GET: Homepublic string Get(int id){return $"这是参数为{id}的Get方法";}}
}
》》》测试 用postman
》》客户端凭证 走 GrantClientCredentials方法
ValidateAuthorizeRequest 》》》授权码验证
ValidateClientAuthentication 》》》客户端模式验证
ValidateClientRedirectUri
ValidateTokenRequest 》》》验证令牌请求, 简化模式、隐藏式模式
密码模式(Password Grant):
用户将用户名和密码发送给第三方应用程序,第三方应用程序直接向授权服务器请求访问令牌。
如果你高度信任某个应用,RFC 6749 也允许用户把用户名和密码,直接告诉该应用。该应用就使用你的密码,申请令牌,这种方式称为"密码式"(password)。
在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分,或者由一个著名公司出品。而授权服务器只有在其他授权模式无法执行的情况下,才能考虑使用这种模式。
适用场景:公司搭建的授权服务器
(A)用户向客户端提供用户名和密码。
(B)客户端将用户名和密码发给认证服务器,向后者请求令牌。
(C)认证服务器确认无误后,向客户端提供访问令牌。
其它都一样,修改OpenAuthorizationServerProvider 即可
using Microsoft.Owin.Security.OAuth;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Threading.Tasks;
using System.Security.Claims;
using System.Security.Principal;namespace WebApplication3.Provider
{/// <summary>/// 授权服务器配置/// </summary>public class OpenAuthorizationServerProvider:OAuthAuthorizationServerProvider{public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context){//获取用户传入的用户名和密码string UserName = context.UserName;string Password = context.Password;//通过查数据库,判断用户名和密码是否正确//以下只是一个示例,用户名必须以test开头if (!UserName.StartsWith("test")){context.SetError("invalid_grant", "用户名或密码不正确");return;}//以下即为认证成功//通过查数据库,得到一些用户的信息int userid = 13;string role = "管理员";string scope = "权限1,权限2";var identity = new ClaimsIdentity(context.Options.AuthenticationType);identity.AddClaim(new Claim("userid", userid.ToString()));identity.AddClaim(new Claim("role", role));identity.AddClaim(new Claim("scope", scope));context.Validated(identity);}/// <summary>/// 验证客户端信息/// </summary>/// <param name="context"></param>/// <returns></returns>public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context){可以验证 ClientID、ClientSecret,或不验证//从上下文中获取ClientID和ClientSecretcontext.TryGetFormCredentials(out string clientId, out string clientSecret);//非法客户端if (clientId == null || !clientId.StartsWith("AAA")){context.SetError("invalid_clientId", "客户端没有授权");return Task.FromResult<object>(null);}//如果不验证可以直接执行下面的 验证通过context.Validated(); } }
}
简化模式
有些 Web 应用是纯前端应用,没有后端。必须将令牌储存在前端。RFC 6749 就规定了第二种方式,允许直接向前端颁发令牌,这种方式没有授权码这个中间步骤,所以称为(授权码)“隐藏式”(implicit)
简化模式不通过第三方应用程序的服务器,直接在浏览器中向授权服务器申请令牌,跳过了"授权码"这个步骤,所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。所以 不会触发 ValidateClientAuthentication
这种方式把令牌直接传给前端,是很不安全的。因此,只能用于一些安全要求不高的场景,并且令牌的有效期必须非常短,通常就是会话期间(session)有效,浏览器关掉,令牌就失效了。
using Microsoft.Owin.Security.OAuth;
using Owin;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using WebApplication3.Provider;namespace WebApplication3.App_Start
{public class AuthorizationConfig{public static void Register(Owin.IAppBuilder app){OAuthAuthorizationServerOptions options = new OAuthAuthorizationServerOptions(){AllowInsecureHttp = true,//允许http而非https访问AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,//激活授权码模式TokenEndpointPath = new Microsoft.Owin.PathString(value: "/token"),//访问host/token获取AccessTokenAuthorizeEndpointPath = new Microsoft.Owin.PathString("/auth"),//访问host/auth获取授权码AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),//AccessToken在30分钟后过期Provider = new OpenAuthorizationServerProvider(),//AccessToken的提供类// 简化模式 省略下面代码 简化模式又称为隐式授权码模式,它是授权码模式的一个简化版本//AuthorizationCodeProvider = new OpenAuthorizationCodeProvider(),//授权码的提供类 RefreshTokenProvider = new OpenRefreshTokenProvider(),//生成RefreshToken的配置};app.UseOAuthAuthorizationServer(options);app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());}}
}
using Microsoft.Owin.Security.OAuth;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Threading.Tasks;
using System.Security.Claims;
using System.Security.Principal;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Infrastructure;namespace WebApplication3.Provider
{/// <summary>/// 授权服务器配置/// </summary>public class OpenAuthorizationServerProvider : OAuthAuthorizationServerProvider{/// <summary>/// 验证重定向URI是否合法/// 授权码模式、简化模式 都会触发/// </summary>/// <param name="context"></param>/// <returns></returns>public override async Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context){string url = context.RedirectUri;context.Validated(context.RedirectUri);}/// <summary>/// 验证请求信息是否合法/// 授权码模式、简化模式 都会触发/// </summary>/// <param name="context"></param>/// <returns></returns>public override async Task ValidateAuthorizeRequest(OAuthValidateAuthorizeRequestContext context){if (context.AuthorizeRequest.ClientId.StartsWith("zen")){context.Validated();}else{context.Rejected();}}/// <summary>/// 完成认证,跳转到重定向URI/// 授权码模式、简化模式 都会触发/// </summary>/// <param name="context"></param>/// <returns></returns>public override async Task AuthorizeEndpoint(OAuthAuthorizeEndpointContext context){var identity = new ClaimsIdentity("Bearer");context.OwinContext.Authentication.SignIn(identity);context.RequestCompleted();} }
}
》》访问
》》直接跳转 access_token 是通过锚链接的
授权码模式 :
一、是获取授权码,
二、是获取AccessToken
在获取授权码时,我们需要请求host/auth这个地址,输入的参数有以下要求:
(1)grant_type,必须为authorization_code。
(2)response_type,必须为code。
(3)client_id,客户端ID。
(4)redirect_uri,重定向地址,如为http://abc.com/,
则请求授权码完成后,将会重定向到:http://abc.com/code=[授权码]。
(5)scope,授权范围,可选。
(6)state,客户端状态,可选。
》》》Startup 同上
》》》JsonFormatConfig 同上
》》》api控制器同上
》》》OpenRefreshTokenProvider 刷新token 同上
》》》 AuthorizationConfig 类
using Microsoft.Owin.Security.OAuth;
using Owin;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using WebApplication3.Provider;namespace WebApplication3.App_Start
{public class AuthorizationConfig{public static void Register(Owin.IAppBuilder app){OAuthAuthorizationServerOptions options = new OAuthAuthorizationServerOptions(){AllowInsecureHttp = true,//允许http而非https访问AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,//激活授权码模式TokenEndpointPath = new Microsoft.Owin.PathString(value: "/token"),//访问host/token获取AccessTokenAuthorizeEndpointPath = new Microsoft.Owin.PathString("/auth"),//访问host/auth获取授权码AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),//AccessToken在30分钟后过期Provider = new OpenAuthorizationServerProvider(),//AccessToken的提供类// 简化模式 省略下面代码 简化模式又称为隐式授权码模式,它是授权码模式的一个简化版本AuthorizationCodeProvider = new OpenAuthorizationCodeProvider(),//授权码的提供类 RefreshTokenProvider = new OpenRefreshTokenProvider(),//生成RefreshToken的配置};app.UseOAuthAuthorizationServer(options);app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());}}
}
》》》OpenAuthorizationServerProvider
using Microsoft.Owin.Security.OAuth;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Threading.Tasks;
using System.Security.Claims;
using System.Security.Principal;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Infrastructure;namespace WebApplication3.Provider
{/// <summary>/// 授权服务器配置/// </summary>public class OpenAuthorizationServerProvider : OAuthAuthorizationServerProvider{/// <summary>/// 验证重定向URI是否合法/// 授权码模式、简化模式 都会触发/// </summary>/// <param name="context"></param>/// <returns></returns>public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context){string url = context.RedirectUri;context.Validated(context.RedirectUri);return base.ValidateClientRedirectUri(context);}/// <summary>/// 验证请求信息是否合法/// 授权码模式、简化模式 都会触发/// </summary>/// <param name="context"></param>/// <returns></returns>public override async Task ValidateAuthorizeRequest(OAuthValidateAuthorizeRequestContext context){if (context.AuthorizeRequest.ClientId.StartsWith("zen")){context.Validated();}else{context.Rejected();}}/// <summary>/// 完成认证,跳转到重定向URI/// 授权码模式、简化模式 都会触发/// </summary>/// <param name="context"></param>/// <returns></returns>public override async Task AuthorizeEndpoint(OAuthAuthorizeEndpointContext context){//授权码模式var redirectUri = context.Request.Query["redirect_uri"];var clientId = context.Request.Query["client_id"];var identity = new ClaimsIdentity(new GenericIdentity(clientId, OAuthDefaults.AuthenticationType)); var authorizeCodeContext = new AuthenticationTokenCreateContext(context.OwinContext,context.Options.AuthorizationCodeFormat,new AuthenticationTicket(identity,new AuthenticationProperties(new Dictionary<string, string>{{"client_id", clientId},{"redirect_uri", redirectUri}}){IssuedUtc = DateTimeOffset.UtcNow,ExpiresUtc = DateTimeOffset.UtcNow.Add(context.Options.AccessTokenExpireTimeSpan)}));await context.Options.AuthorizationCodeProvider.CreateAsync(authorizeCodeContext);context.Response.Write(Uri.EscapeDataString(authorizeCodeContext.Token));//为了测试方便,直接打印出code//正常使用时是把code加在重定向网址后面//context.Response.Redirect(redirectUri + "?code=" + Uri.EscapeDataString(authorizeCodeContext.Token));context.RequestCompleted();}/// <summary>/// 验证客户端///// </summary>/// <param name="context"></param>/// <returns></returns>public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context){string cd = context.ClientId;string clientId, clientSecret;context.TryGetFormCredentials(out clientId, out clientSecret);if (!clientId.StartsWith("zen")){context.SetError("invalid_client", "未授权的客户端");return Task.FromResult<object>(null); ;}context.Validated();return Task.FromResult<object>(null);}/// <summary>/// 生成客户端模式Access_Token/// 还需要将对应的客户端信息存储在web中/// </summary>/// <param name="context"></param>/// <returns></returns>public override Task GrantClientCredentials(OAuthGrantClientCredentialsContext context){//以下即为认证成功return base.GrantClientCredentials(context);}public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context){// 以下即为认证成功//var identity = new ClaimsIdentity(context.Options.AuthenticationType);ClaimsIdentity identity = new GenericIdentity(name: context.ClientId, type: OAuthDefaults.AuthenticationType);//通过查数据库,得到一些用户的信息int userid = 13;string role = "管理员";string scope = "权限1,权限2";identity.AddClaim(new Claim("userid", userid.ToString()));identity.AddClaim(new Claim("role", role));identity.AddClaim(new Claim("scope", scope));context.Validated(identity);return base.GrantResourceOwnerCredentials(context);}public override async Task ValidateTokenRequest(OAuthValidateTokenRequestContext context){if (context.TokenRequest.IsAuthorizationCodeGrantType){context.Validated();}else{context.Rejected();}}}
}
》》》 OpenAuthorizationCodeProvider
using Microsoft.Owin.Security.Infrastructure;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;namespace WebApplication3.Provider
{public class OpenAuthorizationCodeProvider : IAuthenticationTokenProvider{private Dictionary<string, string> codes = new Dictionary<string, string>();public void Create(AuthenticationTokenCreateContext context){string new_code = Guid.NewGuid().ToString("n");context.SetToken(new_code);//context.SerializeTicket() 生成tokencodes.Add(new_code, context.SerializeTicket());}public Task CreateAsync(AuthenticationTokenCreateContext context){Create(context);return Task.FromResult<object>(null);}public void Receive(AuthenticationTokenReceiveContext context){string code = context.Token;if (codes.ContainsKey(code)){string value = codes[code];codes.Remove(code);context.DeserializeTicket(value);}}public Task ReceiveAsync(AuthenticationTokenReceiveContext context){Receive(context);return Task.FromResult<object>(null);}}
}
access_token
access_token不能暴露在浏览器那么该存放在哪?
重定向传回access_token会使安全保密性要求极高的访问令牌暴露在浏览器,增加访问令牌失窃风险。
在我看来,重定向携带的参数在URL上,http协议下重定向传回access_token的形式,是没有经过数据加密的,他会增加令牌失窃的风险。那么关于access_token存放在哪的问题,个人认为通过授权码以及客户端id和secret共同校验后获取的access_token,可以把access_token存放在localStorage中,localStorage虽然是永久存储,但是access_token会有一个有效期,有效期到了之后,即便access_token一直都存在但是有效期过后就无法访问到受保护资源。
》》》webstorage (sessionStorage和localStorage)
sessionStorage和localStorage区别
**注意: **不同浏览器无法共享localStorage或sessionStorage中的信息。
相同浏览器的不同页面间【相同域名和端口】可以共享相同的 localStorage,
但是不同页面或标签页间无法共享sessionStorage的信息。