栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > .Net

.Net5 框架搭建(五):JWT+IHttpContextAccessor实现用户认证以及用户信息获取

.Net 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

.Net5 框架搭建(五):JWT+IHttpContextAccessor实现用户认证以及用户信息获取

前言

相信用做过登录功能的小伙伴都知道,用户登录成功后的有用信息,如:姓名、用户ID等等,无非这几种做法来保存这些信息,Session、Cookie、QueryString等等。但如今跨平台,百花齐放的时代,小程序啊、APP端啊、多端时代,最常见的就是单点登录,这明显传统上的传参方式就无法满足我们现有的需求。那么就会引用一个新的传参方式:JWT,根据维基百科的定义,JSON WEB Token(JWT),是一种基于JSON的、用于在网络上声明某种主张的令牌(token),是目前最流行的接口认证方案。对于它的介绍,我就不多说了,有兴趣的小伙伴可以可以参考我之前的文章《.net core3.1项目引用JWT保护API接口》

功能简介

大致流程图:

用户输入账号密码=》用户信息存入HttpContext,并得到一个Token数据=》用Token进行JWT身份认=》 利用IHttpContextAccessor来获取HttpContext的User属性

JWT

1、这里封装一个jwt认证的帮助类,一共有两块功能,颁发注册JWT字符串(用于登录成功返回一个)和解析JWT字符串

using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;

namespace CuoDing.Core.Common
{
    /// 
    /// jwt封装类
    /// 
    public class JwtHelper
    {

        /// 
        /// 颁发JWT字符串
        /// 
        /// 
        /// 
        public static string IssueJwt(TokenModelJwt tokenModel)
        {
            string iss = Appsettings.app(new string[] { "Jwt", "Issuer" });
            string aud = Appsettings.app(new string[] { "Jwt", "Audience" });
            string secret = Appsettings.app(new string[] { "Jwt", "SecurityKey" });
            int expires = Appsettings.app(new string[] { "Jwt", "Expires" }).ObjToInt();
            int refreshExpires = Appsettings.app(new string[] { "Jwt", "RefreshExpires" }).ObjToInt();
            var timestamp = DateTime.Now.AddMinutes(expires + refreshExpires).ObjToTimestamp().ToString();
            var claims = new List
                {
                 
                   
                new Claim(ClaimAttributes.UserId, tokenModel.UserId),
                new Claim(ClaimAttributes.CreateDept, tokenModel.CreateDept),
                new Claim(ClaimAttributes.RefreshExpires, timestamp)
               };


            //秘钥 (SymmetricSecurityKey 对安全性的要求,密钥的长度太短会报出异常)
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

            var jwt = new JwtSecurityToken(
                issuer: iss,
                audience: aud,
                claims: claims,
                notBefore: DateTime.Now,
                expires: DateTime.Now.AddMinutes(expires),
                signingCredentials: creds);

            var jwtHandler = new JwtSecurityTokenHandler();
            var encodedJwt = jwtHandler.WriteToken(jwt);

            return encodedJwt;
        }

        /// 
        /// 解析
        /// 
        /// 
        /// 
        public static TokenModelJwt SerializeJwt(string jwtStr)
        {
            var jwtHandler = new JwtSecurityTokenHandler();
            TokenModelJwt tokenModelJwt = new TokenModelJwt();

            // token校验
            if (jwtStr.IsNotEmptyOrNull() && jwtHandler.CanReadToken(jwtStr))
            {

                JwtSecurityToken jwtToken = jwtHandler.ReadJwtToken(jwtStr);
               
                Claim[] claimArr = jwtToken?.Claims?.ToArray();
                if (claimArr != null && claimArr.Length > 0)
                {
                    tokenModelJwt.UserId = claimArr.FirstOrDefault(a => a.Type == ClaimAttributes.UserId)?.Value;
                    tokenModelJwt.RefreshExpires = claimArr.FirstOrDefault(a => a.Type == ClaimAttributes.RefreshExpires)?.Value;
                    tokenModelJwt.CreateDept = claimArr.FirstOrDefault(a => a.Type == ClaimAttributes.CreateDept)?.Value;
                }
            }
            return tokenModelJwt;
        }
    }

    /// 
    /// 令牌
    /// 
    public class TokenModelJwt
    {
        /// 
        /// 用户ID
        /// 
        public string UserId { get; set; }
        /// 
        /// 所属部门
        /// 
        public string CreateDept { get; set; }
        /// 
        /// 刷新有效时间
        /// 
        public string RefreshExpires { get; set; }

    }
}

