zoukankan      html  css  js  c++  java
  • .net code 3.1 一个网站可以同时使用网页登录和使用JWT方式访问API

    主要用于网站以.Net Core 3.1 Web MVC网站,其中有部分数据需要建立API通过外部Winform程序使用。

    主要代码

    Startup中的ConfigureServices:

    //增加网页登录方式,直接使用自带的Identity
    services.AddIdentity<ApplicationUser, ApplicationRole>(options =>
    {
        options.Password.RequireDigit = false;
        options.Password.RequireLowercase = false;
        options.Password.RequireNonAlphanumeric = false;
        options.Password.RequireUppercase = false;
        options.Password.RequiredLength = 4;
        options.Password.RequiredUniqueChars = 1;
        options.User.AllowedUserNameCharacters = "";
    })
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();
    
    //增加API验证方式
    services.AddAuthentication()
        .AddJwtBearer("JwtBearerLogin", options=> {//用于AccessToken
            options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
            {
                ValidIssuer = JwtSetting.Issuer,
                ValidAudience = JwtSetting.Audience,
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JwtSetting.SecretKey)),
    
                ValidateLifetime = true
            };
        }).AddJwtBearer("JwtBearerRefreshTokenLogin", options => {//用户RefreshToken
            options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
            {
                ValidIssuer = JwtSetting.Issuer,
                ValidAudience = JwtSetting.RefreshTokenAudience,
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JwtSetting.SecretKey)),
    
                ValidateLifetime = true
            };
        });

    API中的AuthorizeController,用户提供JWT的登录和获取新的Token

    //API登录
    [HttpPost("/api/Token")]
    public async Task<IActionResult> TokenAsync([FromBody]Models.AuthorizeViewModels.LoginViewModel viewModel)
    {
        if (!ModelState.IsValid)//判断是否合法
        {
            return BadRequest("验证错误!");
        }
    
        var user = await _userManager.FindByNameAsync(viewModel.UserName);
        if (user == null)
        {
            return BadRequest("输入的用户名或密码错误!");
    
        }
    
        if (!await _userManager.CheckPasswordAsync(user, viewModel.Password))
        {
            return BadRequest("输入的用户名或密码错误!");
        }
        _logger.LogInformation("用户" + user.UserName + "通过API Token登录!");
    
        var th = new TokenHepler(_userManager);
        var res = th.CreateToken(user);
        res.UserName = user.UserName;
        return Ok(res);
    }
    
    //使用RefreshToken获取新的AccessToken
    //[Authorize(AuthenticationSchemes = "JwtBearerRefreshTokenLogin")]代表使用第二种API验证规则,此规则仅用于验证RefreshToken
    [HttpGet("/api/RefreshToken", Name = nameof(RefreshTokenAsync))]
    [Authorize(AuthenticationSchemes = "JwtBearerRefreshTokenLogin")]
    public async Task<IActionResult> RefreshTokenAsync()
    {
        var th = new TokenHepler(_userManager);
        _logger.LogInformation("用户通过API RefreshToken刷新Token!");
    
        return Ok(await th.RefreshTokenAsync(Request.HttpContext.User));
    }

    相关辅助代码:

    TokenHelper:

    public class TokenHepler
    {
        private readonly UserManager<ApplicationUser> _userManager;
        public TokenHepler(UserManager<ApplicationUser> options)
        {
            _userManager = options;
        }
    
        public TokenViewModel CreateAccessToken(ApplicationUser user)
        {
            Claim[] claims = new Claim[] { 
                new Claim(ClaimTypes.NameIdentifier, user.Id), 
                new Claim(ClaimTypes.Name, user.UserName) 
            };
    
            return CreateToken(claims, TokenType.AccessToken);
        }
    
        public ComplexTokenViewModel CreateToken(ApplicationUser user)
        {
            Claim[] claims = new Claim[] {
                new Claim(ClaimTypes.NameIdentifier, user.Id),
                new Claim(ClaimTypes.Name, user.UserName)
            };
    
            return CreateToken(claims);
        }
    
        public ComplexTokenViewModel CreateToken(Claim[] claims)
        {
            return new ComplexTokenViewModel
            {
                AccessToken = CreateToken(claims, TokenType.AccessToken),
                RefreshToken = CreateToken(claims, TokenType.RefreshToken)
            };
        }
    
        /// <summary>
        /// 用于创建AccessToken和RefreshToken。
        /// 这里AccessToken和RefreshToken只是过期时间不同,【实际项目】中二者的claims内容可能会不同。
        /// 因为RefreshToken只是用于刷新AccessToken,其内容可以简单一些。
        /// 而AccessToken可能会附加一些其他的Claim。
        /// </summary>
        /// <param name="claims"></param>
        /// <param name="tokenType"></param>
        /// <returns></returns>
        private TokenViewModel CreateToken(Claim[] claims, TokenType tokenType)
        {
            var now = DateTime.Now;
            var expires = now.Add(TimeSpan.FromMinutes(tokenType.Equals(TokenType.AccessToken) ? JwtSetting.ExpiresMinutes : JwtSetting.RefreshTokenExpiresMinutes));
            var token = new JwtSecurityToken(
                issuer: JwtSetting.Issuer,
                audience: tokenType.Equals(TokenType.AccessToken) ? JwtSetting.Audience : JwtSetting.RefreshTokenAudience,
                claims: claims,
                notBefore: now,
                expires: expires,
                signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JwtSetting.SecretKey)), SecurityAlgorithms.HmacSha256));
            return new TokenViewModel { Token = new JwtSecurityTokenHandler().WriteToken(token), Expires = expires };
        }
    
        public async Task<TokenViewModel> RefreshTokenAsync(ClaimsPrincipal claimsPrincipal)
        {
            var code = claimsPrincipal.Claims.FirstOrDefault(m => m.Type.Equals(ClaimTypes.NameIdentifier));
            if (null != code)
            {
                return CreateAccessToken(await _userManager.FindByIdAsync(code.Value));
            }
            else
            {
                return null;
            }
        }
    }
    
    public enum TokenType
    {
        AccessToken = 1,
        RefreshToken = 2
    }

    TokenModel

        public class TokenViewModel
        {
            public string Token { get; set; }
    
            public DateTime Expires { get; set; }
        }
    
        public class ComplexTokenViewModel
        {
            public TokenViewModel AccessToken { get; set; }
            public TokenViewModel RefreshToken { get; set; }
    
            public string UserName { get; set; }
        }

    JwtSetting只是一个简单的用于保存配置文件的静态类

    编写需要验证的API时在顶部增加

        [ApiController]
        [Route("/api/DictionaryClasses/{ClassKey}/Dictionarys")]
        [Authorize(AuthenticationSchemes = "JwtBearerLogin")]
        public class DictionaryController : ControllerBase

    来选择第一种JWT验证方式

    需要网页登录的Controller直接使用 [Authorize] 即可。

    Winform中的登录方式:

    var model = new LoginViewModel()
    {
        UserName = userName,
        Password = password
    };
    var json = JsonConvert.SerializeObject(model);
    
    var res = await HttpHelper.DoJsonPostAsync(LoginUrl, json);
    if (res.IsSuccessStatusCode)
    {
                        
        var tokenJson = await res.Content.ReadAsStringAsync();
        var complexToken = JsonConvert.DeserializeObject<ComplexTokenViewModel>(tokenJson);
        var lm = new LoginMessage()
        {
            AccessToken = complexToken.AccessToken.Token,
            AccessTokenExpires = complexToken.AccessToken.Expires,
            RefreshToken = complexToken.RefreshToken.Token,
            RefreshTokenExpires = complexToken.RefreshToken.Expires,
            UserName = complexToken.UserName
        };
        loginMessage = lm;
        MessageBox.Show("登录成功,欢迎您:" + complexToken.UserName);
        this.DialogResult = DialogResult.OK;
        this.Close();
    }
    else if (res.StatusCode == System.Net.HttpStatusCode.NotFound)
    {
        MessageBox.Show("无法找到服务器!", "系统错误", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
    }
    else if (res.StatusCode == System.Net.HttpStatusCode.BadRequest)
    {
        MessageBox.Show(await res.Content.ReadAsStringAsync());//登录的错误信息API通过BadRequest发送
    }
    else
    {
        MessageBox.Show(res.StatusCode.ToString());
    }

    主要方法是官方文档的《使用 ASP.NET Core 中的特定方案授权》可以配置多种授权方式

  • 相关阅读:
    DNS原理总结及其解析过程详解
    linux修改进程名
    mq_open失败,Invalid argument
    Posix消息队列
    undefined reference to 'mq_open'
    量化投资学习笔记08——统计学基础补漏
    量化投资学习笔记07——python知识补漏
    量化投资学习笔记06——《打开量化投资的黑箱》读书笔记
    量化投资学习笔记05——检验计算回测指标程序
    量化投资学习笔记04——回测实盘策略
  • 原文地址:https://www.cnblogs.com/ANPY/p/12486208.html
Copyright © 2011-2022 走看看