話不多講,也不知道咋講!直接上代碼
認證信息承載對象【user】
/// <summary>
/// 認證用戶信息
/// </summary>
public class DyUser
{
/// <summary>
/// 用戶ID
/// </summary>
public int UserId { get; set; }
/// <summary>
/// 所屬商戶ID
/// </summary>
public int? TenantId { get; set; }
}
Jwt配置對象
public class AuthOptions
{
/// <summary>
/// Jwt認證Key
/// </summary>
public string Security { get; set; }
/// <summary>
/// 過期時間【天】
/// </summary>
public int Expiration { get; set; }
}
JWT管理接口
public interface IAuthManage
{
/// <summary>
/// 生成JwtToken
/// </summary>
/// <param name="user">用戶信息</param>
/// <returns></returns>
string GenerateJwtToken(DyUser user);
}
JWT管理接口實現
暫時是使用微軟提供類庫生成,如果有想法可以自己生成
public class MicrosoftJwtAuthManage : IAuthManage
{
private readonly AuthOptions _authOptions;
public MicrosoftJwtAuth(AuthOptions authOptions)
{
_authOptions = authOptions;
}
public string GenerateJwtToken(DyUser user)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_authOptions.Security);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim("user",user.ToJson())
}),
Expires = DateTime.UtcNow.AddDays(_authOptions.Expiration),//一周過期
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
}
處理JWT中間件
這里借鑒國外大牛的代碼,主要就是驗證jwt並且存把解析出來的數據存放到當前上下文
public class JwtMiddleware
{
private readonly RequestDelegate _next;
private readonly AuthOptions _authOptions;
public JwtMiddleware(RequestDelegate next, AuthOptions authOptions)
{
_next = next;
_authOptions = authOptions;
}
public async Task Invoke(HttpContext context)
{
//獲取上傳token,可自定義擴展
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last()
?? context.Request.Headers["X-Token"].FirstOrDefault()
?? context.Request.Query["Token"].FirstOrDefault()
?? context.Request.Cookies["Token"];
if (token != null)
AttachUserToContext(context, token);
await _next(context);
}
private void AttachUserToContext(HttpContext context, string token)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_authOptions.Security);
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
// set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later)
ClockSkew = TimeSpan.Zero
}, out SecurityToken validatedToken);
var jwtToken = (JwtSecurityToken)validatedToken;
var user = jwtToken.Claims.First(x => x.Type == "user").Value.ToJsonEntity<DyUser>();
//寫入認證信息,方便業務類使用
var claimsIdentity = new ClaimsIdentity(new Claim[] { new Claim("user", jwtToken.Claims.First(x => x.Type == "user").Value) });
Thread.CurrentPrincipal = new ClaimsPrincipal(claimsIdentity);
// attach user to context on successful jwt validation
context.Items["User"] = user;
}
catch
{
// do nothing if jwt validation fails
// user is not attached to context so request won't have access to secure routes
}
}
}
權限過濾器
這個根據剛才中間件的存放的信息判斷是否授權成功,支持匿名特性
public class ApiAuthorizeAttribute : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
var user = context.HttpContext.Items["User"];
//驗證是否需要授權和授權信息
if (HasAllowAnonymous(context) == false && user == null)
{
// not logged in
context.Result = new JsonResult(new {message = "Unauthorized"})
{StatusCode = StatusCodes.Status401Unauthorized};
}
}
private static bool HasAllowAnonymous(AuthorizationFilterContext context)
{
var filters = context.Filters;
if (filters.OfType<IAllowAnonymousFilter>().Any())
{
return true;
}
// When doing endpoint routing, MVC does not add AllowAnonymousFilters for AllowAnonymousAttributes that
// were discovered on controllers and actions. To maintain compat with 2.x,
// we'll check for the presence of IAllowAnonymous in endpoint metadata.
var endpoint = context.HttpContext.GetEndpoint();
return endpoint?.Metadata?.GetMetadata<IAllowAnonymous>() != null;
}
}
擴展IServiceCollection
方便以后管理和維護,主要就是把需要的對象注入到IOC容器里面
public static class AuthServiceExtensions
{
public static void AddAuth(this IServiceCollection services, Action<AuthOptions> configAction)
{
var options = new AuthOptions();
configAction(options);
services.AddSingleton(options);
services.AddSingleton<IAuthManage>(new MicrosoftJwtAuthManage(options));
}
}
NullDySession
這里是為了在非控制器類獲取用戶信息用
/// <summary>
/// 當前會話對象
/// </summary>
public class NullDySession
{
/// <summary>
/// 獲取DySession實例
/// </summary>
public static NullDySession Instance { get; } = new NullDySession();
/// <summary>
/// 獲取當前用戶信息
/// </summary>
public DyUser DyUser
{
get
{
var claimsPrincipal = Thread.CurrentPrincipal as ClaimsPrincipal;
var claimsIdentity = claimsPrincipal?.Identity as ClaimsIdentity;
var userClaim = claimsIdentity?.Claims.FirstOrDefault(c => c.Type == "user");
if (userClaim == null || string.IsNullOrEmpty(userClaim.Value))
{
return null;
}
return userClaim.Value.ToJsonEntity<DyUser>();
}
}
private NullDySession()
{
}
}
到這為止准備工作完成,開始用起來吧~
修改【Startup.cs->ConfigureServices】
//添加全局權限認證過濾器
services.AddControllersWithViews(options =>
{
options.Filters.Add<ApiAuthorizeAttribute>();
})
//添加認證配置信息
services.AddAuth(options =>
{
options.Expiration = 7;//天為單位
options.Security = apolloConfig.Get("JwtSecret");
});
添加中間件【Startup.cs->Configure(IApplicationBuilder app, IWebHostEnvironment env)方法中】
注意中間件的位置
//啟用jwt認證中間件
app.UseMiddleware<JwtMiddleware>();
api使用案例【使用構造注入IAuthManage】
//生成了JwtToken
var token = _authManage.GenerateJwtToken(new DyUser {UserId = userId, TenantId = tenantId});
//Controller里面獲取用戶信息
public DyUser DyUser => (DyUser)this.HttpContext.Items["User"];
//普通class類獲取用戶信息【如果不是Web應用,需要獨立引用Dymg.Core】
NullDySession.Instance.DyUser.UserId;
//如果個別不接口不需要認證,可以使用AllowAnonymous特性
[HttpPost, AllowAnonymous]
public string Noauth()
{
return "這個不需要授權";
}
前端調用案例
//token放在請求頭里面
Authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoie1wiVXNlcklkXCI6MTIzNDU2ODcsXCJUZW5hbnRJZFwiOjY1NDMyMSxcIlN0YXRpb25JZFwiOm51bGwsXCJTbWFydEJveFNuXCI6bnVsbH0iLCJuYmYiOjE1OTU5MDAxMzYsImV4cCI6MTU5NjUwNDkzNiwiaWF0IjoxNTk1OTAwMTM2fQ.lkEunspinGeQK9sFoQs2WLpNticqOR4xv_18CQdOE_Y
//自定義key
x-token:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoie1wiVXNlcklkXCI6MTIzNDU2ODcsXCJUZW5hbnRJZFwiOjY1NDMyMSxcIlN0YXRpb25JZFwiOm51bGwsXCJTbWFydEJveFNuXCI6bnVsbH0iLCJuYmYiOjE1OTU5MDAxMzYsImV4cCI6MTU5NjUwNDkzNiwiaWF0IjoxNTk1OTAwMTM2fQ.lkEunspinGeQK9sFoQs2WLpNticqOR4xv_18CQdOE_Y
//使用連接字符串方式
https://xxxxx/user/getUser?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoie1wiVXNlcklkXCI6MTIzNDU2ODcsXCJUZW5hbnRJZFwiOjY1NDMyMSxcIlN0YXRpb25JZFwiOm51bGwsXCJTbWFydEJveFNuXCI6bnVsbH0iLCJuYmYiOjE1OTU5MDAxMzYsImV4cCI6MTU5NjUwNDkzNiwiaWF0IjoxNTk1OTAwMTM2fQ.lkEunspinGeQK9sFoQs2WLpNticqOR4xv_18CQdOE_Y