[.NET 6] JWT 인증하기

해질녘·2022년 3월 2일
0

닷넷 (.NET)

목록 보기
1/12

[.NET 6] JWT 인증하기

.NET 6 Web API Authentication | Minimal API & Swagger (CRUD)

유튜브의 Code with Julian 님의 영상 보고 작성하였습니다. 이 문서는 해당 영상의 반 이후 (swagger 추가 이후)를 문서로 남긴 것입니다.

전체 코드 | GitHub

JWT의 동작

세션 VS. 토큰! JWT가 뭔가요? | 얄팍한 코딩사전

JWT 설정하기

appsettings.json

AllowedHosts 아래에 추가

"AllowedHosts": "*",
  "Jwt": {
    "Key": "DhftOS5uphK3vmCJQrexST1RsyjZBjXWRgJMFPU4",
    "Issuer": "https://localhost:포트번호/",
    "Audience": "https://localhost:포트번호/"
  }

이 키는 서버의 public key에 해당한다.

포트번호 같은 경우에는 VS에서 F5 눌러서 실행했을 때 서버가 열리는 포트번호를 적으면 된다.

Program.cs

builder 생성 코드 아래에 다음과 같이 추가한다.

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters()
    {
        ValidateActor = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        ValidIssuer = builder.Configuration["Jwt:Issuer"],
        ValidAudience = builder.Configuration["Jwt:Audience"],
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
    };
});
builder.Services.AddAuthorization();
  • builder.Configuration["Jwt:Issuer"] 이 부분은 appsetting.json을 참고하는 것이다.

app 인스턴스 생성 아래에 다음과 같이 추가한다.

app.UseAuthorization();
app.UseAuthentication();

로그인 메서드

유저가 로그인을 하면 서버에서 jwt 토큰을 만들어서 주어야 하고, 해당 토큰은 웹 브라우저의 쿠키에 저장된다.

유저가 인증이 필요한 시도를 할 때마다 request와 함께 jwt 토큰이 생성되어 전송된다

Program.cs의 app.UseAuthetication(); 아래에 메서드들 정의된 곳에 추가한다. 위치는 app생성~app.run() 사이라면 상관 없다.

app.MapPost("/login",
    (UserLogin user, IUserService service) => Login(user, service));
  • POST 방식이다.
  • UserLogin은 아이디, 패스워드로 구성된 객체.

위에서 정의한 함수를 구현. 위 정의부와 마찬가지로 구현 암데나 하면 됨.

IResult Login(UserLogin user, IUserService service)
{
    if (!string.IsNullOrEmpty(user.Username) &&
        !string.IsNullOrEmpty(user.Password))
    {
        var loggedInUser = service.Get(user);
        if (loggedInUser is null) return Results.NotFound("User not found");

        var claims = new[]
        {
            new Claim(ClaimTypes.NameIdentifier, loggedInUser.Username),
            new Claim(ClaimTypes.Email, loggedInUser.EmailAddress),
            new Claim(ClaimTypes.GivenName, loggedInUser.GivenName),
            new Claim(ClaimTypes.Surname, loggedInUser.Surname),
            new Claim(ClaimTypes.Role, loggedInUser.Role)
        };

        var token = new JwtSecurityToken
        (
            issuer: builder.Configuration["Jwt:Issuer"],
            audience: builder.Configuration["Jwt:Audience"],
            claims: claims,
            expires: DateTime.UtcNow.AddDays(60),
            notBefore: DateTime.UtcNow,
            signingCredentials: new SigningCredentials(
                new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])),
                SecurityAlgorithms.HmacSha256)
        );

        var tokenString = new JwtSecurityTokenHandler().WriteToken(token);

        return Results.Ok(tokenString);
    }
    return Results.BadRequest("Invalid user credentials");
}

길다 길어

하나씩 살펴보자.

  • if (!string.IsNullOrEmpty(user.Username) && !string.IsNullOrEmpty(user.Password))
    • 입력받은 Username, Password가 비어있는지 검증.
    • 여기서 튕기면
    • return Results.BadRequest("Invalid user credentials");
  • var loggedInUser = service.Get(user); if (loggedInUser is null) return Results.NotFound("User not found");
    • 이 Get 메서드라는건 Users 레포에 날려서 Username과 userLogin의 정보를 비교하는 함수.
    • 메서드의 리턴값은 Uesr 객체임
  • claim 이라는 건, jwt 토큰에 첨부할 정보들.
    • claim 사용해서 API가 정보에 쉽게 접근 가능.
  • token
    • expires: DateTime.UtcNow.AddDays(60),
      • 편의상 이렇게 썼지만 보통 몇분~몇시간단위로 만료

ENDPOINT에 Authentication 적용

이 튜토리얼에서 유저는 두가지 타입이 있음. admin과 standard(그냥)유저임.

몇몇 ENDPOINT에 대해 admin만 접근할 수 있도록 처리해보자.

app.MapPost("/create",
    [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Administrator")]
    (Movie movie, IMovieService service) => Create(movie, service));

create 에 대해 두번째 줄이 추가되었다.

  • [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Administrator")]
    • Admimistrator에 대해서만 액세스 허용한다.
app.MapGet("/get",
    [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Standard, Administrator")]
    (int id, IMovieService service) => Get(id, service));

이번에는 Standard, Admin 모두 가능. 일단 login으로 authentication 받아야하는 것은 동일하다. 일단 authentication 받아놓고, 이렇게 메서드에 대해 너가 스탠다드, 어드민 이렇게 가리는 것이 authorize이다.

update, delete에 대해서도 위와 같이 Admin만 허용하는 코드를 추가한다.

swagger에 인증 테스트 기능 추가하기

(튜토리얼의 앞선 부분에서 swagger를 추가했었음)

이거까지만 하고 Run 해서 swagger 돌리면, login이 되긴 되는데 막상 그 상태가 유지가 안 된다. 다음과 같은 코드를 program.cs에 추가.

builder.Services.AddSwaggerGen(options =>
{
    options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        Scheme = "Bearer",
        BearerFormat = "JWT",
        In = ParameterLocation.Header,
        Name = "Authorization",
        Description = "Bearer Authentication with JWT Token"
    });
});

다시 swagger 켜면 Authorize 버튼이 생긴다. Value 항목에 아래 api 테스트해서 로그인 시도한 후 Response body에 나오는 값을 복붙 한다.

이제 Authorization 접근 테스트가 가능하다.

끝!

0개의 댓글