Asp.Net Core 6 之 基于Jwt 的身份认证

程序集

Microsoft.AspNetCore.Authentication.JwtBearer;

身份认证服务器

jwt配置类: JWTTokenOptions.cs

public class JWTTokenOptions
    {
        public string Audience
        {
            get;
            set;
        }
        public string SecurityKey
        {
            get;
            set;
        }
        //public SigningCredentials Credentials
        //{
        //    get;
        //    set;
        //}
        public string Issuer
        {
            get;
            set;
        }
    }

apppsetting.json


  "JWTTokenOptions": {
    "Audience": "http://localhost:5200",
    "Issuer": "http://localhost:5200",
    "SecurityKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADxxxx"
  }

生成 token

     [Route("api/[controller]")]
    [ApiController]
    public class AuthenticationController : ControllerBase
    {
        private ICustomJWTService _iJWTService = null;
        public AuthenticationController(ICustomJWTService customJWTService)
        {
            _iJWTService = customJWTService;
        }

        [Route("Login")]
        [HttpPost]
        public string Login(string name, string password)
        {
            //在这里需要去数据库中做数据验证
            if ("admin".Equals(name) && "123456".Equals(password))
            {
                //就应该生成Token 
                string token = this._iJWTService.GetToken(name, password);
                return JsonConvert.SerializeObject(new
                {
                    result = true,
                    token
                });

            }
            else
            {
                return JsonConvert.SerializeObject(new
                {
                    result = false,
                    token = ""
                });
            }

对称加密

using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

    public class CustomHSJWTService : ICustomJWTService
    {
        #region Option注入
        private readonly JWTTokenOptions _JWTTokenOptions;
        public CustomHSJWTService(IOptionsMonitor<JWTTokenOptions> jwtTokenOptions)
        {
            this._JWTTokenOptions = jwtTokenOptions.CurrentValue;
        }
        #endregion
        /// <summary>
        /// 用户登录成功以后,用来生成Token的方法
        /// </summary>
        /// <param name="UserName"></param>
        /// <returns></returns>
        public string GetToken(string UserName, string password)
        {
            #region 有效载荷,大家可以自己写,爱写多少写多少;尽量避免敏感信息
            var claims = new[]
            {
               new Claim(ClaimTypes.Name, UserName),
                new Claim(ClaimTypes.Role, "teache0"),
               new Claim("NickName",UserName),
               new Claim("Role","Administrator"),//传递其他信息   
               new Claim("ABCC","ABCC"),
               new Claim("Student","甜酱油")
            };

            //需要加密:需要加密key:
            //Nuget引入:Microsoft.IdentityModel.Tokens
            SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_JWTTokenOptions.SecurityKey));

            SigningCredentials creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

            //Nuget引入:System.IdentityModel.Tokens.Jwt
            JwtSecurityToken token = new JwtSecurityToken(
             issuer: _JWTTokenOptions.Issuer,
             audience: _JWTTokenOptions.Audience,
             claims: claims,
             expires: DateTime.Now.AddMinutes(5),//5分钟有效期
             signingCredentials: creds);

            string returnToken = new JwtSecurityTokenHandler().WriteToken(token);
            return returnToken;
            #endregion

        }
    }

非对称加密

 public class CustomRSSJWTervice : ICustomJWTService
 {
        #region Option注入
        private readonly JWTTokenOptions _JWTTokenOptions;
        public CustomRSSJWTervice(IOptionsMonitor<JWTTokenOptions> jwtTokenOptions)
        {
            this._JWTTokenOptions = jwtTokenOptions.CurrentValue;
        }
        #endregion

        public string GetToken(string userName, string password)
        {
            #region 使用加密解密Key  非对称 
            string keyDir = Directory.GetCurrentDirectory();
            if (RSAHelper.TryGetKeyParameters(keyDir, true, out RSAParameters keyParams) == false)
            {
                keyParams = RSAHelper.GenerateAndSaveKey(keyDir);
            }
            #endregion

            //string jtiCustom = Guid.NewGuid().ToString();//用来标识 Token
            Claim[] claims = new[]
            {
                   new Claim(ClaimTypes.Name, userName),
                   new Claim(ClaimTypes.Role,"admin"),
                   new Claim("password",password)
            };

            SigningCredentials credentials = new SigningCredentials(new RsaSecurityKey(keyParams), SecurityAlgorithms.RsaSha256Signature);

            var token = new JwtSecurityToken(
               issuer: this._JWTTokenOptions.Issuer,
               audience: this._JWTTokenOptions.Audience,
               claims: claims,
               expires: DateTime.Now.AddMinutes(60),//5分钟有效期
               signingCredentials: credentials);

            var handler = new JwtSecurityTokenHandler();
            string tokenString = handler.WriteToken(token);
            return tokenString;
        }
    }

受保护WebApi

执行身份验证

Program.cs

//跨域
builder.Services.AddCors(policy =>
{
policy.AddPolicy("CorsPolicy", opt => opt
.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod()
.WithExposedHeaders("X-Pagination"));
});

{
    #region jwt校验  HS(对称加密)
    {
        //第二步,增加鉴权逻辑
        JWTTokenOptions tokenOptions = new JWTTokenOptions();
        builder.Configuration.Bind("JWTTokenOptions", tokenOptions);
        builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)//Scheme
         .AddJwtBearer(options =>  //这里是配置的鉴权的逻辑
         {
             options.TokenValidationParameters = new TokenValidationParameters
             {
                 //JWT有一些默认的属性,就是给鉴权时就可以筛选了
                 ValidateIssuer = true,//是否验证Issuer
                 ValidateAudience = true,//是否验证Audience
                 ValidateLifetime = true,//是否验证失效时间
                 ValidateIssuerSigningKey = true,//是否验证SecurityKey
                 ValidAudience = tokenOptions.Audience,//
                 ValidIssuer = tokenOptions.Issuer,//Issuer,这两项和前面签发jwt的设置一致
                 IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(tokenOptions.SecurityKey))//拿到SecurityKey 
             };

             options.Events = new JwtBearerEvents
             {
                 //通过身份验证后,回调
                 //可以在这里进行登录,设置 HttpContext.Principal
                 OnTokenValidated = async ctx =>
                 {
                     string userName = ctx.Principal.FindFirst(ClaimTypes.Name)?.Value;
                     /*
                     string userName = ctx.Principal.FindUserByUserName(userName);
                     User user = FindFromDb()
                     ctx.Principal = 
                     */

                     var claims = new List<Claim>()//鉴别你是谁,相关信息
                    {
                        new Claim(ClaimTypes.Name,userName),
 
                        new Claim("Userid","1"),
                        new Claim(ClaimTypes.Role,"Admin"),
                        new Claim(ClaimTypes.Role,"User"),
                        new Claim(ClaimTypes.Email,$"xxxx@163.com"),
                        new Claim("password",password),//可以写入任意数据
                        new Claim("Account","Administrator"),
                        new Claim("role","admin"),
                        new Claim("QQ","xxxx")
                    };
                     ClaimsPrincipal userPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, "Customer"));
                     ctx.Principal = userPrincipal;
                 }
             };
         });

        
    }
    #endregion
}


var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

重点: 使用

options.Events = new JwtBearerEvents
             {
                 //通过身份验证后,回调
                 //可以在这里进行登录,设置 HttpContext.Principal
                 OnTokenValidated = async ctx =>
                 {
                     string userName = ctx.Principal.FindFirst(ClaimTypes.Name)?.Value;
                     /*
                     string userName = ctx.Principal.FindUserByUserName(userName);
                     User user = FindFromDb()
                     ctx.Principal = 
                     */

                     var claims = new List<Claim>()//鉴别你是谁,相关信息
                    {
                        new Claim(ClaimTypes.Name,userName),
 
                        new Claim("Userid","1"),
                        new Claim(ClaimTypes.Role,"Admin"),
                        new Claim(ClaimTypes.Role,"User"),
                        new Claim(ClaimTypes.Email,$"xxxx@163.com"),
                        new Claim("password",password),//可以写入任意数据
                        new Claim("Account","Administrator"),
                        new Claim("role","admin"),
                        new Claim("QQ","xxxx")
                    };
                     ClaimsPrincipal userPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, "Customer"));
                     ctx.Principal = userPrincipal;
                 }

