ASP.NET Core에서 대칭 암호화를 사용한 JWT 인증
JWT 인증은 최신 웹 애플리케이션 또는 서비스에서 가장 많이 사용되는 인증 유형 중 하나가 되고 있습니다. 이 문서에서는 ASP.NET Core에서 대칭 키를 사용한 JWT 인증에 대해 설명합니다. 첫 번째 부분에서는 대칭 키가 무엇을 나타내는지에 대한 간략한 소개가 있고 두 번째 부분에는 이 프로젝트의 전제 조건과 이 인증 유형의 실제 구현이 포함되어 있습니다.
이 글은 2편의 시리즈 중 첫 번째 글이고, 두 번째 글은 인증서를 이용한 비대칭 키로 인증하는 내용입니다.
소개
JWT Token is a common way of creating access tokens that can contain several claims (e.g. Username, Roles). JWT Token means JSON (JavaScript Object Notation) Web Token. Every JWT Token has the following structure:
- Header, containing the encryption algorithm;
- Payload, containing custom Claims, plus at least two required claims:
-
exp
representing the expiration time when the Token will become unavailable; -
iat
or Issued at Time, the time when the Token was created;\ The times are formatted using the Unix Timestamp format (e.g. 1582784721).
-
- Signature, representing the encoded header, plus
a dot
, plus the encoded payload, plus a secret key. The concatenated result will be run through the encryption algorithm specified on the Header to validate the Token.
대칭 키
The Symmetric Key is used both for signing and validation. For example, let's say John wants to share a secret with Jane, when the secret is told, John also tells Jane a password - the key - in order for the secret to being understood. In this way, John - the identity provider or the service - ensures that his secret is well kept by using the given password.
설정
ASP.NET Core 3.1 will be used for this project. Microsoft also offers a great package that provides all that is needed to create a JWT Token-based authentication. The package is calledMicrosoft.AspNetCore.Authentication.JwtBearer
, this is the only package that the project needs, and can be found here: https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.JwtBearer .비밀 키 생성
The signing and validation key will be a user secret key. ASP.NET provides the user secret key feature to store all the confidential data that doesn't have to be committed or shared outside the user or developer environment. For the production or testing environments, the keys need to be store in a cloud vault, like Microsoft Azure offers through Key Vault - https://azure.microsoft.com/en-us/services/key-vault/ -, 하지만 이것은 다른 문서의 주제가 됩니다.\먼저 프로젝트 폴더에서 다음 명령을 실행하여 사용자 암호를 사용하기 위해 프로젝트를 시작해야 합니다.
dotnet user-secrets init
그런 다음 다음 명령을 사용하여 사용자 비밀 키가 추가됩니다.
dotnet user-secrets set "AppSettings:EncryptionKey" "POWERFULENCRYPTIONKEY"
이 명령어는
secrets.json
파일에 AppSettings:EncryptionKey
키 값을 POWERFULENCRYPTIONKEY
.\로 추가합니다.AppSettings
에 대한 여러 값이 있는 경우 다음과 같은 JSON 형식을 사용하여 이 키를 더 쉽게 읽을 수 있습니다."AppSettings": {
"EncryptionKey": "POWERFULENCRYPTIONKEY",
"Key2": "Value2"
}
POWERFULENCRYPTIONKEY
는 바이트 배열로 인코딩된 다음 이 바이너리는 Base64로 인코딩되며 이는 서명과 유효성 검사 모두에 필요합니다.시작
In the ConfigureServices
method from the Startup
class, the AppSettings
section needs to be read. To read a type from the configuration file, a class must be created, so for the AppSettings
section an equivalent class needs to exists, as is shown below. This class can be seen as a Data Transfer Object, which contains plain properties.
public class AppSettings
{
public string EncryptionKey { get; set; }
}
After the section is read, the EncryptionKey
needs to be converted into bytes.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
IConfigurationSection settingsSection = configuration.GetSection("AppSettings");
AppSettings settings = settingsSection.Get<AppSettings>();
byte[] signingKey = Encoding.UTF8.GetBytes(settings.EncryptionKey);
services.AddAuthentication(signingKey);
services.Configure<AppSettings>(settingsSection);
services.AddTransient<AuthenticationService>();
services.AddTransient<UserService>();
services.AddTransient<TokenService>();
}
On line 9
the Authentication
service is added into the App container, this service is responsible, for managing Authentication
settings, like IssuerSigningKey
or LifeTimeValidation
.\
For this step, an extension method is created called AddAuthentication
, which receives the service
and the signingKey
converted earlier.\
From line 11
to 14
, the services are configured for the Dependency Injection, we will return to the implementation of these services in a moment.
public static IServiceCollection AddAuthentication(this IServiceCollection services,
byte[] signingKey)
{
services.AddAuthentication(authOptions =>
{
authOptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
authOptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(jwtOptions =>
{
jwtOptions.SaveToken = true;
jwtOptions.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(signingKey),
ValidateLifetime = true,
LifetimeValidator = LifetimeValidator
};
});
return services;
}
The authenticationOptions
need to configure the Authenticate
and Challenge
Schemes, in order to verify that the endpoint(s) which receives a JWT Token will go through the validation step, as is described below starting from line 12
. The same Schema will be seen on the endpoints that use the AuthorizeAttribute
.\
Then the JwtBearer
is added to the Authentication
process, using the following properties:
-
SaveToken
is self-explanatory. It's used to persist the Token into local storage. The token will be valid even if the service restarts, so its lifetime is different from the application; -
ValidateAudience
andValidateIssuer
must be used for the service to skip or to validate the Audience or the Issuer. The Audience refers to the server or the Identity Provider, in this case, our ASP.NET Service. And theIssuer
refers to the client(s) that makes HTTP request(s). For the sake of this example, both are setfalse
. Please note that even if you don't want to validate theAudience
or/and theIssuer
these values must be set; -
ValidateIssuerSigningKey
needs to be set totrue
, in order to validate the received Token; - For
IssuerSigningKey
will use theSymmetricSecurityKey
, the same approach will be also used when the Token will be created. -
LifeTimeValidator
is important if the generatedToken
has set an expiration time.
LifeTimeValidator
핸들러는 다음과 같이 만료 날짜가 현재 날짜보다 큰지 확인합니다.private static bool LifetimeValidator(DateTime? notBefore,
DateTime? expires,
SecurityToken securityToken,
TokenValidationParameters validationParameters)
{
return expires != null && expires > DateTime.Now;
}
서비스를 구성한 후
Authentication
및 Authorization
미들웨어를 Configure
메소드에서 앱 파이프라인에 추가해야 합니다.public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseAuthentication();
app.UseAuthorization();
...
}
사용자 자격 증명
User's Credentials will be used as a Data Transfer Object, this class will be received on the authentication endpoint and sent to the AuthenticationService
. It's a plain class that contains only the Username
and the Password
of the user.
public class UserCredentials
{
public string Username { get; set; }
public string Password { get; set; }
}
인증 서비스
The AuthenticationService
is used like a middleware that receives the UserCredentials
from the Controller
, validates them using the UserService
and if the credentials are valid, it creates a Token using the TokenService
. Both the User
and Token
services are injected into the constructor.
public string Authenticate(UserCredentials userCredentials)
{
userService.ValidateCredentials(userCredentials);
string securityToken = tokenService.GetToken();
return securityToken;
}
사용자 서비스
For the sake of this example, the UserService
contains a list of users created on the constructor. In a real-life scenario, this will be read from storage or from a service.
public class UserService
{
private readonly IEnumerable<UserCredentials> users;
public UserService()
{
users = new List<UserCredentials>
{
new UserCredentials
{
Username = "john.doe",
Password = "john.password"
}
};
}
...
This is more like a UserValidation
service, but to better illustrate that it also reads the users, the UserService
name will be kept.
...
public void ValidateCredentials(UserCredentials userCredentials)
{
bool isValid =
users.Any(u =>
u.Username == userCredentials.Username &&
u.Password == userCredentials.Password);
if (!isValid)
{
throw new InvalidCredentialsException();
}
}
}
The ValidateCredentials
method checks if the username
and password
pair exists, and if it doesn't it will throw the InvalidCredentialsException
which will be caught on the Controller
.
토큰 서비스
TokenService
is receiving on the constructor the AppSettings
, which will be used on the GetTokenDescriptor
method to set up the Token.
public class TokenService
{
private readonly AppSettings appSettings;
public TokenService(IOptions<AppSettings> options)
{
appSettings = options.Value;
}
...
The public GetToken
method is used to get the token description, to create the Token and write it into a string, that will be returned to the calling service, in this case to the AuthenticationService
.
public string GetToken()
{
SecurityTokenDescriptor tokenDescriptor = GetTokenDescriptor();
var tokenHandler = new JwtSecurityTokenHandler();
SecurityToken securityToken = tokenHandler.CreateToken(tokenDescriptor);
string token = tokenHandler.WriteToken(securityToken);
return token;
}
On the GetTokenDescriptor
method, the token is constructed. In this method, the ExpirationTime
and SigningCredentials
are set. Because the Claims are not in the main focus of this article, I will create another one, in which I will explain how the Claims can be set on the Token and how they can be used.
private SecurityTokenDescriptor GetTokenDescriptor()
{
const int expiringDays = 7;
byte[] securityKey = Encoding.UTF8.GetBytes(appSettings.EncryptionKey);
var symmetricSecurityKey = new SymmetricSecurityKey(securityKey);
var tokenDescriptor = new SecurityTokenDescriptor
{
Expires = DateTime.UtcNow.AddDays(expiringDays),
SigningCredentials = new SigningCredentials(symmetricSecurityKey, SecurityAlgorithms.HmacSha256Signature)
};
return tokenDescriptor;
}
인증 컨트롤러
Now, all we have to do, is to create an AuthenticationController
which receives the UserCredentials
and uses the previously created AuthenticationService
.\
On the constructor the AuthenticationService
is injected, to be used on the Authentication
endpoint.
[Route("identity/[controller]")]
public class AuthenticationController : ControllerBase
{
private readonly AuthenticationService authenticationService;
public AuthenticationController(AuthenticationService authenticationService)
{
this.authenticationService = authenticationService;
}
...
The authentication endpoint accepts HTTP Post requests, receives the UserCredentials
as previously mentioned, and uses the AuthenticationService
to authenticate and create the Token.
...
[HttpPost]
public IActionResult Authenticate([FromBody] UserCredentials userCredentials)
{
try
{
string token = authenticationService.Authenticate(userCredentials);
return Ok(token);
}
catch (InvalidCredentialsException)
{
return Unauthorized();
}
}
}
If the credentials are valid, then the endpoint will return an OK
HTTP Status code and the generated token. Otherwise, if the InvalidCredentialsException
is thrown, the Unauthorized
HTTP Status code is returned.
유효성 검사 컨트롤러
The purpose of the ValidationController
is to check that the signing process is working, in order to validate the Token.
Route("identity/[controller]")]
public class ValidationController : ControllerBase
{
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public IActionResult Validate()
{
return Ok();
}
}
You may notice that the Validate
endpoint has the AuthorizeAttribute
which has on its constructor the same AuthenticationSchemes
as was set on the Authentication
service.
결과
Firstly, the happy flow for the AuthenticationController
is tested, so we'll provide the correct combination of the username and password, in order to receive the token.
올바르지 않은 자격 증명으로 테스트해 보겠습니다. 응답은 Unauthorized여야 합니다.
둘째, 생성된 토큰은
Validation
컨트롤러를 사용하여 테스트해야 합니다. 첫 번째 테스트는 유효성 검사가 통과되었는지 확인하기 위해 생성된 토큰을 사용합니다.두 번째 테스트는 유효성 검사를 위해 잘못된 토큰이 제공되는 경우입니다.
이 기사의 소스 코드는 내 GitHub 계정https://github.com/StefanescuEduard/JwtAuthentication에서 찾을 수 있습니다.
이 기사를 읽어 주셔서 감사합니다. 흥미로웠다면 동료 및 친구들과 공유하십시오. 또는 개선할 수 있는 부분이 있으면 알려주세요.
Reference
이 문제에 관하여(ASP.NET Core에서 대칭 암호화를 사용한 JWT 인증), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/eduardstefanescu/jwt-authentication-with-symmetric-encryption-in-asp-net-core-2i53텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)