.NET Core中JWT+OAuth2.0实现SSO,附完整源码(.NET6)

网友投稿 1265 2022-10-07

.NET Core中JWT+OAuth2.0实现SSO,附完整源码(.NET6)

.NET Core中JWT+OAuth2.0实现SSO,附完整源码(.NET6)

一、简介

单点登录(SingleSignOn,SSO)

指的是在多个应用系统中,只需登录一次,就可以访问其他相互信任的应用系统。

JWT

json Web Token,这里不详细描述,简单说是一种认证机制。

OAuth2.0

OAuth2.0是一个认证流程,一共有四种方式,这里用的是最常用的授权码方式,流程为:

1、系统A向认证中心先获取一个授权码code。

2、系统A通过授权码code获取 token,refresh_token,expiry_time,scope。

token:系统A向认证方获取资源请求时带上的token。

refresh_token:token的有效期比较短,用来刷新token用。

expiry_time:token过期时间。

这里的SSO都是公司自己的系统,都是获取用户信息,所以这个为空,第三方需要接入我们的登录时才需要scope来做资源权限判断。

二、实现目标

1、一处登录,全部登录

流程图为:

1、浏览器访问A系统,发现A系统未登录,跳转到统一登录中心(SSO),带上A系统的回调地址,

地址为:token,实际项目应该存到数据库。

{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "AppSetting": { "appHSSettings": [ { "domain": " "clientId": "web1", "clientSecret": "Nu4Ohg8mfpPnNxnXu53W4g0yWLqF0mX2" }, { "domain": " "clientId": "web2", "clientSecret": "pQeP5X9wejpFfQGgSjyWB8iFdLDGHEV8" } ] } }

domain:接入系统的域名,可以用来校验请求来源是否合法。

clientId:接入系统标识,请求token时传进来识别是哪个系统。

clientSecret:接入系统密钥,用来生成对称加密的JWT。

建一个IJWTService定义JWT生成需要的方法

///

/// JWT服务接口 /// public interface IJWTService { /// /// 获取授权码 /// /// /// /// /// ResponseModel GetCode(string clientId, string userName, string password); /// /// 根据会话Code获取授权码 /// /// /// /// ResponseModel GetCodeBySessionCode(string clientId, string sessionCode); /// /// 根据授权码获取Token+RefreshToken /// /// /// Token+RefreshToken ResponseModel GetTokenWithRefresh(string authCode); /// /// 根据RefreshToken刷新Token /// /// /// /// string GetTokenByRefresh(string refreshToken, string clientId); }

建一个抽象类JWTBaseService加模板方法实现详细的逻辑

///

