JWT를 이용한 인증/인가 + Security : JwtProvider
사전 작업
- User 와 UserRoleType Entity 생성
- 조건: User 가 갖는 UserRoleType을 collection 타입으로 설정
클래스 분류
- JWT 정의하기 (JWT 생성과 소멸, 가져오기, 확인하기 >> 토큰을 통한 사용자 정보 얻기)
- JwtAuthenticationFilter 설정하기 (클라이언트가 로그인시 사용, 정의된 JWT 를 생성하는 과정에 넣음)
- JwtAuthorizationFilter 설정하기 (클라이언트가 데이터 요청시 사용, 쿠키에서 JWT를 가져와서 확인할 때 정의된 JWT를 넣음)
- SecurityConfig 설정하기 (Security >> AuthenticationFilter >> AuthorizationFilter 순서도 설정해야 하므로 4번째)
JwtProvider (토큰 생성/제공하는 클래스) - 또는 JwtUtil 등으로 이름 붙이는 클래스
<로그인 요청 받으면 작동할 때 필요한 메서드 (>> 추후 로그인 메서드 필요**)>
1. JWT 생성 (Access, Refresh)
- UsernamePasswordAuthenticationToken 을 생성 (request로 받은 username, password 이용)
- authentication 객체 생성후
- 이 authentication으로 토큰을 발급함
*UsernamePasswordAuthenticationToken는
Authentication을 implements한 AbstractAuthenticationToken의 하위 클래스로,
"인증이 완료된 후" 인증객체를 만드는데 사용
2. JWT 쿠키에 저장 또는 JSON 방식으로 응답** (아래 더 보기 참고)
<데이터 요청을 받은 후 필요한 메서드>
1. 쿠키 또는 JSON에서 JWT 찾기 (Access, Refresh)
& JWT 검증 (유효기간, 위변조)
: Spring Security의 필터를 사용하여 토큰의 추출 및 검증, 인증 객체를 SecurityContext에 설정 자동화
*Spring Security의 BearerTokenResolver를 사용하여 자동으로 토큰 추출 (필터의 일부 기능)
** BearerTokenResolver 는 Bearer를 제거한 토큰을 추출할 때 사용되는 인터페이스
***구조: 필터는 보통 doFilterInternal 메서드에서 BearerTokenResolver를 사용하여 토큰을 추출하고,
그 토큰의 유효성을 검증한 후, 인증 객체를 생성하는 방식으로 작동
JWT 토큰을 클라이언트에 전달하는 방식에는 두 가지 주요 방법이 있으며, 각각의 방법에는 장단점이 있습니다. 선택은 보안 요구 사항, 애플리케이션의 구조, 사용 편의성 등 여러 요소에 따라 달라집니다.
1. JSON 응답으로 전달
- 설명: JWT 토큰을 JSON 응답의 일부로 클라이언트에게 전달합니다. 클라이언트는 이 토큰을 로컬 스토리지나 세션 스토리지에 저장하고, 이후 요청 시 HTTP 헤더(예: Authorization: Bearer <token>)에 토큰을 포함시켜 서버로 전송합니다.
- 장점:
- 서버 간 통신의 유연성: API 요청 시 특정 헤더에 토큰을 포함시켜 전달하기 때문에, CORS 문제를 피하기가 쉽습니다.
- 어플리케이션 간의 유연한 사용: 여러 도메인 간에서 동일한 토큰을 사용할 수 있습니다.
- 제어 가능성: 클라이언트가 언제, 어떻게 토큰을 전송할지 명확하게 제어할 수 있습니다.
- 단점:
- 보안 이슈: 로컬 스토리지나 세션 스토리지를 사용하는 경우 XSS(교차 사이트 스크립팅) 공격에 취약할 수 있습니다. 악성 스크립트가 클라이언트의 저장소에 접근할 수 있다면, 토큰을 탈취당할 위험이 있습니다.
- 개발자 책임 증가: 개발자가 수동으로 토큰을 관리하고, 요청마다 이를 헤더에 추가해야 합니다.
2. 쿠키에 저장
- 설명: JWT 토큰을 쿠키에 저장하고, 브라우저가 자동으로 서버에 쿠키를 전송하도록 합니다. 이 방식에서는 클라이언트가 쿠키에 저장된 토큰을 신경 쓰지 않아도, 모든 요청에 자동으로 토큰이 포함됩니다.
- 장점:
- 편리함: 쿠키를 사용하면 클라이언트가 요청 시 토큰을 직접 관리할 필요가 없습니다. 브라우저가 자동으로 쿠키를 포함시켜 서버에 전송합니다.
- 보안: HttpOnly 및 Secure 플래그를 설정하면, 클라이언트 측 스크립트에서 쿠키에 접근할 수 없게 되어 XSS 공격에 대한 보호가 강화됩니다. SameSite 속성을 이용해 CSRF(교차 사이트 요청 위조) 공격도 방어할 수 있습니다.
- 단점:
- CORS 문제: 서로 다른 도메인에서 API 요청을 할 때, 쿠키를 전송하려면 CORS 설정을 신중하게 구성해야 합니다. 특히, Access-Control-Allow-Credentials를 설정하는 데 신경 써야 합니다.
- 스케일링 문제: 여러 서브도메인 간에 쿠키를 사용하려면 추가 설정이 필요할 수 있습니다.
결론: 어느 방식이 더 좋은가?
- 보안과 편리성을 우선한다면, 쿠키에 저장하는 방법이 더 안전합니다. 특히, HttpOnly, Secure, SameSite 플래그를 사용하면 클라이언트 측 스크립트로부터 토큰을 보호할 수 있어 보안이 강화됩니다.
- 유연성과 제어 가능성을 원한다면, JSON 응답으로 전달하는 방법이 더 좋을 수 있습니다. 이 방법은 여러 도메인에서의 사용이 필요하거나, API 설계에서의 유연성이 중요한 경우 유리합니다.
보안 요구 사항이 높은 경우 쿠키를 사용하는 것이 일반적으로 권장됩니다. 하지만, 애플리케이션의 특성에 따라 두 방법을 적절히 혼합하여 사용할 수도 있습니다
두 가지 방식을 혼합하여 사용하는 전략은 보안과 유연성 모두를 고려한 좋은 접근 방식입니다. 이를 통해 보안이 덜 중요한 페이지와 보안이 중요한 페이지에서 각각 다른 수준의 보안을 제공할 수 있습니다. 아래는 이러한 접근 방식의 개요입니다.
1. 일반적인 페이지에서는 JSON 방식 사용
- 처음 로그인 시: 사용자가 로그인하면, 서버는 JWT 토큰을 JSON 응답으로 반환하고, 클라이언트는 이 토큰을 로컬 스토리지나 세션 스토리지에 저장합니다.
- 보안 수준: 일반적인 보안 수준이 요구되는 페이지에서는 이 토큰을 이용해 API 요청을 보냅니다. 클라이언트는 HTTP 헤더에 토큰을 포함시켜 서버에 전송하며, 서버는 이를 통해 인증을 수행합니다.
- 장점: 클라이언트 측에서 다양한 도메인 간 API 요청이 가능하고, 사용자가 쉽게 토큰을 제어할 수 있습니다.
2. 보안이 중요한 페이지에서는 쿠키 사용
- 추가 인증 절차: 사용자가 더 민감한 정보를 다루는 페이지에 접근하려고 할 때, 서버는 추가 인증을 요구할 수 있습니다. 이 과정에서 서버는 JWT 토큰을 HttpOnly 및 Secure 플래그가 설정된 쿠키에 저장하여 클라이언트에 전송합니다.
- 보안 강화: 쿠키에 저장된 토큰은 브라우저가 자동으로 포함하여 서버로 전송하기 때문에, 클라이언트 측 스크립트로부터 안전합니다. 이를 통해 XSS나 CSRF와 같은 공격에 대한 방어를 강화할 수 있습니다.
- 예시: 사용자가 결제 페이지에 접근하거나, 중요한 설정을 변경하는 페이지에 접근할 때, 추가적인 인증 과정을 거쳐 쿠키에 저장된 토큰을 발급하는 방식입니다.
3. 혼합 접근의 장점
- 유연성: 일반적인 페이지에서 클라이언트는 더 유연하게 JWT를 사용할 수 있고, 필요에 따라 다양한 도메인에서 API 요청을 할 수 있습니다.
- 보안 강화: 민감한 작업을 수행할 때는 추가적인 보안 계층을 통해 사용자를 보호할 수 있습니다. 쿠키에 저장된 토큰은 CSRF와 XSS 공격에 더 강력한 방어 수단을 제공합니다.
4. 구현 방법
- 초기 로그인 시: JWT를 JSON 응답으로 반환하여 클라이언트 측에 저장.
- 민감한 작업 시 추가 인증: 사용자가 보안이 필요한 페이지에 접근할 때, 추가 인증 과정을 통해 서버가 JWT를 쿠키에 저장하고, 클라이언트는 해당 쿠키를 사용하여 민감한 작업을 진행.
- 쿠키와 JSON 응답의 조합: 필요한 경우 특정 API 호출은 쿠키를 통해 인증하고, 나머지는 JSON 응답으로 전달된 JWT를 통해 처리하도록 API 설계를 나눌 수 있습니다.
5. 주의 사항
- 복잡도 증가: 두 가지 인증 방식을 혼합하면 구현과 유지보수가 다소 복잡해질 수 있습니다.
- CORS 설정: 쿠키를 사용할 때는 CORS 정책을 신중하게 구성해야 합니다. 특히 Access-Control-Allow-Credentials 헤더를 적절히 설정해야 합니다.
이런 혼합 접근 방식은 사용자의 보안 요구 사항에 따라 다양한 보호 수단을 제공할 수 있으며, 중요한 작업에 대해 더 강화된 보안을 적용할 수 있는 유연한 설계입니다.
따라서 일단은 JSON 방식으로 구현하기로 결정함
property 클래스 를 사용하기도 함
*속성을 정의할 때 사용
>> JwtProperty
AccessToken 의 단위시간
RefreshToken의 단위시간
secretKey
..
등을 정의해둘 수 있음
추후 JWT 와 Security를 함께 사용하기 위해 필요한 정보
SecurityContextHolder 내부엔
- SecurityContext 가 들어있고, 그 내부에는
-- Authentication (Principal, Credential, Authorities) 가 들어있음
>> Authentication 구현체로
UsernamePasswordAuthenticationToken(userDetails, password, authorities)이 가장 많이 쓰임
추후 작성할 User의 권한을 USER 와 ADMIN 으로 정의
참고
https://velog.io/@rmswjdtn/SpringSecurity-Servlet-Authentication-Architecture
인증, 인가 처리에 실패한 경우 예외처리
https://non-stop.tistory.com/667