c# - JWT 与 C# 与通用存储库模式
问题描述
我的一个同事和我一直在做一个学校项目,我们在将 JWT 令牌添加到我们的 Web 服务时遇到了问题。我们试图重新创建一个教程,但我们正在使用带有工作单元类的通用存储库模式。我们试图重新创建的教程是Jason Watmore的教程。然而,为了忠实于存储库模式,当已经有一个本质上旨在做同样事情的 UserRepository 时,我们认为创建 IUserService 和 UserService 没有多大意义。我们的 UserRepository 现在看起来像这样:
public class UserRepository : Repository<User>, IUserRepository
{
public UserRepository(JellyContext context)
: base(context)
{
}
public JellyContext DbContext
{
get { return _context as JellyContext; }
}
public User Create(User user, string password)
{
// validation
if (string.IsNullOrWhiteSpace(password))
throw new AppException("Password is required");
if (_context.Users.Any(x => x.Username == user.Username))
throw new AppException("Username " + "user.Username "+ "is already taken");
byte[] passwordHash, passwordSalt;
CreatePasswordHash(password, out passwordHash, out passwordSalt);
user.PasswordHash = passwordHash;
user.PasswordSalt = passwordSalt;
_context.Users.Add(user);
_context.SaveChanges();
return user;
}
public User Authenticate(string username, string password)
{
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
return null;
User user = _context.Users.SingleOrDefault(x => x.Username == username);
// check if username exists
if (user == null)
return null;
// check if password is correct
if (!VerifyPasswordHash(password, user.PasswordHash, user.PasswordSalt))
return null;
// authentication successful
return user;
}
private static bool VerifyPasswordHash(string password, byte[] storedHash, byte[] storedSalt)
{
if (password == null) throw new ArgumentNullException("password");
if (string.IsNullOrWhiteSpace(password)) throw new ArgumentException("Value cannot be empty or whitespace only string.", "password");
if (storedHash.Length != 64) throw new ArgumentException("Invalid length of password hash (64 bytes expected).", "passwordHash");
if (storedSalt.Length != 128) throw new ArgumentException("Invalid length of password salt (128 bytes expected).", "passwordHash");
using (var hmac = new System.Security.Cryptography.HMACSHA512(storedSalt))
{
var computedHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
for (int i = 0; i < computedHash.Length; i++)
{
if (computedHash[i] != storedHash[i]) return false;
}
}
return true;
}
private static void CreatePasswordHash(string password, out byte[] passwordHash, out byte[] passwordSalt)
{
if (password == null) throw new ArgumentNullException("password");
if (string.IsNullOrWhiteSpace(password)) throw new ArgumentException("Value cannot be empty or whitespace only string.", "password");
using (var hmac = new System.Security.Cryptography.HMACSHA512())
{
passwordSalt = hmac.Key;
passwordHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
}
}
public async Task<IEnumerable<Project>> getProjectsFromUser(int userId)
{
List<UserProject> uP = await DbContext.Set<UserProject>().Where(c => c.FKEY_User == userId).ToListAsync();
IList<Project> projects = new List<Project>();
foreach (UserProject item in uP)
{
Project project = await DbContext.Set<Project>().SingleOrDefaultAsync(c => c.ID == item.FKEY_Project);
if (project != null)
{
projects.Add(project);
}
}
return projects;
}
public async Task<IEnumerable<Hour>> getHoursFromUser(int userId)
{
return await DbContext.Set<Hour>().Where(c => c.FKEY_User == userId).ToListAsync();
}
//example method implementation:
//public IEnumerable<User> GetUserHours(int id){
// return DbContext.User.Where(x => x.Irgendwas == soundsowoascheh);
//}
}
UserController 中的实现如下所示:
[AllowAnonymous]
[HttpPost("authenticate")]
public IActionResult Authenticate([FromBody]AuthenticateModel model)
{
User user = _unitOfWork.User.Authenticate(model.Username, model.Password);
if (user == null) return BadRequest(new { message = "Username or password is incorrect" });
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[] {
new Claim(ClaimTypes.Name, user.ID.ToString())
}),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
return Ok(new
{
ID = user.ID,
Username = user.Username,
Email = user.Email,
Token = tokenString
});
}
[AllowAnonymous]
[HttpPost("register")]
public IActionResult Register([FromBody]RegisterModel model)
{
// map model to entity
var user = _mapper.Map<User>(model);
try
{
// create user
//_unitOfWork.User.Create(user, model.Password);
_unitOfWork.User.Create(user, model.Password);
return Ok();
}
catch (AppException ex)
{
// return error message if there was an exception
return BadRequest(new { message = ex.Message });
}
}
该解决方案无法编译但会引发错误,我们不太确定原因。我们的 Startup 类如下所示:
public class Startup
{
private readonly IConfiguration _configuration;
public Startup(IWebHostEnvironment env, IConfiguration configuration)
{
_configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<JellyContext>(opts => opts.UseLazyLoadingProxies().UseMySql(Configuration["ConnectionString:JellyDB"]));
services.AddScoped<IUnitOfWork, UnitOfWork>();
services.AddCors();
services.AddControllersWithViews();
services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
var appSettingSection = _configuration.GetSection("AppSettings");
services.Configure<AppSettings>(appSettingSection);
var appSettings = appSettingSection.Get<AppSettings>();
var key = Encoding.ASCII.GetBytes(appSettings.Secret);
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
var userService = context.HttpContext.RequestServices.GetRequiredService<IUnitOfWork>();
var userId = int.Parse(context.Principal.Identity.Name);
var user = userService.User.Get(userId);
if (user == null)
{
context.Fail("Unauthorized");
}
return Task.CompletedTask;
}
};
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "JellyAPI", Version = "v1" });
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
});
services.AddControllers().AddNewtonsoftJson(options =>
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "JellyAPI v1");
});
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseRouting();
app.UseCors(x => x
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
}
}
抛出的错误与 AutoMapper 有关,它说
System.Reflection.ReflectionTypeLoadException: 'Unable to load one or more of the requested types.
Could not load file or assembly 'Microsoft.EntityFrameworkCore.Relational, Version=3.1.3.0, Culture=neutral, PublicKeyToken=sometoken'. The system cannot find the file specified.
Could not load file or assembly 'Microsoft.EntityFrameworkCore.Relational, Version=3.1.3.0, Culture=neutral, PublicKeyToken=sometoken'. The system cannot find the file specified.'
解决方案
尝试Microsoft.EntityFrameworkCore
使用 Nuget 包管理器安装到项目中
https://www.nuget.org/packages/Microsoft.EntityFrameworkCore
推荐阅读
- java - 尝试在 Selenium 中通过 Java 使用 PageFactory 时出现异常
- java - 如何处理 Maven POM.xml 中属性的区分大小写
- java - 我的 Java 程序出现 FileSystemException 错误
- jquery - Rails JQuery Ajax:刷新表行抛出404错误
- ios - 如何从 swift 4.2 中的通知传递 url 以在 webview 中打开
- wordpress - 搜索多个日期 ACF
- types - 有界参数多态性与临时多态性
- sql - Laravel 查询 sql
- c - 嵌套 For 循环来发牌
- python - 从另一个函数调用字典