18

asp.net core 3.1 自定义中间件实现jwt token认证

 3 years ago
source link: http://www.cnblogs.com/jackgmchen/p/13416069.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
/// <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
            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<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 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

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK