.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 中的特定方案授权》可以配置多种授权方式

原文地址:https://www.cnblogs.com/ANPY/p/12486208.html