/// jwt服务 /// public abstract class JWTBaseService : IJWTService { protected readonly IOptions _appSettingOptions; protected readonly Cachelper _cachelper; public JWTBaseService(IOptions appSettingOptions, Cachelper cachelper) { _appSettingOptions = appSettingOptions; _cachelper = cachelper; } /// /// 获取授权码 /// /// /// /// /// public ResponseModel GetCode(string clientId, string userName, string password) { ResponseModel result = new ResponseModel(); string code = string.Empty; AppHSSetting appHSSetting = _appSettingOptions.Value.appHSSettings.Where(s => s.clientId == clientId).FirstOrDefault(); if (appHSSetting == null) { result.SetFail("应用不存在"); return result; } //真正项目这里查询数据库比较 if (!(userName == "admin" && password == "123456")) { result.SetFail("用户名或密码不正确"); return result; } //用户信息 CurrentUserModel currentUserModel = new CurrentUserModel { id = 101, account = "admin", name = "张三", mobile = "13800138000", role = "SuperAdmin" }; //生成授权码 code = Guid.NewGuid().ToString().Replace("-", "").ToUpper(); string key = $"AuthCode:{code}"; string appCachekey = $"AuthCodeClientId:{code}"; //缓存授权码 _cachelper.StringSet(key, currentUserModel, TimeSpan.FromMinutes(10)); //缓存授权码是哪个应用的 _cachelper.StringSet(appCachekey, appHSSetting.clientId, TimeSpan.FromMinutes(10)); //创建全局会话 string sessionCode = $"SessionCode:{code}"; SessionCodeUser sessionCodeUser = new SessionCodeUser { expiresTime = DateTime.Now.AddHours(1), currentUser = currentUserModel }; _cachelper.StringSet(sessionCode, currentUserModel, TimeSpan.FromDays(1)); //全局会话过期时间 string sessionExpiryKey = $"SessionExpiryKey:{code}"; DateTime sessionExpirTime = DateTime.Now.AddDays(1); _cachelper.StringSet(sessionExpiryKey, sessionExpirTime, TimeSpan.FromDays(1)); Console.WriteLine($"登录成功,全局会话code:{code}"); //缓存授权码取token时最长的有效时间 _cachelper.StringSet($"AuthCodeSessionTime:{code}", sessionExpirTime, TimeSpan.FromDays(1)); result.SetSuccess(code); return result; } /// /// 根据会话code获取授权码 /// /// /// /// public ResponseModel GetCodeBySessionCode(string clientId, string sessionCode) { ResponseModel result = new ResponseModel(); string code = string.Empty; AppHSSetting appHSSetting = _appSettingOptions.Value.appHSSettings.Where(s => s.clientId == clientId).FirstOrDefault(); if (appHSSetting == null) { result.SetFail("应用不存在"); return result; } string codeKey = $"SessionCode:{sessionCode}"; CurrentUserModel currentUserModel = _cachelper.StringGet(codeKey); if (currentUserModel == null) { return result.SetFail("会话不存在或已过期", string.Empty); } //生成授权码 code = Guid.NewGuid().ToString().Replace("-", "").ToUpper(); string key = $"AuthCode:{code}"; string appCachekey = $"AuthCodeClientId:{code}"; //缓存授权码 _cachelper.StringSet(key, currentUserModel, TimeSpan.FromMinutes(10)); //缓存授权码是哪个应用的 _cachelper.StringSet(appCachekey, appHSSetting.clientId, TimeSpan.FromMinutes(10)); //缓存授权码取token时最长的有效时间 DateTime expirTime = _cachelper.StringGet($"SessionExpiryKey:{sessionCode}"); _cachelper.StringSet($"AuthCodeSessionTime:{code}", expirTime, expirTime - DateTime.Now); result.SetSuccess(code); return result; } /// /// 根据刷新Token获取Token /// /// /// /// public string GetTokenByRefresh(string refreshToken, string clientId) { //刷新Token是否在缓存 CurrentUserModel currentUserModel = _cachelper.StringGet($"RefreshToken:{refreshToken}"); if(currentUserModel==null) { return String.Empty; } //刷新token过期时间 DateTime refreshTokenExpiry = _cachelper.StringGet($"RefreshTokenExpiry:{refreshToken}"); //token默认时间为600s double tokenExpiry = 600; //如果刷新token的过期时间不到600s了,token过期时间为刷新token的过期时间 if(refreshTokenExpiry>DateTime.Now&&refreshTokenExpiry /// 根据授权码,获取Token /// /// /// /// public ResponseModel GetTokenWithRefresh(string authCode) { ResponseModel result = new ResponseModel(); string key = $"AuthCode:{authCode}"; string clientIdCachekey = $"AuthCodeClientId:{authCode}"; string AuthCodeSessionTimeKey = $"AuthCodeSessionTime:{authCode}"; //根据授权码获取用户信息 CurrentUserModel currentUserModel = _cachelper.StringGet(key); if (currentUserModel == null) { throw new Exception("code无效"); } //清除authCode,只能用一次 _cachelper.DeleteKey(key); //获取应用配置 string clientId = _cachelper.StringGet(clientIdCachekey); //刷新token过期时间 DateTime sessionExpiryTime = _cachelper.StringGet(AuthCodeSessionTimeKey); DateTime tokenExpiryTime = DateTime.Now.AddMinutes(10);//token过期时间10分钟 //如果刷新token有过期期比token默认时间短,把token过期时间设成和刷新token一样 if (sessionExpiryTime > DateTime.Now && sessionExpiryTime < tokenExpiryTime) { tokenExpiryTime = sessionExpiryTime; } //获取访问token string token = this.IssueToken(currentUserModel, clientId, (sessionExpiryTime - DateTime.Now).TotalSeconds); TimeSpan refreshTokenExpiry; if (sessionExpiryTime != default(DateTime)) { refreshTokenExpiry = sessionExpiryTime - DateTime.Now; } else { refreshTokenExpiry = TimeSpan.FromSeconds(60 * 60 * 24);//默认24小时 } //获取刷新token string refreshToken = this.IssueToken(currentUserModel, clientId, refreshTokenExpiry.TotalSeconds); //缓存刷新token _cachelper.StringSet($"RefreshToken:{refreshToken}", currentUserModel, refreshTokenExpiry); //缓存刷新token过期时间 _cachelper.StringSet($"RefreshTokenExpiry:{refreshToken}",DateTime.Now.AddSeconds(refreshTokenExpiry.TotalSeconds), refreshTokenExpiry); result.SetSuccess(new GetTokenDTO() { token = token, refreshToken = refreshToken, expires = 60 * 10 }); Console.WriteLine($"client_id:{clientId}获取token,有效期:{sessionExpiryTime.ToString("yyyy-MM-dd HH:mm:ss")},token:{token}"); return result; } #region private /// /// 签发token /// /// /// /// /// private string IssueToken(CurrentUserModel userModel, string clientId, double second = 600) { var claims = new[] { new Claim(ClaimTypes.Name, userModel.name), new Claim("Account", userModel.account), new Claim("Id", userModel.id.ToString()), new Claim("Mobile", userModel.mobile), new Claim(ClaimTypes.Role,userModel.role), }; //var appHSSetting = getAppInfoByAppKey(clientId); //var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(appHSSetting.clientSecret)); //var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var creds = GetCreds(clientId); /** * Claims (Payload) Claims 部分包含了一些跟这个 token 有关的重要信息。 JWT 标准规定了一些字段,下面节选一些字段: iss: The issuer of the token,签发主体,谁给的 sub: The subject of the token,token 主题 aud: 接收对象,给谁的 exp: Expiration Time。 token 过期时间,Unix 时间戳格式 iat: Issued At。 token 创建时间, Unix 时间戳格式 jti: JWT ID。针对当前 token 的唯一标识 除了规定的字段外,可以包含其他任何 JSON 兼容的字段。 * */ var token = new JwtSecurityToken( issuer: "SSOCenter", //谁给的 audience: clientId, //给谁的 claims: claims, expires: DateTime.Now.AddSeconds(second),//token有效期 notBefore: null,//立即生效 DateTime.Now.AddMilliseconds(30),//30s后有效 signingCredentials: creds); string returnToken = new JwtSecurityTokenHandler().WriteToken(token); return returnToken; } /// /// 根据appKey获取应用信息 /// /// /// private AppHSSetting getAppInfoByAppKey(string clientId) { AppHSSetting appHSSetting = _appSettingOptions.Value.appHSSettings.Where(s => s.clientId == clientId).FirstOrDefault(); return appHSSetting; } /// /// 获取加密方式 /// /// protected abstract SigningCredentials GetCreds(string clientId); #endregion }