2、JWT授权认证生效,需要在我们在Startup的ConfigureServices方法中将JwtBearer的注册信息注册进来
记得添加依赖包Microsoft.AspNetCore.Authentication.JwtBearer;


            #region 身份认证授权
            services.AddAuthentication(options =>
            {
                options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    SaveSigninToken = true,//保存token,后台验证token是否生效(重要)
                    ValidateIssuer = true,//是否验证Issuer
                    ValidateAudience = true,//是否验证Audience
                    ValidateLifetime = true,//是否验证失效时间
                    ValidateIssuerSigningKey = true,//是否验证SecurityKey
                    ValidAudience = Appsettings.app(new string[] { "Jwt", "Audience" }).ToString(),//订阅者
                    ValidIssuer = Appsettings.app(new string[] { "Jwt", "Issuer" }).ToString(),//Issuer,这两项和前面签发jwt的设置一致
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Appsettings.app(new string[] { "Jwt", "SecurityKey" }).ToString()))//密钥
                };
                options.Events = new JwtBearerEvents()
                {
                    OnChallenge = context =>
                    {
                        context.HandleResponse();
                        context.Response.Clear();
                        context.Response.ContentType = "application/json";
                        context.Response.StatusCode = 401;
                        //context.Response.WriteAsync(new { message = "授权未通过", status = false, code = 401 }.ToJson());
                        context.Response.WriteAsync(new DXResult { code = DXCode.Unauthorized, msg = "授权未通过" }.ToJson());
                        return Task.CompletedTask;
                    }
                };
            });
            #endregion

3、注册完以上信息,还需在我们的请求管道方法Configure把授权认证配置起来,注意先后顺序。

  • 中间件的注册顺序严格按照官方配置推荐依次顺序,更多请看https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/middleware/?view=aspnetcore-5.0
  • ExceptionHandler=>HSTS=>HttpsRedirection=>StaticFiles=>CORS=>Authentication=>Authorization=>自定义中间组件=》Endpoint
app.UseAuthentication();
app.UseAuthorization();
IHttpContextAccessor

1、首先得把IHttpContextAccessor注册进来

services.AddSingleton();

2、然后我们封装一个方法从IHttpContextAccessor 的HttpContext中获取对应的ClaimsPrincipal,如下(认证通过后,User是具有当前用户的身份标志的ClaimsPrincipal),在这里我们定义一个User类负责从HttpContext?.User把用户的身份标志信息提取出来,比如用户的Id,部门等业务数据,这些是需要在获取Token时系统所提供过的信息。

ClaimAttributes.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CuoDing.Core.Common
{
    /// 
    /// Claim属性
    /// 
    public static class ClaimAttributes
    {
        /// 
        /// 用户Id
        /// 
        public const string UserId = "id";

        /// 
        /// 认证授权用户Id
        /// 
        public const string IdentityServerUserId = "sub";

        /// 
        /// 用户名
        /// 
        public const string UserName = "na";

        /// 
        /// 姓名
        /// 
        public const string UserNickName = "nn";

        /// 
        /// 刷新有效期
        /// 
        public const string RefreshExpires = "re";

        /// 
        /// 创建部门
        /// 
        public const string CreateDept = "dept";

    }
}

User.cs

using Microsoft.AspNetCore.Http;

namespace CuoDing.Core.Common
{
    /// 
    /// 用户信息
    /// 
    public class User : IUser
    {
        private readonly IHttpContextAccessor _accessor;

        public User(IHttpContextAccessor accessor)
        {
            _accessor = accessor;
        }

        /// 
        /// 用户Id
        /// 
        public virtual string Id
        {
            get
            {
                var id = _accessor?.HttpContext?.User?.FindFirst(ClaimAttributes.UserId);
                if (id != null && id.Value.IsNotEmptyOrNull())
                {
                    return id.Value;
                }
                return "";
            }
        }

        /// 
        /// 用户名
        /// 
        public string Name
        {
            get
            {
                var name = _accessor?.HttpContext?.User?.FindFirst(ClaimAttributes.UserName);

                if (name != null && name.Value.IsNotEmptyOrNull())
                {
                    return name.Value;
                }

                return "";
            }
        }

        /// 
        /// 创建部门
        /// 
        public string CreateDept
        {
            get
            {
                var name = _accessor?.HttpContext?.User?.FindFirst(ClaimAttributes.CreateDept);

                if (name != null && name.Value.IsNotEmptyOrNull())
                {
                    return name.Value;
                }

                return "";
            }
        }


    }
}

