asp.Netcore 3.1カスタムミドルウェアjwt token認証を実現

9315 ワード

あまり話さないし、どう言ったらいいか分からない.ダイレクトコード

認証情報ベアラ対象【user】

/// 
///       
/// 
public class DyUser
{
    /// 
    ///   ID
    /// 
    public int UserId { get; set; }
    /// 
    ///     ID
    /// 
    public int? TenantId { get; set; }
}

Jwt構成オブジェクト

public class AuthOptions
{
    /// 
    /// Jwt  Key
    /// 
    public string Security { get; set; }
    /// 
    ///     【 】
    /// 
    public int Expiration { get; set; }
}

JWT管理インタフェース

public interface IAuthManage
{
    /// 
    ///   JwtToken
    /// 
    ///     
    /// 
    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();

            //      ,       
            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
            throw;
        }
    }
}

アクセス権フィルタ


これは,先ほどのミドルウェアの格納情報から許可が成功したか否かを判断し,匿名特性をサポートする.
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().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() != null;
    }
}

IServiceCollectionの拡張


今後の管理とメンテナンスを容易にするには、主に必要な対象をIOC容器に注入することです
public static class AuthServiceExtensions
{
    public static void AddAuth(this IServiceCollection services, Action configAction)
    {
        var options = new AuthOptions();
        configAction(options);
        services.AddSingleton(options);
        services.AddSingleton(new MicrosoftJwtAuthManage(options));
    }
}

NullDySession


ここでは、非コントローラクラスでユーザ情報を取得するために
/// 
///       
/// 
public class NullDySession
{
    /// 
    ///   DySession  
    /// 
    public static NullDySession Instance { get; } = new NullDySession();
    /// 
    ///         
    /// 
    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();
        }
    }

    private NullDySession()
    {
    }
}

ここまで準備が完了して、使い始めましょう~

修正【Startup.cs->ConfigureServices】

//           
services.AddControllersWithViews(options =>
{
	options.Filters.Add();
})
//        
services.AddAuth(options =>
{
	options.Expiration = 7;//    
	options.Security = apolloConfig.Get("JwtSecret");
});

ミドルウェアの追加【Startup.cs->Configure(IApplicationBuilder app,IWebHostEnvironment env)メソッド中】


ミドルウェアの位置に注意
//  jwt     
app.UseMiddleware();

api使用例【使用構造注入IAuthManagement】

//   JwtToken
var newToken = _authManage.CreateJwtToken(para.Sn);

//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