[英].NET Core 3.0 using Identity + JWT fails to authorize
I am using Microsoft.AspNetCore.Identity.EntityFrameworkCore
and I can generate JWT token and it works fine.我正在使用Microsoft.AspNetCore.Identity.EntityFrameworkCore
并且我可以生成 JWT 令牌并且它工作正常。 However, I cannot go to any route that needs authentication after adding the token to the header like this: 'Authorization', 'Bearer: <token>'
.但是,在将令牌添加到标头后,我无法转到任何需要身份验证的路由,如下所示: 'Authorization', 'Bearer: <token>'
。 I think there is something wrong with the way I login the user.我认为我登录用户的方式有问题。
I appreciate any help or hint.我感谢任何帮助或提示。
This is the startup.cs
code:这是startup.cs
代码:
/// <summary>
/// This method gets called by the runtime. Use this method to add services to the container.
/// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// If environment is localhost, then enable CORS policy, otherwise no cross-origin access
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", builder => builder
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
});
// Add framework services
// Add functionality to inject IOptions<T>
services.AddOptions();
services.Configure<JwtSettings>(_configuration.GetSection("JwtSettings"));
// Add our Config object so it can be injected
services.Configure<SecureHeadersMiddlewareConfiguration>(
_configuration.GetSection("SecureHeadersMiddlewareConfiguration"));
services.AddLogging();
services.AddRouting(options => options.LowercaseUrls = true);
if (_env.IsDevelopment())
{
services.AddDistributedMemoryCache();
}
else
{
services.AddDistributedRedisCache(opt =>
opt.Configuration = _configuration.GetRequiredValue<string>("REDISCLOUD_URL"));
}
services.AddSession(options =>
{
// Set a short timeout for easy testing.
options.IdleTimeout = TimeSpan.FromMinutes(50);
options.Cookie.HttpOnly = true;
options.Cookie.Name = ApiConstants.ApplicationName;
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
});
services.AddDbContext<EntityDbContext>(builder =>
{
if (_env.IsDevelopment())
{
builder.UseSqlite(_configuration.GetValue<string>("ConnectionStrings:Sqlite"));
}
else
{
builder.UseNpgsql(
ConnectionStringUrlToResource(_configuration.GetRequiredValue<string>("DATABASE_URL")));
}
});
services.AddIdentity<User, UserRole>(opt => opt.User.RequireUniqueEmail = true)
.AddEntityFrameworkStores<EntityDbContext>()
.AddDefaultTokenProviders();
var jwtSetting = _configuration
.GetSection("JwtSettings")
.Get<JwtSettings>();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(config =>
{
config.RequireHttpsMetadata = false;
config.SaveToken = true;
config.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = jwtSetting.Issuer,
ValidAudience = jwtSetting.Audience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSetting.Key))
};
});
services.AddControllers(opt =>
{
opt.EnableEndpointRouting = false;
opt.ModelValidatorProviders.Clear();
// Not need to have https
opt.RequireHttpsPermanent = false;
// Allow anonymous for localhost
if (_env.IsDevelopment())
{
opt.Filters.Add<AllowAnonymousFilter>();
}
opt.Filters.Add<CustomExceptionFilterAttribute>();
opt.Filters.Add<FileUploadActionFilterAttribute>();
})
.AddNewtonsoftJson(option => option.SerializerSettings.Converters.Add(new StringEnumConverter()));
services.AddSwaggerGen(config =>
{
config.SwaggerDoc("v1", new OpenApiInfo
{
Title = "Contractor-Finder-Api",
Description = "Contractor finder service API layer, .NET Core + PostgresSQL"
});
// Set the comments path for the Swagger JSON and UI.
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
if (File.Exists(xmlPath))
{
config.IncludeXmlComments(xmlPath);
}
config.OperationFilter<FileUploadOperation>();
config.AddSecurityDefinition("Bearer", // Name the security scheme
new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme.",
Type = SecuritySchemeType.Http, // We set the scheme type to http since we're using bearer authentication
Scheme = "bearer" // The name of the HTTP Authorization scheme to be used in the Authorization header. In this case "bearer".
});
});
var container = new Container(config =>
{
var (accessKeyId, secretAccessKey, url) = (
_configuration.GetRequiredValue<string>("CLOUDCUBE_ACCESS_KEY_ID"),
_configuration.GetRequiredValue<string>("CLOUDCUBE_SECRET_ACCESS_KEY"),
_configuration.GetRequiredValue<string>("CLOUDCUBE_URL")
);
var prefix = new Uri(url).Segments[1];
const string bucketName = "cloud-cube";
// Generally bad practice
var credentials = new BasicAWSCredentials(accessKeyId, secretAccessKey);
// Create S3 client
config.For<IAmazonS3>().Use(() => new AmazonS3Client(credentials, RegionEndpoint.USEast1));
config.For<S3ServiceConfig>().Use(new S3ServiceConfig(bucketName, prefix));
// Register stuff in container, using the StructureMap APIs...
config.Scan(_ =>
{
_.AssemblyContainingType(typeof(Startup));
_.Assembly("Logic");
_.Assembly("Dal");
_.WithDefaultConventions();
});
// Populate the container using the service collection
config.Populate(services);
config.For<IMapper>().Use(ctx => ResolveMapper(ctx, Assembly.Load("Logic"))).Singleton();
});
container.AssertConfigurationIsValid();
return container.GetInstance<IServiceProvider>();
}
/// <summary>
/// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
/// </summary>
/// <param name="app"></param>
public void Configure(IApplicationBuilder app)
{
// Add SecureHeadersMiddleware to the pipeline
app.UseSecureHeadersMiddleware(_configuration.Get<SecureHeadersMiddlewareConfiguration>());
app.UseCors("CorsPolicy")
.UseEnableRequestRewind()
.UseDeveloperExceptionPage();
if (_env.IsDevelopment())
{
app.UseDatabaseErrorPage();
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
// specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); });
}
// Not necessary for this workshop but useful when running behind kubernetes
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
// Read and use headers coming from reverse proxy: X-Forwarded-For X-Forwarded-Proto
// This is particularly important so that HttpContent.Request.Scheme will be correct behind a SSL terminating proxy
ForwardedHeaders = ForwardedHeaders.XForwardedFor |
ForwardedHeaders.XForwardedProto
});
// Use wwwroot folder as default static path
app.UseDefaultFiles()
.UseStaticFiles()
.UseCookiePolicy()
.UseSession()
.UseRouting()
.UseAuthentication()
.UseAuthorization()
.UseEndpoints(endpoints => endpoints.MapControllers());
Console.WriteLine("Application Started!");
}
}
} }
And finally this is my AccountController.cs
:最后这是我的AccountController.cs
:
namespace Api.Controllers
{
[Route("api/[controller]")]
public class AccountController : Controller
{
private readonly UserManager<User> _userManager;
private readonly SignInManager<User> _signManager;
private readonly IOptions<JwtSettings> _jwtSettings;
private readonly RoleManager<UserRole> _roleManager;
public AccountController(IOptions<JwtSettings> jwtSettings, UserManager<User> userManager,
SignInManager<User> signManager, RoleManager<UserRole> roleManager)
{
_jwtSettings = jwtSettings;
_userManager = userManager;
_signManager = signManager;
_roleManager = roleManager;
}
[HttpGet]
[Route("")]
[SwaggerOperation("AccountInfo")]
public async Task<IActionResult> Index()
{
return User.Identity.IsAuthenticated
? Ok(await _userManager.FindByEmailAsync(User.Identity.Name))
: Ok(new { });
}
[HttpPost]
[Route("Register/{role}")]
[SwaggerOperation("Register")]
public async Task<IActionResult> Register([FromRoute] RoleEnum role,
[FromBody] RegisterViewModel registerViewModel)
{
var user = UserFactory.New(role, x =>
{
x.Firstname = registerViewModel.Firstname;
x.Lastname = registerViewModel.Lastname;
x.Email = registerViewModel.Email;
x.UserName = registerViewModel.Username;
x.Role = role;
});
// Create user
var identityResults = new List<IdentityResult>
{
await _userManager.CreateAsync(user, registerViewModel.Password)
};
// Create the role if not exist
if (!await _roleManager.RoleExistsAsync(role.ToString()))
{
identityResults.Add(await _roleManager.CreateAsync(new UserRole {Name = role.ToString()}));
}
// Register the user to the role
identityResults.Add(await _userManager.AddToRoleAsync(user, role.ToString()));
return identityResults.Aggregate(true, (b, result) => b && result.Succeeded)
? (IActionResult) Ok("Successfully registered!")
: BadRequest("Failed to register!");
}
[HttpPost]
[Route("Login")]
[SwaggerOperation("Login")]
public async Task<IActionResult> Login([FromBody] LoginViewModel loginViewModel)
{
// Ensure the username and password is valid.
var user = await _userManager.FindByNameAsync(loginViewModel.Username);
if (user == null || !await _userManager.CheckPasswordAsync(user, loginViewModel.Password))
{
return BadRequest(new
{
error = "", // OpenIdConnectConstants.Errors.InvalidGrant,
error_description = "The username or password is invalid."
});
}
await _signManager.SignInAsync(user, true);
// Generate and issue a JWT token
var claims = new[]
{
new Claim(ClaimTypes.Name, user.Email),
new Claim(JwtRegisteredClaimNames.Sub, user.Email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.Value.Key));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
_jwtSettings.Value.Issuer,
_jwtSettings.Value.Issuer,
claims,
expires: DateTime.Now.AddMinutes(_jwtSettings.Value.AccessTokenDurationInMinutes),
signingCredentials: credentials);
var userRoleInfo = await _userManager.GetRolesAsync(user);
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token),
roles = userRoleInfo,
user.Role,
user.Firstname,
user.Lastname,
user.Email
});
}
[HttpPost]
[Route("Logout")]
[SwaggerOperation("Logout")]
public async Task<IActionResult> Logout()
{
await _signManager.SignOutAsync();
return Ok("Logged-Out");
}
}
}
This is an example of one of my controllers that I get not authorized error:这是我收到未授权错误的控制器之一的示例:
[Authorize(Roles = "Internal")]
[ApiController]
[Route("Api/[controller]")]
public class ProfileController : Controller
{
private readonly UserManager<User> _userManager;
public ProfileController(UserManager<User> userManager)
{
_userManager = userManager;
}
[HttpGet]
[Route("")]
public async Task<IActionResult> Index()
{
var user = await _userManager.FindByEmailAsync(User.Identity.Name);
return Ok(new Profile(user));
}
}
Screenshot of chrome dev tool: chrome 开发工具截图:
Please correct your Authorization Header value.请更正您的授权标头值。
try removing colon(:) after Bearer .尝试在Bearer之后删除冒号(:)。
It should be only Bearer <Token>
它应该只是Bearer <Token>
For More Information -> https://tools.ietf.org/html/rfc6750#section-2.1更多信息 -> https://tools.ietf.org/html/rfc6750#section-2.1
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.