Q> JWT(JSON Web Token)기반 인증 및 권한관리 구현시 보안강화를 위한 방안 문의
개발 프로젝트에서 JWT기반의 인증과 권한관리를 구현하고자 합니다.
JWT(JSON Web Token)기반 인증 및 권한관리 구현시, 클라이언트 정보를 활용하여 보안을 강화할 수 있는 구체적인 구현방안에 대한 도움을 요청합니다.
A>
- 최근 JWT(JSON Web Token)기반으로 사용자 인증 및 권한관리를 구현하는 경우가 많아졌다.
- JWT는 전통적인 session 방식과 다르게, 서버에 인증정보를 갖지 않아도 되는 특장점들이 있기 때문에 기존의 전통적인 session기반의 인증 처리와 함께 사용범위가 확대되고 있다.
- JWT 토큰 생성하는 부분을 개발할때, 클라이언트의 특징정보들을 활용하여 JWT보안 강화하는 방법들이 몇 가지있으며, 서비스 환경과 목적에 따라 선택적으로 사용하는 것이 바람직하다.
<> JWT생성시 클라이언트의 특징정보들을 활용하여 클라이언트를 식별할수 있는 방법
1. 클라이언트 IP주소 활용 방법
- 클라이언트의 IP 주소를 JWT의 Payload에 포함시키는 방법이다.
- 클라이언트의 IP 주소는 클라이언트를 식별하는 가장 기본적인 방법 중 하나이다.
- 서버에서 요청을 받을 때 마다, JWT의 IP 주소와 요청한 클라이언트의 IP 주소를 비교하여 일치하는지 검증한다.
- 이 방법은 클라이언트의 IP 주소가 변경되지 않을 경우에는 유용하지만, IP 주소가 변경될 가능성이 있거나 여러 IP 주소를 사용하는 경우에는 적합하지 않을 수 있습니다.
2. 클라이언트의 지문(Fingerprint)를 활용하는 방법
- 디바이스 지문은 클라이언트 디바이스의 여러 속성(예: 사용자 에이전트, 화면 해상도, 설치된 폰트, 브라우저 플러그인 등)을 종합하여 생성한 고유 식별자이다.
- 이러한 정보들은 클라이언트의 디바이스를 특정할 수 있기 때문에 Payload를 이용하여 유용하게 사용될수 있다.
3. HTTP 헤더를 이용하는 방법
- HTTP 요청과 함께 전송되는 헤더 정보(예: User-Agent, Accept-Language, Accept-Encoding 등)는 클라이언트의 브라우저, 운영 체제, 언어 설정 등이 있다.
- 이 정보를 Payload에 적용하면 이용하여 클라이언트를 식별하는 데 도움이 될 수 있다.
4. Cookie 및 Session ID를 이용하는 방법
- 웹 애플리케이션은 사용자의 세션을 유지하기 위해 쿠키나 세션 ID를 사용한다.
- 이 정보는 클라이언트의 특정 세션을 식별하는 데 사용될 수 있다.
5. OAuth/OpenID 토큰을 이용하는 방법
- SNS 로그인이나 외부 인증 제공자를 통한 인증 과정에서 발급받은 토큰은 사용자의 신원을 확인하는 데 사용될 수 있다.
- 이 토큰은 JWT 클레임에 포함시켜 클라이언트를 식별하는 데 사용할 수 있다.
6. Custom 클라이언트 ID를 이용하는 방법
- 애플리케이션 내에서 사용자 또는 디바이스에 고유하게 할당된 식별자(ID)를 사용할 수 있다.
- 이는 API 키나 애플리케이션에서 생성한 고유한 사용자 ID일 수 있으며, 애플리케이션 마다 고유한 식별정보들이 많이 있기 때문에 이를 활용하는 방법이다.
7. 지리적 위치 정보를 이용하는 방법
- 클라이언트의 지리적 위치정보(예: 국가, 도시, 지역 코드)는 IP 주소나 GPS 정보를 통해 얻을 수 있다.
- 사용자의 위치를 기반으로 한 식별에 사용될 수 있다.
8. 시간대(Time Zone)를 이용하는 방법
- 클라이언트의 시간대 정보는 사용자의 지리적 위치와 연관된 식별 정보로 사용될 수 있다.
- 이 식별자는 어떤 경우에는 보안 검증이나 사용자 경험 개선에 활용될 수 있다.
<> 예제코드 - 클라이언트의 IP 주소를 JWT의 클레임에 포함하는 방법
- 아래의 Python 예제코드는 클라이언트의 IP주소를 이용하는 방법으로써, Pyjwt 라이브러리와 Flask 웹 프레임워크를 이용한 예제코드이다.
- '/create_token'는 사용자 ID를 받아 JWT를 생성하고, /verify_token 엔드포인트는 제공된 토큰을 검증한다.
- 클라이언트의 IP 주소는 토큰 생성 시 포함되며, 토큰 검증 시에도 이 IP 주소가 일치하는지 확인한다.
import jwt import datetime from flask import Flask, request, jsonify app = Flask(__name__) # 비밀키 설정 - 실제 환경에서는 보다 안전하게 관리해야함 SECRET_KEY = 'your_secret_key' # JWT 생성 함수 def create_token(user_id, client_ip): try: # 현재 시간 기준으로 토큰의 만료 시간을 설정 expiration_time = datetime.datetime.utcnow() + datetime.timedelta(hours=1) # JWT 페이로드에 사용자 ID와 클라이언트 IP, 만료 시간을 포함 payload = { 'user_id': user_id, 'ip': client_ip, 'exp': expiration_time } # JWT 생성 token = jwt.encode(payload, SECRET_KEY, algorithm='RS256') return token except Exception as e: # 오류 발생 시 처리 print(f"Token creation failed: {e}") return None # JWT 검증 함수 def verify_token(token, client_ip): try: # 토큰 디코드 payload = jwt.decode(token, SECRET_KEY, algorithms=['RS256']) # 클라이언트 IP 주소 검증 if payload['ip'] != client_ip: raise jwt.InvalidTokenError("IP address mismatch") return payload except jwt.ExpiredSignatureError: # 토큰 만료 오류 처리 print("Token has expired") return None except jwt.InvalidTokenError as e: # 기타 JWT 오류 처리 print(f"Invalid token: {e}") return None @app.route('/create_token', methods=['POST']) def create_token_route(): user_id = request.json.get('user_id') client_ip = request.remote_addr # 클라이언트의 IP 주소를 Flask를 통해 얻음 token = create_token(user_id, client_ip) if token: return jsonify({'token': token}), 200 else: return jsonify({'error': 'Token creation failed'}), 500 @app.route('/verify_token', methods=['POST']) def verify_token_route(): token = request.json.get('token') client_ip = request.remote_addr # 클라이언트의 IP 주소를 다시 확인 payload = verify_token(token, client_ip) if payload: return jsonify({'payload': payload}), 200 else: return jsonify({'error': 'Token verification failed'}), 500 if __name__ == '__main__': app.r0un(debug=True)
<> 예제코드 - 클라이언트 디바이스 지문을 활용하는 방법
- 아래의 Python 예제코드는 사용자 ID와 디바이스 지문, 만료 시간을 활용하여 클라이언트를 식별하는 예제코드이다.
from flask import Flask, request, jsonify import jwt import datetime app = Flask(__name__) # 비밀키 설정 - 실제 환경에서는 보다 안전하게 관리해야 함 SECRET_KEY = 'your_secret_key' # JWT 생성 함수 def create_token(user_id, device_fingerprint): """ JWT를 생성하는 함수 user_id: 사용자 식별자 device_fingerprint: 클라이언트 디바이스 지문 """ try: # 현재 시간 기준으로 토큰의 만료 시간을 설정 expiration_time = datetime.datetime.utcnow() + datetime.timedelta(hours=1) # JWT 페이로드에 사용자 ID와 디바이스 지문, 만료 시간을 포함 payload = { 'user_id': user_id, 'device_fingerprint': device_fingerprint, 'exp': expiration_time } # JWT 생성 token = jwt.encode(payload, SECRET_KEY, algorithm='RS256').decode('utf-8') return token except Exception as e: # 오류 발생 시 처리 print(f"Token creation failed: {e}") return None # JWT 검증 함수 def verify_token(token, device_fingerprint): """ JWT를 검증하는 함수 token: 검증할 JWT device_fingerprint: 클라이언트 디바이스 지문 """ try: # 토큰 디코드 payload = jwt.decode(token, SECRET_KEY, algorithms=['RS256']) # 디바이스 지문 검증 if payload['device_fingerprint'] != device_fingerprint: raise jwt.InvalidTokenError("Device fingerprint mismatch") return payload except jwt.ExpiredSignatureError: # 토큰 만료 오류 처리 print("Token has expired") return None except jwt.InvalidTokenError as e: # 기타 JWT 오류 처리 print(f"Invalid token: {e}") return None @app.route('/create_token', methods=['POST']) def create_token_route(): """ 토큰 생성 요청을 처리하는 라우트 """ user_id = request.json.get('user_id') device_fingerprint = request.json.get('device_fingerprint') # 클라이언트의 디바이스 지문을 요청에서 가져옴 token = create_token(user_id, device_fingerprint) if token: return jsonify({'token': token}), 200 else: return jsonify({'error': 'Token creation failed'}), 500 @app.route('/verify_token', methods=['POST']) def verify_token_route(): """ 토큰 검증 요청을 처리하는 라우트 """ token = request.json.get('token') device_fingerprint = request.json.get('device_fingerprint') # 검증 시 사용할 디바이스 지문을 요청에서 가져옴 payload = verify_token(token, device_fingerprint) if payload: return jsonify({'payload': payload}), 200 else: return jsonify({'error': 'Token verification failed'}), 500 if __name__ == '__main__': app.run(debug=True)