新建类JWTHSService实现对称加密

///

/// JWT对称可逆加密 /// public class JWTHSService : JWTBaseService { public JWTHSService(IOptions options, Cachelper cachelper):base(options,cachelper) { } /// /// 生成对称加密签名凭证 /// /// /// protected override SigningCredentials GetCreds(string clientId) { var appHSSettings=getAppInfoByAppKey(clientId); var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(appHSSettings.clientSecret)); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); return creds; } /// /// 根据appKey获取应用信息 /// /// /// private AppHSSetting getAppInfoByAppKey(string clientId) { AppHSSetting appHSSetting = _appSettingOptions.Value.appHSSettings.Where(s => s.clientId == clientId).FirstOrDefault(); return appHSSetting; } }

新建JWTRSService类实现非对称加密,和上面的对称加密,只需要一个就可以里,这里把两种都写出来了

///

/// JWT非对称加密 /// public class JWTRSService : JWTBaseService { public JWTRSService(IOptions options, Cachelper cachelper):base(options, cachelper) { } /// /// 生成非对称加密签名凭证 /// /// /// protected override SigningCredentials GetCreds(string clientId) { var appRSSetting = getAppInfoByAppKey(clientId); var rsa = RSA.Create(); byte[] privateKey = Convert.FromBase64String(appRSSetting.privateKey);//这里只需要私钥,不要begin,不要end rsa.ImportPkcs8PrivateKey(privateKey, out _); var key = new RsaSecurityKey(rsa); var creds = new SigningCredentials(key, SecurityAlgorithms.RsaSha256); return creds; } /// /// 根据appKey获取应用信息 /// /// /// private AppRSSetting getAppInfoByAppKey(string clientId) { AppRSSetting appRSSetting = _appSettingOptions.Value.appRSSettings.Where(s => s.clientId == clientId).FirstOrDefault(); return appRSSetting; } }