在通过身份验证后,回调设置当前用户信息,

     ClaimsPrincipal userPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, "Customer"));
     ctx.Principal = userPrincipal;

以便无缝对接当前基于用户 Claime 设置的其它授权策略,方案等。
相当于 使用 token 身份验证成功后,再执行 UserName + Password 登录的逻辑.
当然,这一步根据实际情况取舍。

保护端点,Contronller或 Action

   public class SecondController : ControllerBase
   { 
        [HttpGet]
        [Authorize(AuthenticationSchemes= JwtBearerDefaults.AuthenticationScheme)]
        public object GetData()
        {
            Console.WriteLine("请求到了~~");
            return new
            {
                Id = 123,
                Name = "Richard"
            };
        }

Artisan.Zo 中的示例

配置类

JwtOptions

    public class JwtOptions
    {
        public string Audience { get; set; }
        public string SecurityKey { get; set; }
        public string Issuer { get; set; }
        public int ExpirationTime { get; set; }
        public bool RequireHttps { get; set; } 
        

Artisan.Zo.Applicaiton

AccountAppService.cs

using Artisan.Zo.Auths.Jwt;
using Artisan.Zo.Dtos;
using IdentityModel;
using Microsoft.Extensions.Options;
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;
using Volo.Abp;
using Volo.Abp.Identity;
using Volo.Abp.Security.Claims;
using AspNetCore = Microsoft.AspNetCore.Identity;

namespace Artisan.Zo.Account
{
    public class AccountAppService : ZoAppServiceBase, IAccountAppService
    {
        private readonly IdentityUserManager userManager;
        private readonly AspNetCore.SignInManager<IdentityUser> signInManager;

        //jwt
        private readonly JwtOptions jwtOptions;
        private readonly IJwtGenerator jwtGenerator;

        public AccountAppService(
            IdentityUserManager userManager,
            AspNetCore.SignInManager<IdentityUser> signInManager, 
            IOptionsSnapshot<JwtOptions> jwtOptions)
        {
            this.userManager = userManager;
            this.signInManager = signInManager;
            this.jwtOptions = jwtOptions.Value;
        }

        public virtual async Task<LoginDto> LoginAsync(LoginInput input)
        {
            var result = await signInManager.PasswordSignInAsync(input.Name, input.Password, false, true);
            if (result.Succeeded)
            {
                var user = await userManager.FindByNameAsync(input.Name);
                return await BuildLoginResult(user);
            }
            else
            {
                if (result.IsLockedOut)
                {
                    throw new UserFriendlyException(L["DisplayName:LockoutEnabled"]);
                }else 
                {
                    throw new UserFriendlyException(L["InvalidUserNameOrPassword"]);
                }
            }
        }


        private async Task<LoginDto> BuildLoginResult(IdentityUser user)
        {
            if (user.LockoutEnabled) {
                throw new UserFriendlyException(L["DisplayName:LockoutEnabled"]);
            } 
            var roles = await userManager.GetRolesAsync(user);
            var token = jwtGenerator.GenerateToken(new CreateJwtTokenInput { IdentityUser = user, JwtOptions = jwtOptions, Roles = roles});
           
            var LoginDto = ObjectMapper.Map<IdentityUser, LoginDto>(user);
            LoginDto.Token = token;
            LoginDto.Roles = roles; 

            return LoginDto;
        }
    }
}

JwtHsGenerator.cs 对称加密算法token

using IdentityModel;
using Microsoft.Extensions.Options;
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 Volo.Abp.Identity;
using Volo.Abp.Security.Claims;

namespace Artisan.Zo.Auths.Jwt
{
    /// <summary>
    /// 对称加密
    /// </summary>
    public class JwtHsGenerator : IJwtGenerator
    {
        public string GenerateToken(CreateJwtTokenInput input)
        {
            IdentityUser user = input.IdentityUser;
            IList<string> roles = input.Roles;
            JwtOptions jwtOptions = input.JwtOptions;

            var dateNow = DateTime.Now;
            var expirationTime = dateNow + TimeSpan.FromHours(jwtOptions.ExpirationTime);
            var key = Encoding.ASCII.GetBytes(jwtOptions.SecurityKey);
            var claims = new List<Claim>
            {
                new Claim(ClaimTypes.Name, user.UserName),
            };

            var tokenDescriptor = new SecurityTokenDescriptor()
            {
                Subject = new ClaimsIdentity(claims),
                Expires = expirationTime,
                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
            };

            var handler = new JwtSecurityTokenHandler();
            var token = handler.CreateToken(tokenDescriptor);

            return handler.WriteToken(token);
        }

        //public string GenerateToken(CreateJwtTokenInput input)
        //{
        //    IdentityUser user = input.IdentityUser;
        //    IList<string> roles = input.Roles;
        //    JwtOptions jwtOptions = input.JwtOptions;

        //    var dateNow = DateTime.Now;
        //    var expirationTime = dateNow + TimeSpan.FromHours(jwtOptions.ExpirationTime);
        //    var key = Encoding.ASCII.GetBytes(jwtOptions.SecurityKey);

        //    var claims = new List<Claim>
        //    {
        //        new Claim(JwtClaimTypes.Audience, jwtOptions.Audience),
        //        new Claim(JwtClaimTypes.Issuer, jwtOptions.Issuer),
        //        new Claim(AbpClaimTypes.UserId, user.Id.ToString()),
        //        new Claim(AbpClaimTypes.Name, user.Name),
        //        new Claim(AbpClaimTypes.UserName, user.UserName),
        //        new Claim(AbpClaimTypes.Email, user.Email),
        //        new Claim(AbpClaimTypes.TenantId, user.TenantId.ToString())
        //    };

        //    if (roles != null && roles.Any())
        //    {
        //        foreach (var item in roles)
        //        {
        //            claims.Add(new Claim(JwtClaimTypes.Role, item));
        //        }
        //    }

        //    var tokenDescriptor = new SecurityTokenDescriptor()
        //    {
        //        Subject = new ClaimsIdentity(claims),
        //        Expires = expirationTime,
        //        SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
        //    };
        //    var handler = new JwtSecurityTokenHandler();
        //    var token = handler.CreateToken(tokenDescriptor);

        //    return handler.WriteToken(token);
        //}

    }
}

WebApi.Host

appsetting.json

  "Auth": {
    "Jwt": {
      //客户端标识
      "Audience": "ArtisanZoApiHost",
      "SecurityKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNAD=",
      //签发者
      "Issuer": "ArtisanZo",
      //过期时间,单位:小时
      "ExpirationTime": 24,
      "RequireHttps": false
    }

        private void ConfigureOptions(ServiceConfigurationContext context, IConfiguration configuration)
        {
            context.Services.Configure<JwtOptions>(context.Services.GetConfiguration()
                .GetSection("Auth:Jwt"));
        }

       private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
        {
            //
            //context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            //    .AddJwtBearer(options =>
            //    {
            //        options.Authority = configuration["AuthServer:Authority"];
            //        options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]);
            //        options.Audience = "Zo";
            //    });

            var jwtOption = configuration.GetSection("Auth:Jwt").Get<JwtOptions>();

            context.Services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(options =>{
                options.RequireHttpsMetadata = jwtOption.RequireHttps;
                options.SaveToken = true;
                options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidateLifetime = true,
                    //ClockSkew = TimeSpan.Zero,
                    ValidIssuer = jwtOption.Issuer,
                    ValidAudience = jwtOption.Audience,
                    IssuerSigningKey =
                                new SymmetricSecurityKey(
                                    Encoding.ASCII.GetBytes(jwtOption.SecurityKey))
                };
                options.Events = new JwtBearerEvents
                {
                    OnTokenValidated = async ctx =>
                    {
                        var services = ctx.HttpContext.RequestServices;
                        var userManager = services.GetRequiredService<IdentityUserManager>();
                        var signInManager = services.GetRequiredService<AbpSignInManager>();

                        string name = ctx.Principal.FindFirst(ClaimTypes.Name)?.Value;
                        IdentityUser identityUser = await userManager.FindByNameAsync(name);
                        //构建jwt令牌和AspNet Core Identity数据的桥梁,
                        //确保像基于角色的授权这样的特性无缝地工作
                        ctx.Principal = await signInManager.CreateUserPrincipalAsync(identityUser);
                    }
                };
            });

        }

posted @ 2021-12-19 01:07  easy5  阅读(2552)  评论(0编辑  收藏  举报