ASP.NET 核心身份 [Authorize(Roles ="ADMIN")] 无效
ASP.NET Core Identity [Authorize(Roles ="ADMIN")] not work
我正在使用 .NET 版本 5.0.100-rc.1.20452.10
,ASP.NET Core Web API,Microsoft SQL Server 2019,JWT 令牌。我有 Startup.cs
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using shadow.Data;
using shadow.Models;
using shadow.Services;
using System.IO;
using System.Text;
namespace shadow
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddHttpContextAccessor();
//services.AddCors();
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
});
services.AddSingleton<IUriService>(o =>
{
var accessor = o.GetRequiredService<IHttpContextAccessor>();
var request = accessor.HttpContext.Request;
var uri = string.Concat(request.Scheme, "://", request.Host.ToUriComponent());
return new UriService(uri);
});
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
//options.Password.RequireDigit = true;
//options.Password.RequireLowercase = true;
options.Password.RequiredLength = 6;
}).AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();
services
.AddAuthentication(auth =>
{
auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = Configuration["AuthSettings:Audience"],
ValidIssuer = Configuration["AuthSettings:Issuer"],
RequireExpirationTime = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["AuthSettings:Key"])),
ValidateIssuerSigningKey = true
};
});
services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
services.AddScoped<IUserService, UserService>();
services.AddTransient<IMailService, SendGridMailService>();
services.AddControllers();
services.AddRazorPages();
services.AddControllers(options => options.SuppressAsyncSuffixInActionNames = false);
services.AddMvc().AddJsonOptions(options =>
{
//options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
options.JsonSerializerOptions.PropertyNamingPolicy = null;
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
//app.UseHttpsRedirection();
app.UseRouting();
// app.UseCors(options => options.WithOrigins("http://localhost:4200")
//.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
app.UseCors("CorsPolicy");
app.UseAuthentication();
app.UseAuthorization();
// Server: https://shorten.news/static-file/content/10992.mp3
// Local: http://localhost:5000/static-file/content/10992.mp3
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
// Path.Combine(env.ContentRootPath, @"c:\audio\")),
// Path.Combine(@"c:\audio\")),
Path.Combine(@"D:\shadow_backend\Upload\files\")
),
RequestPath = "/static-file",
OnPrepareResponse = context =>
{
context.Context.Response.Headers["Access-Control-Allow-Origin"] = "*";
}
});
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.Run(async (context) =>
{
await context.Response.WriteAsync("cound not find anything");
});
}
}
}
文件IUserService.cs
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using shadow.Models;
using shadow.Shared;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
namespace shadow.Services
{
public interface IUserService
{
Task<UserManagerResponse> RegisterUserAsync(RegisterViewModel model);
Task<UserManagerResponse> LoginUserAsync(LoginViewModel model);
Task<UserManagerResponse> LogoutUserAsync(LoginViewModel model);
Task<UserManagerResponse> ConfirmEmailAsync(string userId, string token);
Task<UserManagerResponse> ForgetPasswordAsync(string email);
Task<UserManagerResponse> ResetPasswordAsync(ResetPasswordViewModel model);
Task<UserManagerResponse> ChangePasswordAsync(ChangePasswordViewModel model);
}
public class UserService : IUserService
{
private Microsoft.AspNetCore.Identity.UserManager<ApplicationUser> _userManger;
private IConfiguration _configuration;
private IMailService _mailService;
public UserService(Microsoft.AspNetCore.Identity.UserManager<ApplicationUser> userManager, IConfiguration configuration, IMailService mailService)
{
_userManger = userManager;
_configuration = configuration;
_mailService = mailService;
}
public async Task<UserManagerResponse> RegisterUserAsync(RegisterViewModel model)
{
if (model == null)
{
throw new NullReferenceException("Reigster Model is null");
}
if (model.Password != model.ConfirmPassword)
{
return new UserManagerResponse
{
Message = "Confirm password doesn't match the password",
IsSuccess = false,
};
}
var identityUser = new ApplicationUser
{
Email = model.Email,
UserName = model.Email,
About = model.About,
SecondMobile = model.SecondMobile,
Fullname = model.Fullname,
AliasName = model.AliasName,
Created = DateTime.Now,
Modified = DateTime.Now
};
var result = await _userManger.CreateAsync(identityUser, model.Password);
if (result.Succeeded)
{
var confirmEmailToken = await _userManger.GenerateEmailConfirmationTokenAsync(identityUser);
var encodedEmailToken = Encoding.UTF8.GetBytes(confirmEmailToken);
var validEmailToken = WebEncoders.Base64UrlEncode(encodedEmailToken);
string url = $"{_configuration["AppUrl"]}/api/auth/ConfirmEmail?userId={identityUser.Id}&token={validEmailToken}";
await _mailService.SendEmailAsync(identityUser.Email, "Confirm your email", $"<h1>Welcome to Trustee app</h1>" +
$"<p>Please confirm your email by <a href='{url}'>clicking here</a></p>");
return new UserManagerResponse
{
Message = "User created successfully!",
IsSuccess = true,
};
}
return new UserManagerResponse
{
Message = "User did not create",
IsSuccess = false,
Errors = result.Errors.Select(e => e.Description)
};
}
/// <summary>
/// Đăng nhập.
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
public async Task<UserManagerResponse> LoginUserAsync(LoginViewModel model)
{
var user = await _userManger.FindByEmailAsync(model.Email);
if (user == null)
{
return new UserManagerResponse
{
Message = "There is no user with that Email address",
IsSuccess = false,
};
}
var result = await _userManger.CheckPasswordAsync(user, model.Password);
if (!result)
{
return new UserManagerResponse
{
Message = "Invalid password",
IsSuccess = false,
};
}
var claims = new[]
{
new Claim("Email", model.Email),
new Claim(ClaimTypes.NameIdentifier, user.Id),
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["AuthSettings:Key"]));
var token = new JwtSecurityToken(
issuer: _configuration["AuthSettings:Issuer"],
audience: _configuration["AuthSettings:Audience"],
claims: claims,
expires: DateTime.Now.AddDays(30),
signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256));
string tokenAsString = new JwtSecurityTokenHandler().WriteToken(token);
return new UserManagerResponse
{
Message = tokenAsString,
IsSuccess = true,
ExpireDate = token.ValidTo
};
}
// Đăng xuất.
public async Task<UserManagerResponse> LogoutUserAsync(LoginViewModel model)
{
var user = await _userManger.FindByEmailAsync(model.Email);
if (user == null)
{
return new UserManagerResponse
{
Message = "There is no user with that Email address",
IsSuccess = false,
};
}
var result = await _userManger.CheckPasswordAsync(user, model.Password);
if (!result)
{
return new UserManagerResponse
{
Message = "Invalid password",
IsSuccess = false,
};
}
var claims = new[]
{
new Claim("Email", model.Email),
new Claim(ClaimTypes.NameIdentifier, user.Id),
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["AuthSettings:Key"]));
var token = new JwtSecurityToken(
issuer: _configuration["AuthSettings:Issuer"],
audience: _configuration["AuthSettings:Audience"],
claims: claims,
expires: DateTime.Now.AddDays(30),
signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256));
string tokenAsString = new JwtSecurityTokenHandler().WriteToken(token);
return new UserManagerResponse
{
Message = tokenAsString,
IsSuccess = true,
ExpireDate = token.ValidTo
};
}
public async Task<UserManagerResponse> ConfirmEmailAsync(string userId, string token)
{
var user = await _userManger.FindByIdAsync(userId);
if (user == null)
{
return new UserManagerResponse { IsSuccess = false, Message = "User not found" };
}
var decodedToken = WebEncoders.Base64UrlDecode(token);
string normalToken = Encoding.UTF8.GetString(decodedToken);
var result = await _userManger.ConfirmEmailAsync(user, normalToken);
if (result.Succeeded)
{
return new UserManagerResponse { Message = "Email confirmed successfully!", IsSuccess = true };
}
return new UserManagerResponse
{
IsSuccess = false,
Message = "Email did not confirm",
Errors = result.Errors.Select(e => e.Description)
};
}
public async Task<UserManagerResponse> ForgetPasswordAsync(string email)
{
var user = await _userManger.FindByEmailAsync(email);
if (user == null)
{
return new UserManagerResponse { IsSuccess = false, Message = "No user associated with email", };
}
var token = await _userManger.GeneratePasswordResetTokenAsync(user);
var encodedToken = Encoding.UTF8.GetBytes(token);
var validToken = WebEncoders.Base64UrlEncode(encodedToken);
string url = $"{_configuration["AppUrl"]}/ResetPassword?email={email}&token={validToken}";
await _mailService.SendEmailAsync(email, "Reset Password", "<h1>Follow the instructions to reset your password</h1>" +
$"<p>To reset your password <a href='{url}'>Click here</a></p>");
return new UserManagerResponse
{
IsSuccess = true,
Message = "Reset password URL has been sent to the email successfully!"
};
}
public async Task<UserManagerResponse> ResetPasswordAsync(ResetPasswordViewModel model)
{
var user = await _userManger.FindByEmailAsync(model.Email);
if (user == null)
{
return new UserManagerResponse { IsSuccess = false, Message = "No user associated with email", };
}
if (model.NewPassword != model.ConfirmPassword)
{
return new UserManagerResponse { IsSuccess = false, Message = "Password doesn't match its confirmation", };
}
var decodedToken = WebEncoders.Base64UrlDecode(model.Token);
string normalToken = Encoding.UTF8.GetString(decodedToken);
var result = await _userManger.ResetPasswordAsync(user, normalToken, model.NewPassword);
if (result.Succeeded)
{
return new UserManagerResponse { Message = "Password has been reset successfully!", IsSuccess = true };
}
return new UserManagerResponse
{
Message = "Something went wrong",
IsSuccess = false,
Errors = result.Errors.Select(e => e.Description)
};
}
public async Task<UserManagerResponse> ChangePasswordAsync(ChangePasswordViewModel model)
{
var tokenString = model.Token;
var jwtEncodedString = tokenString.Substring(7); // trim 'Bearer ' from the start since its just a prefix for the token string
var token = new JwtSecurityToken(jwtEncodedString: jwtEncodedString);
string email = token.Claims.First(c => c.Type == "Email").Value;
Console.WriteLine("email => " + email);
var user = await _userManger.FindByEmailAsync(email);
if (user == null)
{
return new UserManagerResponse { IsSuccess = false, Message = "No user associated with email", };
}
if (model.NewPassword != model.ConfirmPassword)
{
return new UserManagerResponse { IsSuccess = false, Message = "Password doesn't match its confirmation", };
}
var token2 = await _userManger.GeneratePasswordResetTokenAsync(user);
var result = await _userManger.ResetPasswordAsync(user, token2, model.NewPassword);
if (result.Succeeded)
{
return new UserManagerResponse { Message = "Password has been changed successfully!", IsSuccess = true };
}
return new UserManagerResponse
{
Message = "Something went wrong",
IsSuccess = false,
Errors = result.Errors.Select(e => e.Description)
};
}
}
}
文件appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "Server=.;Database=foo;User Id=sa; Password=SecrEt_STring;Trusted_Connection=False;MultipleActiveResultSets=True"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"AuthSettings": {
"Key": "This is the key that we will use in the encryption",
"Audience": "http://example.io",
"Issuer": "http://example.io"
},
"SendGridAPIKey": "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",
"AppUrl": "http://localhost:5002"
}
在控制器中
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using shadow.Data;
using shadow.DTO;
using shadow.Models;
using shadow.Services;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace shadow.Controllers
{
[Route("[controller]")]
[ApiController]
public class AssetItemController : ControllerBase
{
private IUserService _userService;
private IMailService _mailService;
private IConfiguration _configuration;
private ApplicationDbContext _db;
public AssetItemController(IUserService userService, IMailService mailService, IConfiguration configuration, ApplicationDbContext context /*, SignInManager<IdentityUser> signInManager */)
{
_userService = userService;
_mailService = mailService;
_configuration = configuration;
_db = context;
// _signInManager = signInManager;
}
// Admin liệt kê tất cả các tài sản.
[HttpGet]
[Route("all")]
[Authorize(Roles ="ADMIN")]
public async Task<ActionResult<AssetItem>> GetAllAssetItems()
{
var a = await Task.Run(() => _db.AssetItems);
if (a != null)
{
return Ok(a.ToList());
}
else
{
return NoContent();
}
}
// Liệt kê tất cả các tài sản theo user_id.
[HttpGet]
[Route("user/{userId}")]
public async Task<ActionResult<IEnumerable<AssetItem>>> GetAllAssetItemsByUserId(string userId)
{
var assetListByUserId = await Task.Run(() => _db.AssetItems.Where(x => x.UserId == userId).ToList());
if (assetListByUserId != null)
{
return Ok(assetListByUserId);
}
else
{
return NoContent();
}
}
// Xem chi tiet asset_item theo id cua no.
[HttpGet]
[Route("{assetItemId}")]
public async Task<ActionResult<IEnumerable<AssetItem>>> ViewAssetItemById(int assetItemId)
{
var assetListByUserId = await Task.Run(() => _db.AssetItems.Where(x => x.Id == assetItemId).FirstOrDefault());
if (assetListByUserId != null)
{
return Ok(assetListByUserId);
}
else
{
return NoContent();
}
}
// Xóa tài sản.
[HttpDelete("{id}")]
public async Task<ActionResult<string>> DeleteAssetItem(int id)
{
var item = _db.AssetItems.Find(id);
if (item != null)
{
_db.AssetItems.Remove(item);
await _db.SaveChangesAsync();
return Ok("ok");
}
else
{
return NotFound();
}
}
// POST http://localhost:5002/AssetItem
[HttpPost]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(string), StatusCodes.Status400BadRequest)]
[Authorize]
public async Task<ActionResult<AssetItem>> Add([FromForm] AssetItemDTO input)
{
if (ModelState.IsValid)
{
AssetItem assetItem = new AssetItem();
if (input.AssetTypeId != null)
{
assetItem.AssetTypeId = input.AssetTypeId;
}
if (input.UserId != null)
{
assetItem.UserId = input.UserId;
}
if (input.TrustedPersonId != null)
{
assetItem.TrustedPersonId = input.TrustedPersonId;
}
if (input.TrustedContent != null)
{
assetItem.TrustedContent = input.TrustedContent;
}
if (input.Description != null)
{
assetItem.Description = input.Description;
}
assetItem.Created = DateTime.Now;
if (input.file != null)
{
if (IsImageFile(input.file))
{
string imgPath = await WriteFile(input.file);
assetItem.ImagePath = imgPath;
}
else
{
return BadRequest(new { message = "Invalid file extension" });
}
}
_db.AssetItems.Add(assetItem);
await _db.SaveChangesAsync();
return Ok(assetItem);
}
else
{
return BadRequest(ModelState);
}
}
// Edit 1 tài sản.
// PUT http://localhost:5002/AssetItem
[HttpPut("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(string), StatusCodes.Status400BadRequest)]
[Authorize]
public async Task<ActionResult<AssetItem>> Edit([FromForm] AssetItemDTO input, int id)
{
if (ModelState.IsValid)
{
var foo = _db.AssetItems.Find(id);
if (!String.IsNullOrEmpty(input.TrustedContent))
{
foo.TrustedContent = input.TrustedContent;
}else
{
foo.TrustedContent = null;
}
if (input.file != null)
{
string imgPath = await WriteFile(input.file);
foo.ImagePath = imgPath;
}
else
{
foo.ImagePath = null;
}
if (!String.IsNullOrEmpty(input.Description))
{
foo.Description = input.Description;
}
else
{
foo.Description = null;
}
foo.AssetTypeId = input.AssetTypeId;
if (input.TrustedPersonId != null)
{
foo.TrustedPersonId = input.TrustedPersonId;
}
// Hiện chưa ghi nhận user_modified. Cần xem lại Requirement.
if (!String.IsNullOrEmpty(input.UserModified))
{
foo.UserModified = input.UserModified;
}
else
{
foo.UserModified = null;
}
foo.Modified = DateTime.Now;
_db.Update(foo);
await _db.SaveChangesAsync();
return Ok(foo);
}
else
{
return BadRequest(ModelState);
}
}
private bool IsImageFile(IFormFile file)
{
try
{
var extension = "." + file.FileName.Split('.')[file.FileName.Split('.').Length - 1];
return (extension == ".png" || extension == ".jpg" || extension == ".bmp" || extension == ".gif" || extension == ".tif");
}
catch
{
return false;
}
}
private async Task<string> WriteFile(IFormFile file)
{
string fileName = "";
try
{
var extension = "." + file.FileName.Split('.')[file.FileName.Split('.').Length - 1];
fileName = DateTime.Now.Ticks + extension; //Create a new Name for the file due to security reasons.
var pathBuilt = Path.Combine(Directory.GetCurrentDirectory(), "Upload\file");
if (!Directory.Exists(pathBuilt))
{
Directory.CreateDirectory(pathBuilt);
}
var path = Path.Combine(Directory.GetCurrentDirectory(), "Upload\files", fileName);
using (var stream = new FileStream(path, FileMode.Create))
{
await file.CopyToAsync(stream);
}
return fileName;
}
catch (Exception e)
{
//
}
return fileName;
}
}
}
数据库
select * from AspNetUsers;
select * from AspNetUserRoles;
select * from AspNetRoles;
结果
加[Authorize(Roles ="ADMIN")]
时显示403 Forbidden
.
如何使按角色授权工作?
您实际上并没有向令牌添加角色。每个角色都应该是一个声明,像这样。
new Claim(ClaimTypes.Role, "<role name">));
例如
var claims = new[]
{
new Claim("Email", model.Email),
new Claim(ClaimTypes.NameIdentifier, user.Id),
};
var roles = await _userManger.GetRolesAsync(user); // note: you have a typo in "_userManger"
var claimsWithRoles = roles.Select(role => new Claim(ClaimTypes.Role, role));
var allClaims = claims.Concat(claimsWithRoles);
然后将allClaims
添加到JWT中,像这样:
var token = new JwtSecurityToken(
issuer: _configuration["AuthSettings:Issuer"],
audience: _configuration["AuthSettings:Audience"],
claims: allClaims , // note how we add all claims, including the ones for roles, here
expires: DateTime.Now.AddDays(30),
signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256));
告诉我它是如何工作的。
编辑:抱歉,忘记了 AddRange
returns void;
我正在使用 .NET 版本 5.0.100-rc.1.20452.10
,ASP.NET Core Web API,Microsoft SQL Server 2019,JWT 令牌。我有 Startup.cs
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using shadow.Data;
using shadow.Models;
using shadow.Services;
using System.IO;
using System.Text;
namespace shadow
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddHttpContextAccessor();
//services.AddCors();
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
});
services.AddSingleton<IUriService>(o =>
{
var accessor = o.GetRequiredService<IHttpContextAccessor>();
var request = accessor.HttpContext.Request;
var uri = string.Concat(request.Scheme, "://", request.Host.ToUriComponent());
return new UriService(uri);
});
services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
//options.Password.RequireDigit = true;
//options.Password.RequireLowercase = true;
options.Password.RequiredLength = 6;
}).AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();
services
.AddAuthentication(auth =>
{
auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = Configuration["AuthSettings:Audience"],
ValidIssuer = Configuration["AuthSettings:Issuer"],
RequireExpirationTime = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["AuthSettings:Key"])),
ValidateIssuerSigningKey = true
};
});
services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
services.AddScoped<IUserService, UserService>();
services.AddTransient<IMailService, SendGridMailService>();
services.AddControllers();
services.AddRazorPages();
services.AddControllers(options => options.SuppressAsyncSuffixInActionNames = false);
services.AddMvc().AddJsonOptions(options =>
{
//options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
options.JsonSerializerOptions.PropertyNamingPolicy = null;
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
//app.UseHttpsRedirection();
app.UseRouting();
// app.UseCors(options => options.WithOrigins("http://localhost:4200")
//.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
app.UseCors("CorsPolicy");
app.UseAuthentication();
app.UseAuthorization();
// Server: https://shorten.news/static-file/content/10992.mp3
// Local: http://localhost:5000/static-file/content/10992.mp3
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
// Path.Combine(env.ContentRootPath, @"c:\audio\")),
// Path.Combine(@"c:\audio\")),
Path.Combine(@"D:\shadow_backend\Upload\files\")
),
RequestPath = "/static-file",
OnPrepareResponse = context =>
{
context.Context.Response.Headers["Access-Control-Allow-Origin"] = "*";
}
});
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.Run(async (context) =>
{
await context.Response.WriteAsync("cound not find anything");
});
}
}
}
文件IUserService.cs
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using shadow.Models;
using shadow.Shared;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
namespace shadow.Services
{
public interface IUserService
{
Task<UserManagerResponse> RegisterUserAsync(RegisterViewModel model);
Task<UserManagerResponse> LoginUserAsync(LoginViewModel model);
Task<UserManagerResponse> LogoutUserAsync(LoginViewModel model);
Task<UserManagerResponse> ConfirmEmailAsync(string userId, string token);
Task<UserManagerResponse> ForgetPasswordAsync(string email);
Task<UserManagerResponse> ResetPasswordAsync(ResetPasswordViewModel model);
Task<UserManagerResponse> ChangePasswordAsync(ChangePasswordViewModel model);
}
public class UserService : IUserService
{
private Microsoft.AspNetCore.Identity.UserManager<ApplicationUser> _userManger;
private IConfiguration _configuration;
private IMailService _mailService;
public UserService(Microsoft.AspNetCore.Identity.UserManager<ApplicationUser> userManager, IConfiguration configuration, IMailService mailService)
{
_userManger = userManager;
_configuration = configuration;
_mailService = mailService;
}
public async Task<UserManagerResponse> RegisterUserAsync(RegisterViewModel model)
{
if (model == null)
{
throw new NullReferenceException("Reigster Model is null");
}
if (model.Password != model.ConfirmPassword)
{
return new UserManagerResponse
{
Message = "Confirm password doesn't match the password",
IsSuccess = false,
};
}
var identityUser = new ApplicationUser
{
Email = model.Email,
UserName = model.Email,
About = model.About,
SecondMobile = model.SecondMobile,
Fullname = model.Fullname,
AliasName = model.AliasName,
Created = DateTime.Now,
Modified = DateTime.Now
};
var result = await _userManger.CreateAsync(identityUser, model.Password);
if (result.Succeeded)
{
var confirmEmailToken = await _userManger.GenerateEmailConfirmationTokenAsync(identityUser);
var encodedEmailToken = Encoding.UTF8.GetBytes(confirmEmailToken);
var validEmailToken = WebEncoders.Base64UrlEncode(encodedEmailToken);
string url = $"{_configuration["AppUrl"]}/api/auth/ConfirmEmail?userId={identityUser.Id}&token={validEmailToken}";
await _mailService.SendEmailAsync(identityUser.Email, "Confirm your email", $"<h1>Welcome to Trustee app</h1>" +
$"<p>Please confirm your email by <a href='{url}'>clicking here</a></p>");
return new UserManagerResponse
{
Message = "User created successfully!",
IsSuccess = true,
};
}
return new UserManagerResponse
{
Message = "User did not create",
IsSuccess = false,
Errors = result.Errors.Select(e => e.Description)
};
}
/// <summary>
/// Đăng nhập.
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
public async Task<UserManagerResponse> LoginUserAsync(LoginViewModel model)
{
var user = await _userManger.FindByEmailAsync(model.Email);
if (user == null)
{
return new UserManagerResponse
{
Message = "There is no user with that Email address",
IsSuccess = false,
};
}
var result = await _userManger.CheckPasswordAsync(user, model.Password);
if (!result)
{
return new UserManagerResponse
{
Message = "Invalid password",
IsSuccess = false,
};
}
var claims = new[]
{
new Claim("Email", model.Email),
new Claim(ClaimTypes.NameIdentifier, user.Id),
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["AuthSettings:Key"]));
var token = new JwtSecurityToken(
issuer: _configuration["AuthSettings:Issuer"],
audience: _configuration["AuthSettings:Audience"],
claims: claims,
expires: DateTime.Now.AddDays(30),
signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256));
string tokenAsString = new JwtSecurityTokenHandler().WriteToken(token);
return new UserManagerResponse
{
Message = tokenAsString,
IsSuccess = true,
ExpireDate = token.ValidTo
};
}
// Đăng xuất.
public async Task<UserManagerResponse> LogoutUserAsync(LoginViewModel model)
{
var user = await _userManger.FindByEmailAsync(model.Email);
if (user == null)
{
return new UserManagerResponse
{
Message = "There is no user with that Email address",
IsSuccess = false,
};
}
var result = await _userManger.CheckPasswordAsync(user, model.Password);
if (!result)
{
return new UserManagerResponse
{
Message = "Invalid password",
IsSuccess = false,
};
}
var claims = new[]
{
new Claim("Email", model.Email),
new Claim(ClaimTypes.NameIdentifier, user.Id),
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["AuthSettings:Key"]));
var token = new JwtSecurityToken(
issuer: _configuration["AuthSettings:Issuer"],
audience: _configuration["AuthSettings:Audience"],
claims: claims,
expires: DateTime.Now.AddDays(30),
signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256));
string tokenAsString = new JwtSecurityTokenHandler().WriteToken(token);
return new UserManagerResponse
{
Message = tokenAsString,
IsSuccess = true,
ExpireDate = token.ValidTo
};
}
public async Task<UserManagerResponse> ConfirmEmailAsync(string userId, string token)
{
var user = await _userManger.FindByIdAsync(userId);
if (user == null)
{
return new UserManagerResponse { IsSuccess = false, Message = "User not found" };
}
var decodedToken = WebEncoders.Base64UrlDecode(token);
string normalToken = Encoding.UTF8.GetString(decodedToken);
var result = await _userManger.ConfirmEmailAsync(user, normalToken);
if (result.Succeeded)
{
return new UserManagerResponse { Message = "Email confirmed successfully!", IsSuccess = true };
}
return new UserManagerResponse
{
IsSuccess = false,
Message = "Email did not confirm",
Errors = result.Errors.Select(e => e.Description)
};
}
public async Task<UserManagerResponse> ForgetPasswordAsync(string email)
{
var user = await _userManger.FindByEmailAsync(email);
if (user == null)
{
return new UserManagerResponse { IsSuccess = false, Message = "No user associated with email", };
}
var token = await _userManger.GeneratePasswordResetTokenAsync(user);
var encodedToken = Encoding.UTF8.GetBytes(token);
var validToken = WebEncoders.Base64UrlEncode(encodedToken);
string url = $"{_configuration["AppUrl"]}/ResetPassword?email={email}&token={validToken}";
await _mailService.SendEmailAsync(email, "Reset Password", "<h1>Follow the instructions to reset your password</h1>" +
$"<p>To reset your password <a href='{url}'>Click here</a></p>");
return new UserManagerResponse
{
IsSuccess = true,
Message = "Reset password URL has been sent to the email successfully!"
};
}
public async Task<UserManagerResponse> ResetPasswordAsync(ResetPasswordViewModel model)
{
var user = await _userManger.FindByEmailAsync(model.Email);
if (user == null)
{
return new UserManagerResponse { IsSuccess = false, Message = "No user associated with email", };
}
if (model.NewPassword != model.ConfirmPassword)
{
return new UserManagerResponse { IsSuccess = false, Message = "Password doesn't match its confirmation", };
}
var decodedToken = WebEncoders.Base64UrlDecode(model.Token);
string normalToken = Encoding.UTF8.GetString(decodedToken);
var result = await _userManger.ResetPasswordAsync(user, normalToken, model.NewPassword);
if (result.Succeeded)
{
return new UserManagerResponse { Message = "Password has been reset successfully!", IsSuccess = true };
}
return new UserManagerResponse
{
Message = "Something went wrong",
IsSuccess = false,
Errors = result.Errors.Select(e => e.Description)
};
}
public async Task<UserManagerResponse> ChangePasswordAsync(ChangePasswordViewModel model)
{
var tokenString = model.Token;
var jwtEncodedString = tokenString.Substring(7); // trim 'Bearer ' from the start since its just a prefix for the token string
var token = new JwtSecurityToken(jwtEncodedString: jwtEncodedString);
string email = token.Claims.First(c => c.Type == "Email").Value;
Console.WriteLine("email => " + email);
var user = await _userManger.FindByEmailAsync(email);
if (user == null)
{
return new UserManagerResponse { IsSuccess = false, Message = "No user associated with email", };
}
if (model.NewPassword != model.ConfirmPassword)
{
return new UserManagerResponse { IsSuccess = false, Message = "Password doesn't match its confirmation", };
}
var token2 = await _userManger.GeneratePasswordResetTokenAsync(user);
var result = await _userManger.ResetPasswordAsync(user, token2, model.NewPassword);
if (result.Succeeded)
{
return new UserManagerResponse { Message = "Password has been changed successfully!", IsSuccess = true };
}
return new UserManagerResponse
{
Message = "Something went wrong",
IsSuccess = false,
Errors = result.Errors.Select(e => e.Description)
};
}
}
}
文件appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "Server=.;Database=foo;User Id=sa; Password=SecrEt_STring;Trusted_Connection=False;MultipleActiveResultSets=True"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"AuthSettings": {
"Key": "This is the key that we will use in the encryption",
"Audience": "http://example.io",
"Issuer": "http://example.io"
},
"SendGridAPIKey": "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",
"AppUrl": "http://localhost:5002"
}
在控制器中
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using shadow.Data;
using shadow.DTO;
using shadow.Models;
using shadow.Services;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace shadow.Controllers
{
[Route("[controller]")]
[ApiController]
public class AssetItemController : ControllerBase
{
private IUserService _userService;
private IMailService _mailService;
private IConfiguration _configuration;
private ApplicationDbContext _db;
public AssetItemController(IUserService userService, IMailService mailService, IConfiguration configuration, ApplicationDbContext context /*, SignInManager<IdentityUser> signInManager */)
{
_userService = userService;
_mailService = mailService;
_configuration = configuration;
_db = context;
// _signInManager = signInManager;
}
// Admin liệt kê tất cả các tài sản.
[HttpGet]
[Route("all")]
[Authorize(Roles ="ADMIN")]
public async Task<ActionResult<AssetItem>> GetAllAssetItems()
{
var a = await Task.Run(() => _db.AssetItems);
if (a != null)
{
return Ok(a.ToList());
}
else
{
return NoContent();
}
}
// Liệt kê tất cả các tài sản theo user_id.
[HttpGet]
[Route("user/{userId}")]
public async Task<ActionResult<IEnumerable<AssetItem>>> GetAllAssetItemsByUserId(string userId)
{
var assetListByUserId = await Task.Run(() => _db.AssetItems.Where(x => x.UserId == userId).ToList());
if (assetListByUserId != null)
{
return Ok(assetListByUserId);
}
else
{
return NoContent();
}
}
// Xem chi tiet asset_item theo id cua no.
[HttpGet]
[Route("{assetItemId}")]
public async Task<ActionResult<IEnumerable<AssetItem>>> ViewAssetItemById(int assetItemId)
{
var assetListByUserId = await Task.Run(() => _db.AssetItems.Where(x => x.Id == assetItemId).FirstOrDefault());
if (assetListByUserId != null)
{
return Ok(assetListByUserId);
}
else
{
return NoContent();
}
}
// Xóa tài sản.
[HttpDelete("{id}")]
public async Task<ActionResult<string>> DeleteAssetItem(int id)
{
var item = _db.AssetItems.Find(id);
if (item != null)
{
_db.AssetItems.Remove(item);
await _db.SaveChangesAsync();
return Ok("ok");
}
else
{
return NotFound();
}
}
// POST http://localhost:5002/AssetItem
[HttpPost]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(string), StatusCodes.Status400BadRequest)]
[Authorize]
public async Task<ActionResult<AssetItem>> Add([FromForm] AssetItemDTO input)
{
if (ModelState.IsValid)
{
AssetItem assetItem = new AssetItem();
if (input.AssetTypeId != null)
{
assetItem.AssetTypeId = input.AssetTypeId;
}
if (input.UserId != null)
{
assetItem.UserId = input.UserId;
}
if (input.TrustedPersonId != null)
{
assetItem.TrustedPersonId = input.TrustedPersonId;
}
if (input.TrustedContent != null)
{
assetItem.TrustedContent = input.TrustedContent;
}
if (input.Description != null)
{
assetItem.Description = input.Description;
}
assetItem.Created = DateTime.Now;
if (input.file != null)
{
if (IsImageFile(input.file))
{
string imgPath = await WriteFile(input.file);
assetItem.ImagePath = imgPath;
}
else
{
return BadRequest(new { message = "Invalid file extension" });
}
}
_db.AssetItems.Add(assetItem);
await _db.SaveChangesAsync();
return Ok(assetItem);
}
else
{
return BadRequest(ModelState);
}
}
// Edit 1 tài sản.
// PUT http://localhost:5002/AssetItem
[HttpPut("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(string), StatusCodes.Status400BadRequest)]
[Authorize]
public async Task<ActionResult<AssetItem>> Edit([FromForm] AssetItemDTO input, int id)
{
if (ModelState.IsValid)
{
var foo = _db.AssetItems.Find(id);
if (!String.IsNullOrEmpty(input.TrustedContent))
{
foo.TrustedContent = input.TrustedContent;
}else
{
foo.TrustedContent = null;
}
if (input.file != null)
{
string imgPath = await WriteFile(input.file);
foo.ImagePath = imgPath;
}
else
{
foo.ImagePath = null;
}
if (!String.IsNullOrEmpty(input.Description))
{
foo.Description = input.Description;
}
else
{
foo.Description = null;
}
foo.AssetTypeId = input.AssetTypeId;
if (input.TrustedPersonId != null)
{
foo.TrustedPersonId = input.TrustedPersonId;
}
// Hiện chưa ghi nhận user_modified. Cần xem lại Requirement.
if (!String.IsNullOrEmpty(input.UserModified))
{
foo.UserModified = input.UserModified;
}
else
{
foo.UserModified = null;
}
foo.Modified = DateTime.Now;
_db.Update(foo);
await _db.SaveChangesAsync();
return Ok(foo);
}
else
{
return BadRequest(ModelState);
}
}
private bool IsImageFile(IFormFile file)
{
try
{
var extension = "." + file.FileName.Split('.')[file.FileName.Split('.').Length - 1];
return (extension == ".png" || extension == ".jpg" || extension == ".bmp" || extension == ".gif" || extension == ".tif");
}
catch
{
return false;
}
}
private async Task<string> WriteFile(IFormFile file)
{
string fileName = "";
try
{
var extension = "." + file.FileName.Split('.')[file.FileName.Split('.').Length - 1];
fileName = DateTime.Now.Ticks + extension; //Create a new Name for the file due to security reasons.
var pathBuilt = Path.Combine(Directory.GetCurrentDirectory(), "Upload\file");
if (!Directory.Exists(pathBuilt))
{
Directory.CreateDirectory(pathBuilt);
}
var path = Path.Combine(Directory.GetCurrentDirectory(), "Upload\files", fileName);
using (var stream = new FileStream(path, FileMode.Create))
{
await file.CopyToAsync(stream);
}
return fileName;
}
catch (Exception e)
{
//
}
return fileName;
}
}
}
数据库
select * from AspNetUsers;
select * from AspNetUserRoles;
select * from AspNetRoles;
结果
加[Authorize(Roles ="ADMIN")]
时显示403 Forbidden
.
您实际上并没有向令牌添加角色。每个角色都应该是一个声明,像这样。
new Claim(ClaimTypes.Role, "<role name">));
例如
var claims = new[]
{
new Claim("Email", model.Email),
new Claim(ClaimTypes.NameIdentifier, user.Id),
};
var roles = await _userManger.GetRolesAsync(user); // note: you have a typo in "_userManger"
var claimsWithRoles = roles.Select(role => new Claim(ClaimTypes.Role, role));
var allClaims = claims.Concat(claimsWithRoles);
然后将allClaims
添加到JWT中,像这样:
var token = new JwtSecurityToken(
issuer: _configuration["AuthSettings:Issuer"],
audience: _configuration["AuthSettings:Audience"],
claims: allClaims , // note how we add all claims, including the ones for roles, here
expires: DateTime.Now.AddDays(30),
signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256));
告诉我它是如何工作的。
编辑:抱歉,忘记了 AddRange
returns void;