net core SSO 单点登录和控制器中获取Token和UserId
在写WebApi时常常是要获取登录用户的oken和UserId的,本文就这个需求来分享一下我在实际项目中的处理代码。
代码
控制器中注入
[ApiController]
//[Authorize]
[ServiceFilter(typeof(LDAPPLoginFilter))]
[Route("/file/api/[controller]/[action]")]
public class BaseController : ControllerBase
{
public ITokenHelper _tokenHelper;
public IHttpContextAccessor _httpContext;
/// <summary>
/// 当前用户ID
/// </summary>
public string CurrentUserId
{
get{
var userId = "00185770cfb24ccca22e14f8b9111111";
if (_httpContext != null && _tokenHelper != null)
{
var tokenobj = _httpContext.HttpContext.Request.Headers["Authorization"].ToString();
//读取配置文件中 的 秘钥
var secretKey = ConfigurationManager.JwtTokenConfig["Secret"];
string token = tokenobj.Split(" ")[1].ToString();//剔除Bearer
string mobile = "";//用户手机号
//验证jwt,同时取出来jwt里边的用户ID
TokenType tokenType = _tokenHelper.ValiTokenState(token, secretKey
, a => a["iss"] == "test.cn" && a["aud"] == "test"
, action =>
{
userId = action["id"];
mobile = action["phone_number"];
});
}
return userId;
}
}
}
public FileServerController(ITokenHelper tokenHelper, IHttpContextAccessor httpContextAccessor)
{
_tokenHelper = tokenHelper;
_httpContext = httpContextAccessor;
}
调用
登录过滤器
/// <summary>
/// 用户登录过滤器
/// 需要登录时 Check请求头中的token字段
/// </summary>
public class LDAPPLoginFilter : Attribute, IActionFilter
{
private readonly ITokenHelper _tokenHelper;
/// <summary>
/// 通过依赖注入得到数据访问层实例
/// </summary>
/// <param name="tokenHelper"></param>
public LDAPPLoginFilter(ITokenHelper tokenHelper)
{
_tokenHelper = tokenHelper;
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
/// <summary>
/// 操作过滤器
/// </summary>
/// <param name="context">请求上下文</param>
/// <param name="next">下一个过滤器或者终结点本身</param>
/// <returns></returns>
//async public override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
//{
// var descriptor = context.ActionDescriptor;
// // 当未标记为 AllowAnonymous 时再执行
// if (!descriptor.EndpointMetadata.Any(p => p is IAllowAnonymous))
// {
// context.HttpContext.Request.Headers.TryGetValue("token", out var tokens);
// var token = tokens.FirstOrDefault();
// if (string.IsNullOrWhiteSpace(token))
// {
// //如果没有token 直接返回
// context.Result = new UnauthorizedResult();//直接返回401 统一请求返回的话,这里修改成统一需要登录的请求实体
// }
// else
// {
// var redisKey = $"_APP_Token_{token}";
// //存在token
// var userInfo = RedisClient.GetValue<CommonUserModel>(redisKey);
// if (userInfo == null)
// {
// context.Result = new UnauthorizedResult();//直接返回401 统一请求返回的话,这里修改成统一需要登录的请求实体
// return;
// }
// // RedisClient.SetValue(redisKey, userInfo, 180 * 24 * 60);//180天内登录一次就重新置成180天 暂时不启用,重复写入性能影响大,一个月写入一次。 写入缓存时需要设计 cachetime
// await next();
// }
// }
//}
/// <summary>
/// 请求接口时进行拦截处理
/// </summary>
/// <param name="context"></param>
public void OnActionExecuting(ActionExecutingContext context)
{
//LawcaseEvidenceFilePreview
if (context.ActionDescriptor.EndpointMetadata.Any(it => it.GetType() == typeof(NoLDAPPLoginFilter)))
{
return;
}
//Action 名称过滤
if (context.ActionDescriptor.DisplayName.Contains("ActionName"))
{
return;
}
//var ret = new Models.Commons.ResultInfoModel();
var ret = new AjaxResult();
try
{
//获取请求头中的Token
var tokenobj = context.HttpContext.Request.Headers["Authorization"].ToString();
if (string.IsNullOrEmpty(tokenobj))
{
ret.state = (int)ResultCodeEnum.ApiUnauthorized;
ret.message = "接口未授权";
context.Result = new JsonResult(ret);
return;
}
//读取配置文件中 的 秘钥
var secretKey = ConfigurationManager.JwtTokenConfig["Secret"];
string token = tokenobj.Split(" ")[1].ToString();//剔除Bearer
string userId = string.Empty;
string mobile = string.Empty;//用户手机号
//var token = getToken(context);
//验证jwt,同时取出来jwt里边的用户ID
TokenType tokenType = _tokenHelper.ValiTokenState(token, secretKey
, a => a["iss"] == "test.cn" && a["aud"] == "test"
, action =>
{
userId = action["id"];
mobile = action["phone_number"];
});
if (tokenType == TokenType.FormError)
{
ret.state = (int)ResultCodeEnum.ApiUnauthorized;
ret.message = "登录失效,请重新登录!";//token非法
context.Result = new JsonResult(ret);
return;
}
if (tokenType == TokenType.Fail)
{
ret.state = (int)ResultCodeEnum.ApiUnauthorized;
ret.message = "用户信息验证失败!";//token验证失败
context.Result = new JsonResult(ret);
return;
}
if (tokenType == TokenType.Expired)
{
ret.state = (int)ResultCodeEnum.ApiUnauthorized;
ret.message = "登录失效,请重新登录!";
context.Result = new JsonResult(ret);
return;
}
if (string.IsNullOrEmpty(userId))
{
//获取用户编号失败时,阻止用户继续访问接口
ret.state = (int)ResultCodeEnum.Error;
ret.message = "用户信息丢失";
context.Result = new JsonResult(ret);
return;
}
//自定义代码逻辑, 取出token中的 用户编号 进行 用户合法性验证即可
//。。。。。。。
}
catch (Exception ex)
{
ret.state = (int)ResultCodeEnum.Error;
ret.message = "请求来源非法" + ex.Message.ToString();
context.Result = new JsonResult(ret);
return;
}
}
}
/// <summary>
///
/// </summary>
public interface ITokenHelper
{
/// <summary>
/// Token验证
/// </summary>
/// <param name="encodeJwt">token</param>
/// <param name="secretKey">secretKey</param>
/// <param name="validatePayLoad">自定义各类验证; 是否包含那种申明,或者申明的值</param>
/// <returns></returns>
bool ValiToken(string encodeJwt, string secretKey, Func<Dictionary<string, string>, bool> validatePayLoad = null);
/// <summary>
/// 带返回状态的Token验证
/// </summary>
/// <param name="encodeJwt">token</param>
/// <param name="secretKey">secretKey</param>
/// <param name="validatePayLoad">自定义各类验证; 是否包含那种申明,或者申明的值</param>
/// <param name="action"></param>
/// <returns></returns>
TokenType ValiTokenState(string encodeJwt, string secretKey, Func<Dictionary<string, string>, bool> validatePayLoad, Action<Dictionary<string, string>> action);
}
/// <summary>
///
/// </summary>
public class TokenHelper : ITokenHelper
{
/// <summary>
/// 验证身份 验证签名的有效性
/// </summary>
/// <param name="encodeJwt"></param>
/// <param name="secretKey">配置文件中取出来的签名秘钥</param>
/// <param name="validatePayLoad">自定义各类验证; 是否包含那种申明,或者申明的值, </param>
public bool ValiToken(string encodeJwt, string secretKey, Func<Dictionary<string, string>, bool> validatePayLoad = null)
{
var success = true;
var jwtArr = encodeJwt.Split('.');
if (jwtArr.Length < 3)//数据格式都不对直接pass
return false;
var payLoad = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[1]));
//配置文件中取出来的签名秘钥
var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(secretKey));
//验证签名是否正确(把用户传递的签名部分取出来和服务器生成的签名匹配即可)
success = success && string.Equals(jwtArr[2], Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(jwtArr[0], ".", jwtArr[1])))));
if (!success)
return success;//签名不正确直接返回
//其次验证是否在有效期内(也应该必须)
var now = ToUnixEpochDate(DateTime.UtcNow);
success = success && (now >= long.Parse(payLoad["nbf"].ToString()) && now < long.Parse(payLoad["exp"].ToString()));
//不需要自定义验证不传或者传递null即可
if (validatePayLoad == null)
return true;
//再其次 进行自定义的验证
success = success && validatePayLoad(payLoad);
return success;
}
/// <summary>
/// 时间转换
/// </summary>
/// <param name="date"></param>
/// <returns></returns>
private long ToUnixEpochDate(DateTime date)
{
return (long)Math.Round((date.ToUniversalTime() - new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalSeconds);
}
/// <summary>
///
/// </summary>
/// <param name="encodeJwt"></param>
/// <param name="secretKey"></param>
/// <param name="validatePayLoad"></param>
/// <param name="action"></param>
/// <returns></returns>
public TokenType ValiTokenState(string encodeJwt, string secretKey, Func<Dictionary<string, string>, bool> validatePayLoad, Action<Dictionary<string, string>> action)
{
//iss: jwt签发者
//sub: jwt所面向的用户
//aud: 接收jwt的一方
//exp: jwt的过期时间,这个过期时间必须要大于签发时间
//nbf: 定义在什么时间之前,该jwt都是不可用的
//iat: jwt的签发时间
//jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
var jwtArr = encodeJwt.Split('.');
if (jwtArr.Length < 3)//数据格式都不对直接pass
return TokenType.FormError;
//var header = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[0]));
var payLoad = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[1]));
var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(secretKey));
//验证签名是否正确(把用户传递的签名部分取出来和服务器生成的签名匹配即可)
if (!string.Equals(jwtArr[2], Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(jwtArr[0], ".", jwtArr[1]))))))
return TokenType.FormError;
var now = ToUnixEpochDate(DateTime.UtcNow);
var nbf = long.Parse(payLoad["nbf"].ToString());
var exp = long.Parse(payLoad["exp"].ToString());
if (!(now >= nbf && now < exp))
{
action(payLoad);
return TokenType.Expired;
}
//不需要自定义验证不传或者传递null即可
if (validatePayLoad == null)
{
action(payLoad);
return TokenType.Ok;
}
//再其次 进行自定义的验证
if (!validatePayLoad(payLoad))
return TokenType.Fail;
//可能需要获取jwt摘要里边的数据,封装一下方便使用
action(payLoad);
return TokenType.Ok;
}
}
public class TokenManagement
{
public string Secret { get; set; }
public string Issuer { get; set; }
public string Audience { get; set; }
public int AccessExpiration { get; set; }
public int RefreshExpiration { get; set; }
}
public class NoLDAPPLoginFilter : Attribute, IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
}
public void OnActionExecuting(ActionExecutingContext context)
{
//var ret = new Models.Commons.ResultInfoModel();
//ret.Head.ErrorCode = 1000;
//ret.Head.Msg = "成功!";
//context.Result = new JsonResult(ret);
}
}
/// <summary>
/// 设置该方法不会进行AES加密和解密操作,直接传入参数和响应结果
/// </summary>
[AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)]
public class NoAESMiddlewareAttribute : Attribute
{
}
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
LD.Code.ConfigurationManager.Configure(Configuration);
//注册日志功能
LD.Code.LogFactory.ResisterLogger();
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
//services.AddSwaggerGen(options =>
//{
// #region 文档格式化
// options.SwaggerDoc("v1", new OpenApiInfo
// {
// Version = "V1",
// Title = "ASP.NET CORE WepbApi 3.1",
// Description = "基于Asp.Net Core 3.1 实现文件上传下载",
// Contact = new OpenApiContact
// {
// Name = "律盾",
// Email = "[email protected]"
// },
// License = new OpenApiLicense
// {
// Name = "许可证",
// }
// });
// options.DocumentFilter<HiddenApiFilter>();
// #endregion
//});
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "XXX服务接口", Version = "v1" });
// 获取xml文件名
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
// 获取xml文件路径
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
// 添加控制器层注释,true表示显示控制器注释
c.IncludeXmlComments(xmlPath, true);
LD.Domain.xml
//xmlFile = "LD.Domain.xml";
//xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
//c.IncludeXmlComments(xmlPath, true);
LD.Code.xml
//xmlFile = "LD.Code.xml";
//xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
//c.IncludeXmlComments(xmlPath, true);
c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
c.DocumentFilter<HiddenApiFilter>();
});
services.Configure<TokenManagement>(Configuration.GetSection("JwtTokenConfig"));
var token = Configuration.GetSection("JwtTokenConfig").Get<TokenManagement>();
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(token.Secret)),
ValidIssuer = token.Issuer,
ValidAudience = token.Audience,
ValidateIssuer = false,
ValidateAudience = false
};
});
LD.Services.RegisterIoc.Register(services);
// ActionExecutingContext
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddTransient<ITokenHelper, TokenHelper>();
services.AddScoped<NoLDAPPLoginFilter>();
services.AddScoped<LDAPPLoginFilter>();
services.AddControllers();
//跨域
var corsstring = Configuration.GetSection("Cors").Value;
string[] corsarray = corsstring.Split(',');
services.AddCors(options => options.AddPolicy("CorsPolicy",
builder =>
{
builder.AllowAnyMethod().AllowAnyHeader()
.WithOrigins(corsarray)
.AllowCredentials();
}));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//if (env.IsDevelopment())
//{
// app.UseDeveloperExceptionPage();
//}
DocExpansion swaggeerDoc;
//if (env.IsDevelopment())
//{
app.UseDeveloperExceptionPage();
swaggeerDoc = DocExpansion.List;
//添加Swagger有关中间件
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "FileServerAPI v1");
c.RoutePrefix = string.Empty;
c.DocExpansion(DocExpansion.None);
});
//}
//else
//{
// swaggeerDoc = DocExpansion.None;
//}
app.UseStaticFiles();
//app.UseStaticFiles(new StaticFileOptions
//{ //设置不限制content-type
// ServeUnknownFileTypes = true
//});
app.UseHttpsRedirection();
app.UseRouting();//1.路由
app.UseCors("CorsPolicy");
app.UseAuthentication();//2.认证
app.UseAuthorization();//3.授权
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
//app.UseSwagger();
//app.UseSwaggerUI(c =>
//{
// c.SwaggerEndpoint("/swagger/v1/swagger.json", "API V1");
//});
}
}
public class Enum
{
/// <summary>
/// 系统数据返回状态
/// </summary>
public enum ResultCodeEnum
{
/// <summary>
/// 失败
/// </summary>
[Description("失败")]
Error = 0,
/// <summary>
/// 成功
/// </summary>
[Description("成功")]
Success = 1,
/// <summary>
/// 接口未授权
/// </summary>
[Description("接口未授权")]
ApiUnauthorized = 401
}
/// <summary>
///
/// </summary>
public enum TokenType
{
/// <summary>
/// 验证成功
/// </summary>
[Description("验证成功")]
Ok,
/// <summary>
/// 验证失败
/// </summary>
[Description("验证失败")]
Fail,
/// <summary>
/// Token失效
/// </summary>
[Description("Token失效")]
Expired,
/// <summary>
/// Token非法
/// </summary>
[Description("Token非法")]
FormError
}
}
END
标签:core,string,UserId,token,ret,Token,var,new,public From: https://blog.csdn.net/cjh16606260986/article/details/137132038