JSON 웹 토큰 튜토리얼: Laravel 및 AngularJS의 예
게시 됨: 2022-03-11단일 페이지 애플리케이션, 모바일 애플리케이션 및 RESTful API 서비스의 인기가 높아짐에 따라 웹 개발자가 백엔드 코드를 작성하는 방식이 크게 변경되었습니다. AngularJS 및 BackboneJS와 같은 기술을 사용하여 더 이상 마크업을 구축하는 데 많은 시간을 할애하지 않고 프론트 엔드 애플리케이션에서 사용하는 API를 구축하고 있습니다. 백엔드는 비즈니스 로직과 데이터에 관한 것이지만 프레젠테이션 로직은 프론트엔드나 모바일 애플리케이션으로만 이동됩니다. 이러한 변경으로 인해 최신 애플리케이션에서 인증을 구현하는 새로운 방법이 등장했습니다.
인증은 웹 애플리케이션에서 가장 중요한 부분 중 하나입니다. 수십 년 동안 쿠키와 서버 기반 인증은 가장 쉬운 솔루션이었습니다. 그러나 최신 모바일 및 단일 페이지 애플리케이션에서 인증을 처리하는 것은 까다로울 수 있으며 더 나은 접근 방식이 필요합니다. API 인증 문제에 대한 가장 잘 알려진 솔루션은 OAuth 2.0 및 JWT(JSON Web Token)입니다.
이 JSON 웹 토큰 튜토리얼을 시작하기 전에 JWT가 정확히 무엇입니까?
JSON 웹 토큰이란 무엇입니까?
JSON 웹 토큰은 디지털 서명을 통해 확인하고 신뢰할 수 있는 정보를 보내는 데 사용됩니다. 이것은 진위를 확인하기 위해 암호로 서명되고 페이로드에 민감한 정보가 포함된 경우에도 암호화될 수 있는 압축된 URL 안전 JSON 개체로 구성됩니다.
JWT는 컴팩트한 구조로 인해 일반적으로 HTTP Authorization
헤더 또는 URL 쿼리 매개변수에 사용됩니다.
JSON 웹 토큰의 구조
JWT는 마침표 문자로 구분되는 base64url 인코딩 값의 시퀀스로 표시됩니다.
다음은 JWT 토큰의 예입니다.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . eyJpc3MiOiJ0b3B0YWwuY29tIiwiZXhwIjoxNDI2NDIwODAwLCJodHRwOi8vdG9wdGFsLmNvbS9qd3RfY2xhaW1zL2lzX2FkbWluIjp0cnVlLCJjb21wYW55IjoiVG9wdGFsIiwiYXdlc29tZSI6dHJ1ZX0 . yRQYnWzskCZUxPwaQupWkiUzKELZ49eM7oWxAQK_ZXw
머리글
헤더에는 토큰에 대한 메타데이터가 포함되며 최소한 서명 유형과 암호화 알고리즘이 포함됩니다. (JSON 포맷터 도구를 사용하여 JSON 객체를 예쁘게 만들 수 있습니다.)
헤더 예시
{ "alg": "HS256", "typ": "JWT" }
이 JWT 예제 헤더는 인코딩된 객체가 JSON 웹 토큰이고 HMAC SHA-256 알고리즘을 사용하여 서명되었음을 선언합니다.
이것이 base64로 인코딩되면 JWT의 첫 번째 부분이 생깁니다.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
페이로드(클레임)
JWT의 맥락에서 클레임은 엔터티(일반적으로 사용자)에 대한 설명과 토큰 자체에 대한 추가 메타데이터로 정의할 수 있습니다. 클레임에는 전송하려는 정보와 서버가 JSON 웹 토큰 인증을 올바르게 처리하는 데 사용할 수 있는 정보가 포함되어 있습니다. 저희가 제공할 수 있는 여러 클레임이 있습니다. 여기에는 등록된 클레임 이름, 공개 클레임 이름 및 개인 클레임 이름이 포함됩니다.
등록된 JWT 클레임
IANA JSON 웹 토큰 클레임 레지스트리에 등록된 클레임입니다. 이러한 JWT 클레임은 필수가 아니라 유용하고 상호 운용 가능한 클레임 집합의 시작점을 제공하기 위한 것입니다.
여기에는 다음이 포함됩니다.
- iss : 토큰 발행자
- sub : 토큰의 주제
- ud : 토큰의 대상
- exp : Unix 시간으로 정의된 JWT 만료 시간
- nbf : JWT가 처리를 위해 허용되지 않아야 하는 시간을 식별하는 "이전" 시간
- iat : 토큰이 발행된 Unix 시간의 "발급" 시간
- jti : JWT ID 클레임은 JWT에 대한 고유 식별자를 제공합니다.
공개 청구
공개 클레임에는 충돌 방지 이름이 있어야 합니다. 이름을 URI 또는 URN으로 만들면 발신자와 수신자가 폐쇄된 네트워크의 일부가 아닌 JWT에 대해 명명 충돌이 방지됩니다.
공개 클레임 이름의 예는 다음과 같습니다. https://www.toptal.com/jwt_claims/is_admin
, 가장 좋은 방법은 해당 위치에 클레임을 설명하는 파일을 배치하여 문서화를 위해 역참조할 수 있도록 하는 것입니다.
개인 청구
사설 클레임 이름은 JWT가 기업 내부와 같이 알려진 시스템 간의 폐쇄된 환경에서만 교환되는 장소에서 사용될 수 있습니다. 이는 사용자 ID, 사용자 역할 또는 기타 정보와 같이 우리가 스스로 정의할 수 있는 주장입니다.
폐쇄형 또는 사설 시스템 외부에서 의미론적 의미가 충돌할 수 있는 클레임 이름을 사용하면 충돌할 수 있으므로 주의해서 사용하십시오.
웹 토큰을 가능한 한 작게 유지하기를 원하므로 공개 및 비공개 클레임 내에서 필요한 데이터만 사용한다는 점에 유의하는 것이 중요합니다.
JWT 예시 페이로드
{ "iss": "toptal.com", "exp": 1426420800, "https://www.toptal.com/jwt_claims/is_admin": true, "company": "Toptal", "awesome": true }
이 예제 페이로드에는 등록된 클레임 2개(공개 클레임 1개와 개인 클레임 2개)가 있습니다. base64로 인코딩되면 JWT의 두 번째 부분이 생깁니다.
eyJpc3MiOiJ0b3B0YWwuY29tIiwiZXhwIjoxNDI2NDIwODAwLCJodHRwOi8vdG9wdGFsLmNvbS9qd3RfY2xhaW1zL2lzX2FkbWluIjp0cnVlLCJjb21wYW55IjoiVG9wdGFsIiwiYXdlc29tZSI6dHJ1ZX0
서명
JWT 표준은 JWS(JSON Web Signature) 사양을 따라 최종 서명된 토큰을 생성합니다. 인코딩된 JWT 헤더와 인코딩된 JWT 페이로드를 결합하고 HMAC SHA-256과 같은 강력한 암호화 알고리즘을 사용하여 서명하여 생성됩니다. 서명의 비밀 키는 서버에서 보유하므로 기존 토큰을 확인하고 새 토큰에 서명할 수 있습니다.
$encodedContent = base64UrlEncode(header) + "." + base64UrlEncode(payload); $signature = hashHmacSHA256($encodedContent);
이것은 JWT의 마지막 부분을 제공합니다.
yRQYnWzskCZUxPwaQupWkiUzKELZ49eM7oWxAQK_ZXw
JWT 보안 및 암호화
메시지 가로채기(man-in-the-middle) 공격을 방지하려면 JWT와 함께 TLS/SSL을 사용하는 것이 중요합니다. 대부분의 경우 민감한 정보가 포함된 JWT 페이로드를 암호화하는 데 충분합니다. 그러나 추가 보호 계층을 추가하려는 경우 JWE(JSON Web Encryption) 사양을 사용하여 JWT 페이로드 자체를 암호화할 수 있습니다.
물론 JWE 사용으로 인한 추가 오버헤드를 피하려면 중요한 정보를 데이터베이스에 보관하고 민감한 데이터에 액세스해야 할 때마다 서버에 대한 추가 API 호출에 토큰을 사용하는 또 다른 옵션이 있습니다.
웹 토큰이 필요한 이유는 무엇입니까?
JWT 인증 사용의 모든 이점을 보기 전에 과거에 인증이 수행된 방식을 살펴봐야 합니다.
서버 기반 인증
HTTP 프로토콜은 상태 비저장이므로 사용자 정보를 저장하는 메커니즘과 로그인 후 모든 후속 요청에서 사용자를 인증하는 방법이 필요합니다. 대부분의 웹사이트는 사용자의 세션 ID를 저장하기 위해 쿠키를 사용합니다.
작동 원리
브라우저는 사용자의 ID와 암호가 포함된 서버에 POST 요청을 합니다. 서버는 사용자의 브라우저에 설정되어 있는 쿠키로 응답하며 사용자를 식별하기 위한 세션 ID를 포함합니다.
모든 후속 요청에서 사용자 데이터가 서버에 저장되기 때문에 서버는 해당 세션을 찾아 역직렬화해야 합니다.
서버 기반 인증의 단점
확장하기 어려움: 서버는 사용자를 위한 세션을 생성하고 서버의 어딘가에 유지해야 합니다. 이것은 메모리나 데이터베이스에서 수행할 수 있습니다. 분산 시스템이 있는 경우 애플리케이션 서버와 연결되지 않은 별도의 세션 저장소를 사용해야 합니다.
교차 출처 요청 공유(CORS) : AJAX 호출을 사용하여 다른 도메인에서 리소스를 가져올 때(“교차 출처”) 기본적으로 HTTP 요청에는 교차 출처에 쿠키가 포함되어 있지 않기 때문에 금지된 요청 문제가 발생할 수 있습니다. 출처 요청.
웹 프레임워크와의 결합 : 서버 기반 인증을 사용할 때 우리는 프레임워크의 인증 체계에 연결됩니다. 서로 다른 프로그래밍 언어로 작성된 서로 다른 웹 프레임워크 간에 세션 데이터를 공유하는 것은 정말 어렵거나 불가능합니다.
토큰 기반 인증
토큰 기반/JWT 인증은 상태 비저장이므로 세션에 사용자 정보를 저장할 필요가 없습니다. 이를 통해 사용자가 어디에 로그인했는지 걱정하지 않고 애플리케이션을 확장할 수 있습니다. 로그인한 도메인이 아닌 다른 도메인에서 보안 리소스를 가져오는 데 동일한 토큰을 쉽게 사용할 수 있습니다.
JSON 웹 토큰의 작동 방식
브라우저 또는 모바일 클라이언트는 사용자 로그인 정보가 포함된 인증 서버에 요청합니다. 인증 서버는 새 JWT 액세스 토큰을 생성하여 클라이언트에 반환합니다. 제한된 리소스에 대한 모든 요청에서 클라이언트는 쿼리 문자열 또는 Authorization
헤더에 액세스 토큰을 보냅니다. 그런 다음 서버는 토큰의 유효성을 검사하고 유효한 경우 보안 리소스를 클라이언트에 반환합니다.
인증 서버는 모든 보안 서명 방법을 사용하여 토큰에 서명할 수 있습니다. 예를 들어, 모든 당사자 간에 비밀 키를 공유할 수 있는 보안 채널이 있는 경우 HMAC SHA-256과 같은 대칭 키 알고리즘을 사용할 수 있습니다. 또는 RSA와 같은 비대칭 공개 키 시스템도 사용할 수 있으므로 추가 키 공유가 필요하지 않습니다.
토큰 기반 인증의 장점
Stateless, 더 쉬운 확장성 : 토큰에는 사용자를 식별하기 위한 모든 정보가 포함되어 있으므로 세션 상태가 필요하지 않습니다. 로드 밸런서를 사용하면 로그인한 동일한 서버에 바인딩되는 대신 사용자를 모든 서버에 전달할 수 있습니다.
재사용성 : 사용자 인증을 위해 동일한 토큰을 재사용하여 여러 플랫폼과 도메인에서 실행되는 많은 별도의 서버를 가질 수 있습니다. 다른 응용 프로그램과 권한을 공유하는 응용 프로그램을 쉽게 구축할 수 있습니다.
JWT 보안 : 쿠키를 사용하지 않기 때문에 CSRF(Cross-Site Request Forgery) 공격으로부터 보호할 필요가 없습니다. 민감한 정보를 입력해야 하는 경우 JWE를 사용하여 토큰을 암호화하고 메시지 가로채기(man-in-the-middle) 공격을 방지하기 위해 HTTPS를 통해 토큰을 전송해야 합니다.
성능 : 각 요청에서 세션을 찾고 역직렬화하기 위한 서버 측 조회가 없습니다. 우리가 해야 할 유일한 일은 HMAC SHA-256을 계산하여 토큰의 유효성을 검사하고 해당 콘텐츠를 구문 분석하는 것입니다.
Laravel 5와 AngularJS를 사용한 JSON 웹 토큰 예제
이 JWT 튜토리얼에서는 두 가지 인기 있는 웹 기술인 백엔드 코드용 Laravel 5와 프론트엔드 SPA(단일 페이지 애플리케이션) 예제용 AngularJS에서 JSON 웹 토큰을 사용하여 기본 인증을 구현하는 방법을 보여줍니다. (여기에서 전체 데모를 찾을 수 있고 이 GitHub 리포지토리에서 튜토리얼을 따라갈 수 있는 소스 코드를 찾을 수 있습니다.)
이 JSON 웹 토큰 예제는 클레임에서 전송된 정보의 기밀성을 보장하기 위해 어떤 종류의 암호화도 사용하지 않습니다. 실제로 이것은 TLS/SSL이 요청을 암호화하기 때문에 괜찮습니다. 그러나 토큰에 사용자의 주민등록번호와 같은 민감한 정보가 포함될 경우 JWE를 사용하여 암호화해야 합니다.
라라벨 백엔드 예제
우리는 Laravel을 사용하여 사용자 등록을 처리하고, 사용자 데이터를 데이터베이스에 유지하고, Angular 앱이 소비할 인증이 필요한 일부 제한된 데이터를 제공할 것입니다. CORS(Cross-Origin Resource Sharing)도 시뮬레이션하기 위해 예제 API 하위 도메인을 생성합니다.
설치 및 프로젝트 부트스트랩
Laravel을 사용하려면 컴퓨터에 Composer 패키지 관리자를 설치해야 합니다. Laravel에서 개발할 때 Vagrant의 Laravel Homestead 사전 패키지 "상자"를 사용하는 것이 좋습니다. 운영 체제에 관계없이 완벽한 개발 환경을 제공합니다.
JWT Laravel 애플리케이션을 부트스트랩하는 가장 쉬운 방법은 Composer 패키지 Laravel Installer를 사용하는 것입니다.
composer global require "laravel/installer=~1.1"
이제 laravel new jwt
를 실행하여 새로운 Laravel 프로젝트를 만들 준비가 되었습니다.
이 프로세스에 대한 질문은 공식 Laravel 문서를 참조하십시오.
기본 Laravel 5 애플리케이션을 만든 후에는 Homestead.yaml
을 설정해야 합니다. 이 설정은 로컬 환경에 대한 폴더 매핑 및 도메인 구성을 구성합니다.
Homestead.yaml
파일의 예:
--- ip: "192.168.10.10" memory: 2048 cpus: 1 authorize: /Users/ttkalec/.ssh/public.psk keys: - /Users/ttkalec/.ssh/private.ppk folders: - map: /coding/jwt to: /home/vagrant/coding/jwt sites: - map: jwt.dev to: /home/vagrant/coding/jwt/public - map: api.jwt.dev to: /home/vagrant/coding/jwt/public variables: - key: APP_ENV value: local
vagrant up
명령으로 Vagrant 상자를 부팅하고 vagrant ssh
를 사용하여 로그인한 후 이전에 정의한 프로젝트 디렉토리로 이동합니다. 위의 예에서 이것은 /home/vagrant/coding/jwt
입니다. 이제 데이터베이스에 필요한 사용자 테이블을 생성하기 위해 php artisan migrate
명령을 실행할 수 있습니다.
Composer 종속성 설치
다행히도 Laravel에서 작업하고 애플리케이션을 재사용하고 확장할 수 있는 많은 훌륭한 패키지를 유지 관리하는 개발자 커뮤니티가 있습니다. 이 예에서는 Sean Tymon tymon/jwt-auth
를 사용하여 서버 측 토큰을 처리하고 barryvdh/laravel-cors
를 Barry vd. CORS 처리용 Heuvel.
jwt-auth
Composer.json에 composer.json
tymon/jwt-auth
패키지가 필요하고 종속성을 업데이트하십시오.
composer require tymon/jwt-auth 0.5.*
JWTAuthServiceProvider
를 app/config/app.php
공급자 배열에 추가합니다.
'Tymon\JWTAuth\Providers\JWTAuthServiceProvider'
다음으로, app/config/app.php
파일의 aliases
배열 아래에 JWTAuth
파사드를 추가합니다.
'JWTAuth' => 'Tymon\JWTAuth\Facades\JWTAuth'
마지막으로 다음 명령을 사용하여 패키지 구성을 게시하려고 합니다. php artisan config:publish tymon/jwt-auth
JSON 웹 토큰은 비밀 키를 사용하여 암호화됩니다. php artisan jwt:generate
명령을 사용하여 해당 키를 생성할 수 있습니다. 이것은 config/jwt.php
파일 안에 위치할 것입니다. 그러나 프로덕션 환경에서는 구성 파일 내부에 비밀번호나 API 키를 갖고 싶지 않습니다. 대신, 우리는 그것들을 서버 환경 변수 안에 배치하고 env
함수를 사용하여 구성 파일에서 참조해야 합니다. 예를 들어:
'secret' => env('JWT_SECRET')
이 패키지와 모든 구성 설정에 대한 자세한 내용은 Github에서 확인할 수 있습니다.
laravel-cors
Composer.json에 barryvdh/laravel-cors
composer.json
가 필요하고 종속성을 업데이트하십시오.
composer require barryvdh/laravel-cors 0.4.x@dev
CorsServiceProvider
를 app/config/app.php
공급자 배열에 추가합니다.
'Barryvdh\Cors\CorsServiceProvider'
그런 다음 app/Http/Kernel.php
에 미들웨어를 추가하십시오.
'Barryvdh\Cors\Middleware\HandleCors'
php artisan vendor:publish
명령을 사용하여 구성을 로컬 config/cors.php
파일에 게시합니다.
cors.php
파일 구성의 예:
return [ 'defaults' => [ 'supportsCredentials' => false, 'allowedOrigins' => [], 'allowedHeaders' => [], 'allowedMethods' => [], 'exposedHeaders' => [], 'maxAge' => 0, 'hosts' => [], ], 'paths' => [ 'v1/*' => [ 'allowedOrigins' => ['*'], 'allowedHeaders' => ['*'], 'allowedMethods' => ['*'], 'maxAge' => 3600, ], ], ];
HTTP 요청 라우팅 및 처리
간결함을 위해 Laravel 라우팅 및 컨트롤러에 대한 요청 위임을 담당하는 route.php 파일에 모든 코드를 넣습니다. 우리는 일반적으로 모든 HTTP 요청을 처리하기 위한 전용 컨트롤러를 만들고 코드를 모듈식으로 깔끔하게 유지합니다.
다음을 사용하여 AngularJS SPA 보기를 로드합니다.
Route::get('/', function () { return view('spa'); });
사용자 등록
사용자 이름과 암호를 사용하여 /signup
에 POST
요청을 하면 새 사용자를 만들고 데이터베이스에 저장하려고 합니다. 사용자가 생성되면 JWT가 생성되고 JSON 응답을 통해 반환됩니다.
Route::post('/signup', function () { $credentials = Input::only('email', 'password'); try { $user = User::create($credentials); } catch (Exception $e) { return Response::json(['error' => 'User already exists.'], HttpResponse::HTTP_CONFLICT); } $token = JWTAuth::fromUser($user); return Response::json(compact('token')); });
사용자 로그인
사용자 이름과 비밀번호로 /signin
에 POST
요청을 하면 사용자가 존재하는지 확인하고 JSON 응답을 통해 JWT를 반환합니다.
Route::post('/signin', function () { $credentials = Input::only('email', 'password'); if ( ! $token = JWTAuth::attempt($credentials)) { return Response::json(false, HttpResponse::HTTP_UNAUTHORIZED); } return Response::json(compact('token')); });
동일한 도메인에서 제한된 리소스 가져오기