什么时候用JWT的对称加密,什么时候用JWT的非对称加密呢?

对称加密:双方保存同一个密钥,签名速度快,但因为双方密钥一样,所以安全性比非对称加密低一些。

非对称加密:认证方保存私钥,系统方保存公钥,签名速度比对称加密慢,但公钥私钥互相不能推导,所以安全性高。

所以注重性能的用对称加密,注重安全的用非对称加密,一般是公司的系统用对称加密,第三方接入的话用非对称加密。

web1项目:

appsettings.json存着web1的信息

{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "SSOSetting": { "issuer": "SSOCenter", "audience": "web1", "clientId": "web1", "clientSecret": "Nu4Ohg8mfpPnNxnXu53W4g0yWLqF0mX2" }}

Program.cs文件加入认证代码,加入builder.Services.AddAuthentication(。。。和加入app.UseAuthentication(),完整代码如下:

using Microsoft.AspNetCore.Authentication.JwtBearer;using Microsoft.IdentityModel.Tokens;using RSAExtensions;using SSO.Demo.Web1.Models;using SSO.Demo.Web1.Utils;using System.Security.Cryptography;using System.Text;var builder = WebApplication.CreateBuilder(args);// Add services to the container.builder.Services.AddControllersWithViews();builder.Services.AddHttpClient();builder.Services.AddSingleton();builder.Services.Configure(builder.Configuration.GetSection("AppOptions"));builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { //Audience,Issuer,clientSecret的值要和sso的一致 //JWT有一些默认的属性,就是给鉴权时就可以筛选了 ValidateIssuer = true,//是否验证Issuer ValidateAudience = true,//是否验证Audience ValidateLifetime = true,//是否验证失效时间 ValidateIssuerSigningKey = true,//是否验证client secret ValidIssuer = builder.Configuration["SSOSetting:issuer"],// ValidAudience = builder.Configuration["SSOSetting:audience"],//Issuer,这两项和前面签发jwt的设置一致 IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["SSOSetting:clientSecret"]))//client secret }; });#region 非对称加密-鉴权//var rsa = RSA.Create();//byte[] publickey = Convert.FromBase64String(AppSetting.PublicKey); //公钥,去掉begin... end ...////rsa.ImportPkcs8PublicKey 是一个扩展方法,来源于RSAExtensions包//rsa.ImportPkcs8PublicKey(publickey);//var key = new RsaSecurityKey(rsa);//var signingCredentials = new SigningCredentials(key, SecurityAlgorithms.RsaPKCS1);//builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)// .AddJwtBearer(options =>// {// options.TokenValidationParameters = new TokenValidationParameters// {// //Audience,Issuer,clientSecret的值要和sso的一致// //JWT有一些默认的属性,就是给鉴权时就可以筛选了// ValidateIssuer = true,//是否验证Issuer// ValidateAudience = true,//是否验证Audience// ValidateLifetime = true,//是否验证失效时间// ValidateIssuerSigningKey = true,//是否验证client secret// ValidIssuer = builder.Configuration["SSOSetting:issuer"],//// ValidAudience = builder.Configuration["SSOSetting:audience"],//Issuer,这两项和前面签发jwt的设置一致// IssuerSigningKey = signingCredentials.Key// };// });#endregionvar app = builder.Build();ServiceLocator.Instance = app.Services; //用于手动获取DI对象// Configure the HTTP request pipeline.if (!app.Environment.IsDevelopment()){ app.UseExceptionHandler("/Home/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see app.UseHsts();}app.UseHttpsRedirection();app.UseStaticFiles();app.UseRouting();app.UseAuthentication();//这个加在UseAuthorization 前app.UseAuthorization();app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");app.Run();

然后加接口根据授权code获取token,增加AccountController

///

/// 用户信息 /// public class AccountController : Controller { private IHttpClientFactory _ private readonly Cachelper _cachelper; public AccountController(IHttpClientFactory Cachelper cachelper) { _= _cachelper = cachelper; } /// /// 获取用户信息,接口需要进行权限校验 /// /// [MyAuthorize] [HttpPost] public ResponseModel GetUserInfo() { ResponseModel user = new ResponseModel(); return user; } /// /// 登录成功回调 /// /// public ActionResult LoginRedirect() { return View(); } //根据authCode获取token [HttpPost] public async Task> GetAccessCode([FromBody] GetAccessCodeRequest request) { ResponseModel result = new ResponseModel(); //请求SSO获取 token var client = _ var param = new { authCode = request.authCode }; string jsonData = System.Text.Json.JsonSerializer.Serialize(param); StringContent paramContent = new StringContent(jsonData); //请求sso获取token var response = await client.PostAsync("new StringContent(jsonData, Encoding.UTF8, "application/json")); string resultStr = await response.Content.ReadAsStringAsync(); result = System.Text.Json.JsonSerializer.Deserialize>(resultStr); if (result.code == 0) //成功 { //成功,缓存token到局部会话 string token = result.data.token; string key = $"SessionCode:{request.sessionCode}"; string tokenKey = $"token:{token}"; _cachelper.StringSet(key, token, TimeSpan.FromSeconds(result.data.expires)); _cachelper.StringSet(tokenKey, true, TimeSpan.FromSeconds(result.data.expires)); Console.WriteLine($"获取token成功,局部会话code:{request.sessionCode},{Environment.NewLine}token:{token}"); } return result; } /// /// 退出登录 /// /// /// [HttpPost] public ResponseModel LogOut([FromBody] LogOutRequest request) { string key = $"SessionCode:{request.SessionCode}"; //根据会话取出token string token = _cachelper.StringGet(key); if (!string.IsNullOrEmpty(token)) { //清除token string tokenKey = $"token:{token}"; _cachelper.DeleteKey(tokenKey); } Console.WriteLine($"会话Code:{request.SessionCode}退出登录"); return new ResponseModel().SetSuccess(); } }

还有得到的token还没过期,如果我退出登录了,怎么判断这个会话token失效了呢?

这里需要拦截认证过滤器,判断token在缓存中被删除,则认证不通过,增加文件MyAuthorize

///

/// 拦截认证过滤器 /// public class MyAuthorize : Attribute, IAuthorizationFilter { private static Cachelper _cachelper = ServiceLocator.Instance.GetService(); public void OnAuthorization(AuthorizationFilterContext context) { string id = context.HttpContext.User.FindFirst("id")?.Value; if(string.IsNullOrEmpty(id)) { //token检验失败 context.Result = new StatusCodeResult(401); //返回鉴权失败 return; } Console.WriteLine("我是Authorization过滤器"); //请求的地址 var url = context.HttpContext.Request.Path.Value; //获取打印头部信息 var heads = context.HttpContext.Request.Headers; //取到token "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoi5byg5LiJIiwiQWNjb3VudCI6ImFkbWluIiwiSWQiOiIxMDEiLCJNb2JpbGUiOiIxMzgwMDEzODAwMCIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvcm9sZSI6IlN1cGVyQWRtaW4iLCJleHAiOjE2NTMwNjA0MDIsImlzcyI6IlNTT0NlbnRlciIsImF1ZCI6IndlYjIifQ.aAi5a0zr_nLQQaSxSBqEhHZQ6ALFD_rWn2tnLt38DeA" string token = heads["Authorization"]; token = token.Replace("Bearer", "").TrimStart();//去掉 "Bearer "才是真正的token if (string.IsNullOrEmpty(token)) { Console.WriteLine("校验不通过"); return; } //redis校验这个token的有效性,确定来源是sso和确定会话没过期 string tokenKey = $"token:{token}"; bool isVaid = _cachelper.StringGet(tokenKey); //token无效 if (isVaid == false) { Console.WriteLine($"token无效,token:{token}"); context.Result = new StatusCodeResult(401); //返回鉴权失败 } } }

然后需要认证的控制器或方法头部加上[MyAuthorize]即能自动认证。

web1需要登录的页面

@{ ViewData["Title"] = "Home Page";}

欢迎来到Web1

Learn about 退出登录.

@section Scripts{ }

sso登录完要跳转回web1的页面

@* For more information on enabling MVC for empty projects, visit Layout = null;}