IUser.cs

namespace CuoDing.Core.Common
{
    /// 
    /// 用户信息接口
    /// 
    public interface IUser
    {
        /// 
        /// 用户Id
        /// 
        string Id { get; }

        /// 
        /// 用户名
        /// 
        string Name { get; }

        /// 
        /// 创建部门
        /// 
        string CreateDept { get; }


    }
}

3、一样的也要把这个类注册进来

services.AddSingleton();
简单的登录接口

简单写了一个关于登录的控制器,包含登录获取token、token以旧换新、获取封装httpcontent的Claim属性的用户数据

LoginController.cs

using CuoDing.Core.BLL;
using CuoDing.Core.Common;
using CuoDing.Core.Model.Enum;
using CuoDing.Core.Model.Model;
using CuoDing.Core.Model.ViewModels;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace DX.StdTrainning.WebApi.Controllers
{
    /// 
    /// 登录管理【无权限】
    /// 
    [Route("api/[controller]/[action]")]
    [ApiController]
    [AllowAnonymous]

    public class LoginController : ControllerBase
    {
        private readonly IUser _user;
        private readonly ISystemUserBLL _systemUserBLL;

        public LoginController(IUser user, ISystemUserBLL systemUserBLL)
        {
            _user = user;
            _systemUserBLL = systemUserBLL;
        }

        /// 
        /// 登录获取token
        /// 
        /// 
        /// 
        [HttpPost]
        public async Task GetToken(SystemUser param)
        {
            string jwtStr = string.Empty;
            var obj = await _systemUserBLL.LoginToken(param);
            if (obj != null&& obj.code==DXCode.Success)
            {
                var user = obj.data as SystemUser;
                TokenModelJwt tokenModel = new TokenModelJwt { UserId = user.Id.ToString(), CreateDept ="测试部门" };
                jwtStr = JwtHelper.IssueJwt(tokenModel);
                obj.data = jwtStr;
            }

            return obj;
        }


        /// 
        /// 刷新Token(以旧换新)
        /// 
        /// 
        /// 
        [HttpGet]
        public async Task RefreshToken(string token)
        {
            DXResult dXResult = new DXResult { code = DXCode.Success ,msg="刷新成功"};
            string jwtStr = string.Empty;
            var userInfo = JwtHelper.SerializeJwt(token);
            if (userInfo == null)
            {
                return new DXResult { code = DXCode.Failure, msg = "无效token" };
            }
            var refreshExpires = userInfo?.RefreshExpires;
            if (!refreshExpires.IsNotEmptyOrNull())
            {
                return new DXResult { code = DXCode.Failure, msg = "无效刷新时间" };
            }

            if (refreshExpires.ObjToLong() <= DateTime.Now.ObjToTimestamp())
            {
                return new DXResult { code = DXCode.Failure, msg = "登录信息已过期" };
            }

            var userId = userInfo?.UserId;
            if (!userId.IsNotEmptyOrNull())
            {
                return new DXResult { code = DXCode.Failure, msg = "用户信息为空" };
            }
            var dXResultUser = await _systemUserBLL.TokenGetById(userId.ObjToLong());
            if (dXResultUser.code != DXCode.Success)
            {
                return new DXResult { code = DXCode.Failure, msg = "获取用户信息失败" };
            }
            var userObj = dXResultUser.data as SystemUser;
            TokenModelJwt tokenModel = new TokenModelJwt { UserId = userObj.Id.ToString(), CreateDept = "测试刷新部门" };
            jwtStr = JwtHelper.IssueJwt(tokenModel);
            dXResult.data = jwtStr;
            return dXResult;
        }

        /// 
        /// 测试获取登录用户信息
        /// 
        /// 
        [HttpGet]
        [Authorize]
        public  dynamic GetUserTest()
        {
            DXResult dXResult = new DXResult { code = DXCode.Success };
            var userObj = new 
            {
                id=_user.Id,
                dept=_user.CreateDept
            };
            dXResult.data = userObj;
            return dXResult;
        }
    }
}

这里需要注意一点,关于是否开启权限认证,需要用 [AllowAnonymous]跟[Authorize]来区别

测试

1、运行项目,调用api/Login/GetToken

2、先去www.jwt.io验证下我们这个token数据是否正常

没毛病,继续下一步

3、调用api/Login/GetUserTest,获取封装httpcontent的Claim属性的用户数据

成功

完整代码已上传码云:https://gitee.com/shao-jiayong/cuo-ding

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/905016.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号