.Net5 框架搭建登录模块:JWT+IHttpContextAccessor实现用户认证以及用户信息获取

2 篇文章 0 订阅
前言

相信用做过登录功能的小伙伴都知道,用户登录成功后的有用信息,如:姓名、用户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
{
    /// <summary>
    /// jwt封装类
    /// </summary>
    public class JwtHelper
    {

        /// <summary>
        /// 颁发JWT字符串
        /// </summary>
        /// <param name="tokenModel"></param>
        /// <returns></returns>
        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<Claim>
                {
                 /*
                 * 特别重要:
                   1、这里将用户的部分信息,比如 uid 存到了Claim 中,如果你想知道如何在其他地方将这个 uid从 Token 中取出来,请看下边的SerializeJwt() 方法,或者在整个解决方案,搜索这个方法,看哪里使用了!
                   2、你也可以研究下 HttpContext.User.Claims 
                 */
                   
                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;
        }

        /// <summary>
        /// 解析
        /// </summary>
        /// <param name="jwtStr"></param>
        /// <returns></returns>
        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;
        }
    }

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

    }
}

2、JWT授权认证生效,需要在我们在Startup的ConfigureServices方法中将JwtBearer的注册信息注册进来

记得添加依赖包Microsoft.AspNetCore.Authentication.JwtBearer;
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.9" />
            #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<IHttpContextAccessor, HttpContextAccessor>();

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
{
    /// <summary>
    /// Claim属性
    /// </summary>
    public static class ClaimAttributes
    {
        /// <summary>
        /// 用户Id
        /// </summary>
        public const string UserId = "id";

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

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

        /// <summary>
        /// 姓名
        /// </summary>
        public const string UserNickName = "nn";

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

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

    }
}

User.cs

using Microsoft.AspNetCore.Http;

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

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

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

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

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

                return "";
            }
        }

        /// <summary>
        /// 创建部门
        /// </summary>
        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
{
    /// <summary>
    /// 用户信息接口
    /// </summary>
    public interface IUser
    {
        /// <summary>
        /// 用户Id
        /// </summary>
        string Id { get; }

        /// <summary>
        /// 用户名
        /// </summary>
        string Name { get; }

        /// <summary>
        /// 创建部门
        /// </summary>
        string CreateDept { get; }


    }
}

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

services.AddSingleton<IUser, User>();
简单的登录接口

简单写了一个关于登录的控制器,包含登录获取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
{
    /// <summary>
    /// 登录管理【无权限】
    /// </summary>
    [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;
        }

        /// <summary>
        /// 登录获取token
        /// </summary>
        /// <param name="parm"></param>
        /// <returns></returns>
        [HttpPost]
        public async Task<dynamic> 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;
        }


        /// <summary>
        /// 刷新Token(以旧换新)
        /// </summary>
        /// <param name="token"></param>
        /// <returns></returns>
        [HttpGet]
        public async Task<dynamic> 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;
        }

        /// <summary>
        /// 测试获取登录用户信息
        /// </summary>
        /// <returns></returns>
        [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属性的用户数据
在这里插入图片描述
成功

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现用户手机号验证码登录可以分为以下几个步骤: 1. 用户输入手机号和验证码,点击登录按钮。 2. 后端接收到手机号和验证码后,先验证验证码是否正确。 3. 如果验证码正确,后端生成JWT token并将token存储到Redis中,同时将token返回给前端。 4. 前端将token存储到本地,以便后续请求时使用。 5. 后续请求时,前端需要在请求头中加入token,后端通过解析token来判断用户是否已登录。 下面是具体实现过程: 1. 在阿里云短信控制台创建短信模板,获取accessKeyId和accessKeySecret。 2. 在Spring Boot项目中添加依赖: ``` <dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-core</artifactId> <version>4.0.3</version> </dependency> ``` 3. 实现发送短信验证码的接口: ``` @PostMapping("/sendSms") public Result sendSms(@RequestParam("phone") String phone) { // 生成随机验证码 String code = String.valueOf((int) ((Math.random() * 9 + 1) * 100000)); // 发送短信验证码 DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret); IAcsClient client = new DefaultAcsClient(profile); CommonRequest request = new CommonRequest(); request.setSysMethod(MethodType.POST); request.setSysDomain("dysmsapi.aliyuncs.com"); request.setSysVersion("2017-05-25"); request.setSysAction("SendSms"); request.putQueryParameter("RegionId", "cn-hangzhou"); request.putQueryParameter("PhoneNumbers", phone); request.putQueryParameter("SignName", "短信签名"); request.putQueryParameter("TemplateCode", "短信模板编号"); request.putQueryParameter("TemplateParam", "{\"code\":\"" + code + "\"}"); try { CommonResponse response = client.getCommonResponse(request); // 将验证码存储到Redis中,有效期为5分钟 redisTemplate.opsForValue().set(phone, code, 5, TimeUnit.MINUTES); return Result.success("短信验证码发送成功"); } catch (Exception e) { return Result.error("短信验证码发送失败"); } } ``` 4. 实现用户手机号验证码登录的接口: ``` @PostMapping("/login") public Result login(@RequestParam("phone") String phone, @RequestParam("code") String code) { // 验证验证码是否正确 String redisCode = redisTemplate.opsForValue().get(phone); if (StringUtils.isBlank(redisCode)) { return Result.error("验证码已过期,请重新发送"); } if (!redisCode.equals(code)) { return Result.error("验证码不正确"); } // 生成JWT token,并存储到Redis中 String token = JwtUtils.generateToken(phone); redisTemplate.opsForValue().set(phone, token, 1, TimeUnit.DAYS); // 将token返回给前端 return Result.success(token); } ``` 5. 实现JWT token的生成和解析: ``` public class JwtUtils { private static final String SECRET_KEY = "jwt_secret_key"; // JWT密钥 private static final long EXPIRATION_TIME = 7 * 24 * 60 * 60 * 1000; // JWT过期时间(7天) public static String generateToken(String phone) { Date now = new Date(); Date expiration = new Date(now.getTime() + EXPIRATION_TIME); return Jwts.builder() .setSubject(phone) .setIssuedAt(now) .setExpiration(expiration) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } public static String getPhoneFromToken(String token) { try { Claims claims = Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody(); return claims.getSubject(); } catch (Exception e) { return null; } } } ``` 6. 在拦截器中验证token并获取用户信息: ``` public class JwtInterceptor implements HandlerInterceptor { private static final String AUTH_HEADER = "Authorization"; // token在请求头中的名称 @Autowired private StringRedisTemplate redisTemplate; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader(AUTH_HEADER); if (StringUtils.isBlank(token)) { throw new BusinessException("未登录登录已过期"); } String phone = JwtUtils.getPhoneFromToken(token); if (StringUtils.isBlank(phone)) { throw new BusinessException("无效的token"); } String redisToken = redisTemplate.opsForValue().get(phone); if (StringUtils.isBlank(redisToken) || !redisToken.equals(token)) { throw new BusinessException("未登录登录已过期"); } return true; } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值