사용중인 User 테이블의 Password 암호화 방식을 변경했다. 당연하게도 JWT가 401 Unauthorized를 내뱉기 시작했고, 이를 해결하기 위해 고군분투 했지만 아무리 검색해도 원하는 답이 나오지 않았다. 결국 JWT 라이브러리를 직접 분석하기 시작했고, 마침내 401 Unauthorized를 내뱉는 이유를 깨달았다. 해당 내용을 검색하니 그제서야 그토록 원했던 내용을 찾는데 성공했다(원인을 모르면 검색도 못한다는 사실을 뼈저리게 느꼈다). 이렇게 된 김에 JWT 분석 내용을 기록으로 남겨보고자 한다.
1. JWT 401 Unauthorized 원인 분석
원인은 간단하다. 변경 전 User 생성 방식은 password 알고리즘을 Bcrypt로 사용했는데, 변경 후에는 MySQL AES 알고리즘을 사용했기 때문이다. 또 User 테이블의 PK 값도 eamil 에서 user_id로 변경되었다. JWT 에선 당연히 초기 방식인 Bcrypt 알고리즘으로 인증을 시도할텐데, DB에는 AES 암호화 데이터가 담겨져 있으니, 아무리 인증을 요청해도 실패하여 401 Unauthorized가 떨어질 수 밖에.
2. JWT 동작 방식 분석
TL;DR
일반적으로 사용하는 JWT 로그인 방식
auth()->attempt($credentials)
에서 attempt() 함수 찾기
attempt() 정의 위치 = vender/tymon/jwt-auth/src/JWTAuth
$this->auth = Tymon/JWTAuth/Contracts/Providers/Auth
함수 내 byCredentials() 함수 호출
Tymon/JWTAuth/Contracts/Providers/Auth 는 Interface 파일
Tymon/JWTAuth/Providers/Auth/Illuminate 에서 Auth 상속
$this->auth = Laravel/Illuminate/Contracts/Auth/Guard
함수 내 once() 함수 호출
Illuminate/Contracts/Auth/Guard 는 Interface 파일
Vender/tymon/jwt-auth/src/JWTGaurd 에서 Guard 상속
once() -> validate() -> attempt() 순서로 함수 호출
$this->provider = Illuminate/Contracts/Auth/UserProvider
함수 내 retrievByCredentails() 함수 호출
Illuminate/Contracts/Auth/UserProvider 는 Interface 파일
Illuminate/Auth/EloquentUserProvider 에서 UserProvider 상속
retrieveByCredentials() = credential의 PK 값으로 User 정보 조회
해당 User 값으로 Tymon/JWTAuth/JWTGuard 에서
Illuminate/Auth/EloquentUserProvider 의 validateCredentials() 호출
validateCredentials() = User의 Password 값과 credential의 password 값 비교
$user->getAuthPassword()로 부터 User 테이블에 담긴 해당 사용자의 password 암호화 문자열을 가져오고, 이를 credenttial의 password 값과 hash 비교를 통해 인증 여부를 확인하는 것이다. hasher는 Bcrypt로 credential['password'] 를 암호화 할 것이고, $user->getAuthPassword() 는 MySQL AES로 암호화 되어있으니, 단순히 문자열 비교만 해도 달라서 인증을 통과하지 못한다. => 즉 validateCredentials() 함수 내용만 변경해주면 된다.
3. UserProvider 커스텀 생성
위에서 차근차근 봤다면 알겠지만, validateCredentials() 함수가 정의된 EloquentUserProvider는 UserProvider를 상속받아 만들어진 클래스이다. EloquentUserProvider 클래스의 validateCredentials() 함수를 수정해도 당연히 동작하겠지만, Vender에 정의된 라이브러리 소스이기 때문에 건드리지 않는게 정신건강에 좋다. 그래서 우리는 UserProvider를 상속받는 CustomUserProvider 클래스를 새롭게 생성할 것이다.
TL;DR
// 신규 생성.
// App/Provider/CustomUserProvider.php
<?php
namespace App\Provider;
use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Contracts\Auth\Authenticatable as UserContract;
class CustomUserProvider extends EloquentUserProvider
{
public function validateCredentials(UserContract $user, array $credentials)
{
$plain = $credentials['password'];
$password = DB::select("SELECT HEX(AES_ENCRYPT('".$plain."', '".env('DB_ENCRYPT')."')) AS password")[0]->password;
return $password === $user->getAuthPassword();
}
}
// 기존 페이지에 추가
// App/Providers/AuthServiceProvider.php
.
.
use App\Providers\CustomUserProvider;
class AuthServiceProvider extends ServiceProvider
{
.
.
public function boot()
{
$this->registerPolicies();
// Custom
Auth::provider('custom', function($app, array $config) {
return new CustomUserProvider($app['hash'], $config['model']);
});
}
}
// 기존 페이지 수정
// config/auth.php
.
.
'providers' => [
'user' => [
'driver => 'custom', // 'eloquent' 에서 'custom' 으로 변경
'model' => App/Models/User::class
]
]
// 설정파일 캐시 리프레시
$ php artisan config:cache
$ php artisan config:clear
이제 JWTAuth가 다시 동작하기 시작할 것이다.
'Programming > ETC' 카테고리의 다른 글
[Git] .env 파일 gitignore 가 안될 때 (0) | 2022.03.22 |
---|---|
[CSS] 다음카페 게시글 본문 꾸미기 (4) | 2022.02.25 |
[WSL] Laravel JWT 구현 (tymon/jwt-auth 패키지) (0) | 2022.01.24 |
댓글