到这里web1的核心代码就完成了,web2的代码跟web1除了配置里面的加密key,其他全部一样,就不再贴出代码了,后面源码有。

到这里,就实现了一处登录,全部登录了。

2、一处退出,全部退出实现

一处退出,处处退出的流程像实现目标中的流程图,web1系统退出,跳转到SSO,让SSO发 For more information on enabling MVC for empty projects, visit id="form">

用户名:
密码:

这里的SessionCode是关键,作为一个全局code,系统登录会同步到个系统,用于统一退出登录时用

SSO的退出登录页面LogOut.cshtml

@* For more information on enabling MVC for empty projects, visit src="~/lib/jquery/dist/jquery.min.js">

退出登录接口:

///

/// 退出登录 /// /// /// [HttpPost] public async Task LogOutApp([FromBody] LogOutRequest request) { //删除全局会话 string sessionKey = $"SessionCode:{request.sessionCode}"; _cachelper.DeleteKey(sessionKey); var client = _ var param = new { sessionCode = request.sessionCode }; string jsonData = System.Text.Json.JsonSerializer.Serialize(param); StringContent paramContent = new StringContent(jsonData); //这里实战中是用数据库或缓存取 List urls = new List() { " " }; //这里可以异步mq处理,不阻塞返回 foreach (var url in urls) { //web1退出登录 var logOutResponse = await client.PostAsync(url, new StringContent(jsonData, Encoding.UTF8, "application/json")); string resultStr = await logOutResponse.Content.ReadAsStringAsync(); ResponseModel response = System.Text.Json.JsonSerializer.Deserialize(resultStr); if (response.code == 0) //成功 { Console.WriteLine($"url:{url},会话Id:{request.sessionCode},退出登录成功"); } else { Console.WriteLine($"url:{url},会话Id:{request.sessionCode},退出登录失败"); } }; return new ResponseModel().SetSuccess(); }

