Cześć,
kontynuując moje próby napisania sensownego WebApi natrafiłem na dziwną sytuację, w której, mimo, że token JWT generuję z expiration date na 1 minutę (testowo) to jest on ważny jakieś 4-5 minut. W samym tokenie czas podany jest prawidłowo. Przykładowy token:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIqIiwianRpIjoiMTI5ODJhNjItMzA0OS00ZDFmLThkNWItMTAwOWVhZDBlMGJhIiwiZXhwIjoxNTg2MDA2MjcxLCJpc3MiOiJDNUQzQTQ2MC1BRjVGLTMzRkYtOTYwMC0zMDA0NjA3NzRGRjkiLCJhdWQiOiI2MjFCNjJCMC04M0MyLUtLNTctQkVBRi05MzYyRDNEMzFFQzUifQ.rz3GVbz0gjVAVfVAH2IfdAUj-O0MSYncL8GFcIcnZjk
Skorzystałem do nauki z jakiegoś gotowego przykładu i nie mogę też znaleźć miejsca w kodzie, w którym ten token jest sprawdzany, nie rozumiem, jak to jest realizowane.
Dodatkowo zastanawiam się jak zrealizować coś takiego, że podczas weryfikacji tokena chciałbym się jeszcze połączyć z DB (nie wiem jak podpiąć się pod sprawdzanie tokena).
Poniżej troche kodu, uprzejma prośba o wsparcie ;)
UserIdentity.cs:
public class UserIdentity
{
[Required]
[JsonProperty(Required = Required.DisallowNull)]
public string Login { get; set; }
[Required]
[JsonProperty(Required = Required.DisallowNull)]
public string Password { get; set; }
}
JWTConfiguration.cs:
public class JWTConfiguration
{
public string SecretKey { get; set; }
public string ValidIssuer { get; set; }
public string ValidAudience { get; set; }
public int TokenExpirationTime { get; set; }
}
Startup:
public class Startup
{
public IConfigurationRoot Configuration { get; }
public Startup(Microsoft.Extensions.Hosting.IHostEnvironment env)
{
var appSettings = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json")
.Build();
Configuration = appSettings;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient((config) =>
{
var conf = new JWTConfiguration();
Configuration.GetSection("JWTConfiguration").Bind(conf);
return conf;
});
ConnectionStrings con = new ConnectionStrings();
Configuration.Bind("ConnectionStrings", con);
services.AddSingleton(con);
services.AddControllers();
services.AddMvc();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = ".Net Core 3 Web API - Tescik", Version = "v1" });
var filePath = Path.Combine(AppContext.BaseDirectory, "NetCore3WebAPI.xml");
c.IncludeXmlComments(filePath);
});
ConfigureJwt(services);
}
public void ConfigureJwt(IServiceCollection services)
{
var config = services.BuildServiceProvider().GetService<JWTConfiguration>();
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(config.SecretKey));
var tokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKey = signingKey,
ValidIssuer = config.ValidIssuer,
ValidAudience = config.ValidAudience
};
services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(c =>
{
c.RequireHttpsMetadata = false;
c.SaveToken = true;
c.TokenValidationParameters = tokenValidationParameters;
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCertificateForwarding();
app.UseAuthentication();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Testowe API (.NET Core 3,1 Web API) V1");
});
}
}
Generowanie tokenów (AuthController):
[Route("api/[controller]")]
[ApiController]
public class AuthController : ControllerBase
{
private readonly ConnectionStrings con;
private readonly UserIdentity userIdentity;
private readonly JWTConfiguration configuration;
public AuthController(ConnectionStrings c, JWTConfiguration configuration)
{
con = c;
this.configuration = configuration;
}
[HttpPost]
[Route("token")]
public AccessToken GenerateToken([FromBody]UserIdentity credentials)
{
IEnumerable<User> user;
using (var c = new MySqlConnection(con.MySQL))
{
string md5Password = string.Empty;
using (MD5 md5Hash = MD5.Create())
{
md5Password = GetMd5Hash(md5Hash, credentials.Password);
}
var sql = @$"select * from users where login = '{credentials.Login}' and password = '{md5Password}' ";
var query = c.Query<User>(sql, User, commandTimeout: 3000);
user = query;
}
if (user == null || user.Count() == 0)
{
Response.StatusCode = StatusCodes.Status401Unauthorized;
return new AccessToken { Success = false };
}
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, credentials.Login),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var key = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(configuration.SecretKey));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var expiredOn = DateTime.Now.AddMinutes(configuration.TokenExpirationTime);
var token = new JwtSecurityToken(configuration.ValidIssuer,
configuration.ValidAudience,
claims,
expires: expiredOn,
signingCredentials: creds);
return new AccessToken
{
ExpireOnDate = token.ValidTo,
Success = true,
ExpiryIn = configuration.TokenExpirationTime,
Token = new JwtSecurityTokenHandler().WriteToken(token)
};
}
private static string GetMd5Hash(MD5 md5Hash, string input)
{
byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(input));
StringBuilder sBuilder = new StringBuilder();
for (int i = 0; i < data.Length; i++)
{
sBuilder.Append(data[i].ToString("x2"));
}
return sBuilder.ToString();
}
}
I przykładowy endpoint wymagający autoryzacji:
[HttpGet]
[Route("get/{userId}")]
[Authorize]
public async Task<IActionResult> Get(int userId)
{
return await Task.Run(() =>
{
using (var c = new MySqlConnection(con.MySQL))
{
var sql = @"select * from users";
var query = c.Query<User>(sql, User , commandTimeout: 3000);
return Ok(query);
}
});
}