사용자가 로그인하면 제한된 리소스를 가져올 수 있습니다. 인증된 사용자가 필요한 리소스를 시뮬레이트하는 /restricted
경로를 만들었습니다. 이렇게 하려면 요청 Authorization
헤더 또는 쿼리 문자열이 백엔드가 확인할 JWT를 제공해야 합니다.
Route::get('/restricted', [ 'before' => 'jwt-auth', function () { $token = JWTAuth::getToken(); $user = JWTAuth::toUser($token); return Response::json([ 'data' => [ 'email' => $user->email, 'registered_at' => $user->created_at->toDateTimeString() ] ]); } ]);
이 예에서는 'before' => 'jwt-auth'
를 사용하여 jwt-auth
패키지에서 제공하는 jwt-auth
미들웨어를 사용하고 있습니다. 이 미들웨어는 요청을 필터링하고 JWT 토큰을 검증하는 데 사용됩니다. 토큰이 유효하지 않거나 존재하지 않거나 만료된 경우 미들웨어는 catch할 수 있는 예외를 throw합니다.
Laravel 5에서는 app/Exceptions/Handler.php
파일을 사용하여 예외를 잡을 수 있습니다. render
기능을 사용하여 던져진 예외를 기반으로 HTTP 응답을 생성할 수 있습니다.
public function render($request, Exception $e) { if ($e instanceof \Tymon\JWTAuth\Exceptions\TokenInvalidException) { return response(['Token is invalid'], 401); } if ($e instanceof \Tymon\JWTAuth\Exceptions\TokenExpiredException) { return response(['Token has expired'], 401); } return parent::render($request, $e); }
사용자가 인증되고 토큰이 유효한 경우 JSON을 통해 제한된 데이터를 프런트엔드로 안전하게 반환할 수 있습니다.
API 하위 도메인에서 제한된 리소스 가져오기
다음 JSON 웹 토큰 예제에서는 토큰 유효성 검사에 대해 다른 접근 방식을 사용합니다. jwt-auth
미들웨어를 사용하는 대신 수동으로 예외를 처리합니다. API 서버 api.jwt.dev/v1/restricted
에 POST
요청을 할 때 교차 출처 요청을 하고 있으며 백엔드에서 CORS를 활성화해야 합니다. 다행히 config/cors.php
파일에 이미 CORS를 구성했습니다.
Route::group(['domain' => 'api.jwt.dev', 'prefix' => 'v1'], function () { Route::get('/restricted', function () { try { JWTAuth::parseToken()->toUser(); } catch (Exception $e) { return Response::json(['error' => $e->getMessage()], HttpResponse::HTTP_UNAUTHORIZED); } return ['data' => 'This has come from a dedicated API subdomain with restricted access.']; }); });
AngularJS 프론트엔드 예제
우리는 AngularJS를 프런트 엔드로 사용하고 있으며 사용자 인증 및 샘플 데이터를 위한 Laravel 백엔드 인증 서버와 교차 출처 예제 데이터를 위한 API 서버에 대한 API 호출에 의존하고 있습니다. 프로젝트의 홈페이지로 이동하면 백엔드는 Angular 애플리케이션을 부트스트랩할 resources/views/spa.blade.php
보기를 제공합니다.
Angular 앱의 폴더 구조는 다음과 같습니다.
public/ |-- css/ `-- bootstrap.superhero.min.css |-- lib/ |-- loading-bar.css |-- loading-bar.js `-- ngStorage.js |-- partials/ |-- home.html |-- restricted.html |-- signin.html `-- signup.html `-- scripts/ |-- app.js |-- controllers.js `-- services.js
Angular 애플리케이션 부트스트랩
spa.blade.php
에는 응용 프로그램을 실행하는 데 필요한 기본적인 사항이 포함되어 있습니다. 우리는 Bootswatch의 사용자 정의 테마와 함께 스타일링을 위해 Twitter Bootstrap을 사용할 것입니다. AJAX 호출을 할 때 시각적 피드백을 얻기 위해 XHR 요청을 가로채서 로딩 막대를 생성하는 angular-loading-bar 스크립트를 사용할 것입니다. 헤더 섹션에는 다음과 같은 스타일시트가 있습니다.
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"> <link rel="stylesheet" href="/css/bootstrap.superhero.min.css"> <link rel="stylesheet" href="/lib/loading-bar.css">
마크업의 바닥글에는 라이브러리에 대한 참조와 Angular 모듈, 컨트롤러 및 서비스에 대한 사용자 지정 스크립트가 포함되어 있습니다.
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script> <script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.14/angular.min.js"></script> <script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.14/angular-route.min.js"></script> <script src="/lib/ngStorage.js"></script> <script src="/lib/loading-bar.js"></script> <script src="/scripts/app.js"></script> <script src="/scripts/controllers.js"></script> <script src="/scripts/services.js"></script> </body>
AngularJS용 ngStorage
라이브러리를 사용하여 브라우저의 로컬 저장소에 토큰을 저장하여 Authorization
헤더를 통해 각 요청에 대해 토큰을 보낼 수 있습니다.
물론 프로덕션 환경에서는 성능을 향상시키기 위해 모든 스크립트 파일과 스타일시트를 축소하고 결합합니다.
사용자의 로그인 상태에 따라 적절한 링크의 가시성을 변경하는 부트스트랩을 사용하여 탐색 모음을 만들었습니다. 로그인 상태는 컨트롤러 범위에 있는 token
변수에 따라 결정됩니다.
<div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target=".navbar-collapse"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">JWT Angular example</a> </div> <div class="navbar-collapse collapse"> <ul class="nav navbar-nav navbar-right"> <li data-ng-show="token"><a ng-href="#/restricted">Restricted area</a></li> <li data-ng-hide="token"><a ng-href="#/signin">Sign in</a></li> <li data-ng-hide="token"><a ng-href="#/signup">Sign up</a></li> <li data-ng-show="token"><a ng-click="logout()">Logout</a></li> </ul> </div>
라우팅
모든 프런트 엔드 경로를 구성하는 역할을 하는 app.js
라는 파일이 있습니다.
angular.module('app', [ 'ngStorage', 'ngRoute', 'angular-loading-bar' ]) .constant('urls', { BASE: 'http://jwt.dev:8000', BASE_API: 'http://api.jwt.dev:8000/v1' }) .config(['$routeProvider', '$httpProvider', function ($routeProvider, $httpProvider) { $routeProvider. when('/', { templateUrl: 'partials/home.html', controller: 'HomeController' }). when('/signin', { templateUrl: 'partials/signin.html', controller: 'HomeController' }). when('/signup', { templateUrl: 'partials/signup.html', controller: 'HomeController' }). when('/restricted', { templateUrl: 'partials/restricted.html', controller: 'RestrictedController' }). otherwise({ redirectTo: '/' });
여기에서 HomeController
또는 RestrictedController
에 의해 처리되는 4개의 경로를 정의했음을 알 수 있습니다. 모든 경로는 부분 HTML 보기에 해당합니다. 또한 백엔드에 대한 HTTP 요청에 대한 URL을 포함하는 두 개의 상수를 정의했습니다.
요청 인터셉터
AngularJS의 $http 서비스를 사용하면 백엔드와 통신하고 HTTP 요청을 할 수 있습니다. 우리의 경우 모든 HTTP 요청을 가로채서 사용자가 인증된 경우 JWT가 포함된 Authorization
헤더를 삽입하려고 합니다. 인터셉터를 사용하여 전역 HTTP 오류 처리기를 만들 수도 있습니다. 다음은 브라우저의 로컬 저장소에서 사용할 수 있는 경우 토큰을 주입하는 인터셉터의 예입니다.
$httpProvider.interceptors.push(['$q', '$location', '$localStorage', function ($q, $location, $localStorage) { return { 'request': function (config) { config.headers = config.headers || {}; if ($localStorage.token) { config.headers.Authorization = 'Bearer ' + $localStorage.token; } return config; }, 'responseError': function (response) { if (response.status === 401 || response.status === 403) { $location.path('/signin'); } return $q.reject(response); } }; }]);
컨트롤러
controllers.js
파일에서 애플리케이션에 대해 HomeController
및 RestrictedController
라는 두 개의 컨트롤러를 정의했습니다. HomeController
는 로그인, 가입 및 로그아웃 기능을 처리합니다. 로그인 및 가입 양식의 사용자 이름 및 비밀번호 데이터를 Auth
서비스로 전달하여 HTTP 요청을 백엔드로 보냅니다. 그런 다음 백엔드의 응답에 따라 토큰을 로컬 저장소에 저장하거나 오류 메시지를 표시합니다.
angular.module('app') .controller('HomeController', ['$rootScope', '$scope', '$location', '$localStorage', 'Auth', function ($rootScope, $scope, $location, $localStorage, Auth) { function successAuth(res) { $localStorage.token = res.token; window.location = "/"; } $scope.signin = function () { var formData = { email: $scope.email, password: $scope.password }; Auth.signin(formData, successAuth, function () { $rootScope.error = 'Invalid credentials.'; }) }; $scope.signup = function () { var formData = { email: $scope.email, password: $scope.password }; Auth.signup(formData, successAuth, function () { $rootScope.error = 'Failed to signup'; }) }; $scope.logout = function () { Auth.logout(function () { window.location = "/" }); }; $scope.token = $localStorage.token; $scope.tokenClaims = Auth.getTokenClaims(); }])
RestrictedController
는 동일한 방식으로 작동하지만 Data
서비스에서 getRestrictedData
및 getApiData
함수를 사용하여 데이터를 가져옵니다.
.controller('RestrictedController', ['$rootScope', '$scope', 'Data', function ($rootScope, $scope, Data) { Data.getRestrictedData(function (res) { $scope.data = res.data; }, function () { $rootScope.error = 'Failed to fetch restricted content.'; }); Data.getApiData(function (res) { $scope.api = res.data; }, function () { $rootScope.error = 'Failed to fetch restricted API content.'; }); }]);
백엔드는 사용자가 인증된 경우에만 제한된 데이터를 제공할 책임이 있습니다. 즉, 제한된 데이터로 응답하려면 해당 데이터에 대한 요청이 Authorization
헤더 또는 쿼리 문자열 내에 유효한 JWT를 포함해야 합니다. 그렇지 않은 경우 서버는 401 Unauthorized 오류 상태 코드로 응답합니다.
인증 서비스
인증 서비스는 백엔드에 대한 로그인 및 가입 HTTP 요청을 담당합니다. 요청이 성공하면 응답에 서명된 토큰이 포함되며 이 토큰은 base64로 디코딩되고 동봉된 토큰 클레임 정보가 tokenClaims
변수에 저장됩니다. 이것은 getTokenClaims
함수를 통해 컨트롤러에 전달됩니다.
angular.module('app') .factory('Auth', ['$http', '$localStorage', 'urls', function ($http, $localStorage, urls) { function urlBase64Decode(str) { var output = str.replace('-', '+').replace('_', '/'); switch (output.length % 4) { case 0: break; case 2: output += '=='; break; case 3: output += '='; break; default: throw 'Illegal base64url string!'; } return window.atob(output); } function getClaimsFromToken() { var token = $localStorage.token; var user = {}; if (typeof token !== 'undefined') { var encoded = token.split('.')[1]; user = JSON.parse(urlBase64Decode(encoded)); } return user; } var tokenClaims = getClaimsFromToken(); return { signup: function (data, success, error) { $http.post(urls.BASE + '/signup', data).success(success).error(error) }, signin: function (data, success, error) { $http.post(urls.BASE + '/signin', data).success(success).error(error) }, logout: function (success) { tokenClaims = {}; delete $localStorage.token; success(); }, getTokenClaims: function () { return tokenClaims; } }; } ]);
데이터 서비스
이것은 일부 더미 제한된 데이터에 대해 API 서버뿐만 아니라 인증 서버에 요청하는 간단한 서비스입니다. 요청을 만들고 성공 및 오류 콜백을 컨트롤러에 위임합니다.
angular.module('app') .factory('Data', ['$http', 'urls', function ($http, urls) { return { getRestrictedData: function (success, error) { $http.get(urls.BASE + '/restricted').success(success).error(error) }, getApiData: function (success, error) { $http.get(urls.BASE_API + '/restricted').success(success).error(error) } }; } ]);
이 JSON 웹 토큰 튜토리얼을 넘어서
토큰 기반 인증을 사용하면 특정 인증 체계에 연결되지 않은 분리된 시스템을 구성할 수 있습니다. 토큰은 토큰 서명에 동일한 비밀 키를 사용하는 모든 시스템에서 생성되고 사용할 수 있습니다. 모바일 지원이 가능하며 쿠키 사용을 요구하지 않습니다.
JSON 웹 토큰은 모든 인기 있는 프로그래밍 언어에서 작동하며 빠르게 인기를 얻고 있습니다. 그들은 Google, Microsoft 및 Zendesk와 같은 회사의 지원을 받습니다. IETF(Internet Engineering Task Force)의 표준 사양은 아직 초안 버전이며 향후 약간 변경될 수 있습니다.
보안 세부 정보를 처리하는 방법 및 만료된 토큰을 새로 고치는 것과 같이 JWT에 대해 다루어야 할 내용이 아직 많이 있지만 JSON 웹 토큰 자습서는 기본 사용법과 더 중요하게는 JWT 사용의 이점을 보여주어야 합니다.
Toptal 엔지니어링 블로그에 대한 추가 정보:
- Node.js/TypeScript REST API 빌드, 3부: MongoDB, 인증 및 자동 테스트