web1,web2的退出登录接口

///

/// 退出登录 /// /// /// [HttpPost] public ResponseModel LogOut([FromBody] LogOutRequest request) { string key = $"SessionCode:{request.SessionCode}"; //根据会话取出token string token = _cachelper.StringGet(key); if (!string.IsNullOrEmpty(token)) { //清除token string tokenKey = $"token:{token}"; _cachelper.DeleteKey(tokenKey); } Console.WriteLine($"会话Code:{request.SessionCode}退出登录"); return new ResponseModel().SetSuccess(); }

到这里,一处退出,全部退出也完成了。

3、双token机制实现

token和refresh_token生成算法一样就可以了,只是token的有效期短,refresh_token的有效期长。

那刷新token时怎么知道这个是刷新token呢,SSO生成刷新token的时候,把它保存到缓存中,刷新token的时候判断缓存中有就是刷新token。

生成双token的代码:

///

/// 根据授权码,获取Token /// /// /// /// public ResponseModel GetTokenWithRefresh(string authCode) { ResponseModel result = new ResponseModel(); string key = $"AuthCode:{authCode}"; string clientIdCachekey = $"AuthCodeClientId:{authCode}"; string AuthCodeSessionTimeKey = $"AuthCodeSessionTime:{authCode}"; //根据授权码获取用户信息 CurrentUserModel currentUserModel = _cachelper.StringGet(key); if (currentUserModel == null) { throw new Exception("code无效"); } //清除authCode,只能用一次 _cachelper.DeleteKey(key); //获取应用配置 string clientId = _cachelper.StringGet(clientIdCachekey); //刷新token过期时间 DateTime sessionExpiryTime = _cachelper.StringGet(AuthCodeSessionTimeKey); DateTime tokenExpiryTime = DateTime.Now.AddMinutes(10);//token过期时间10分钟 //如果刷新token有过期期比token默认时间短,把token过期时间设成和刷新token一样 if (sessionExpiryTime > DateTime.Now && sessionExpiryTime < tokenExpiryTime) { tokenExpiryTime = sessionExpiryTime; } //获取访问token string token = this.IssueToken(currentUserModel, clientId, (sessionExpiryTime - DateTime.Now).TotalSeconds);if (sessionExpiryTime != default(DateTime)) { refreshTokenExpiry = sessionExpiryTime - DateTime.Now; } else { refreshTokenExpiry = TimeSpan.FromSeconds(60 * 60 * 24);//默认24小时 } //获取刷新token string refreshToken = this.IssueToken(currentUserModel, clientId, refreshTokenExpiry.TotalSeconds); //缓存刷新token _cachelper.StringSet(refreshToken, currentUserModel, refreshTokenExpiry); result.SetSuccess(new GetTokenDTO() { token = token, refreshToken = refreshToken, expires = 60 * 10 }); Console.WriteLine($"client_id:{clientId}获取token,有效期:{sessionExpiryTime.ToString("yyyy-MM-dd HH:mm:ss")},token:{token}"); return result; }

根据刷新token获取token代码:

///

/// 根据刷新Token获取Token /// /// /// /// public string GetTokenByRefresh(string refreshToken, string clientId) { //刷新Token是否在缓存 CurrentUserModel currentUserModel = _cachelper.StringGet($"RefreshToken:{refreshToken}"); if(currentUserModel==null) { return String.Empty; } //刷新token过期时间 DateTime refreshTokenExpiry = _cachelper.StringGet($"RefreshTokenExpiry:{refreshToken}"); //token默认时间为600s double tokenExpiry = 600; //如果刷新token的过期时间不到600s了,token过期时间为刷新token的过期时间 if(refreshTokenExpiry>DateTime.Now&&refreshTokenExpiry

四、效果演示

这里项目的SSO地址是:,web1地址是:在最后加入

127.0.0.1 sso.com127.0.0.1 web1.com127.0.0.1 web2.com

这样得到新的地址,SSO地址:,web1地址是:这里一开始,访问  提取码:qcn1

github: https://github.com/weixiaolong325/SSO.Demo.SSO

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:关于微信小程序设置http请求的步骤
下一篇:微信小程序返回多级页面的实现方法(小程序返回上一级)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~