스프링 시큐리티와 같은 보안 기술을 이해 하는게 쉽지않다... 이번에 jwt를 이용해 회원가입 및 로그인 구현을 하는데 모르는 상태에서 남의 코드를 쓸려고 하니 구현을 못해 이번에 공부하면서 익힐려고 합니다.
인증은 보호된 리소스에 접근하는 대상, 즉 사용자에게 적절한 접근 권한이 있는지 확인하는 일련의 과정.
이때 보호된 리소스에 접근하는 대상(사용자)를 접근 주체(Principal)이라고 한다.
권한은 인증 절차가 끝난 접근 주체가 보호된 리소스에 접근 가능한지를 결정하는 것을 의미 합니다. 이때 권한을 부여하는 작업을 인가(Authorize)이라고 한다.
쉽게 이해하자면 인증은 아이디,비밀번호로 로그인 하는 과정이며, 권한이 필요한 리소스에 접근 하기 위해선 당연히 이러한 인증 과정을 거쳐야 한다.
스프링 시큐리티는 이런 인증 매커니즘을 간단하게 만들 수 있도록 다양한 옵션들을 제공하고 있다.
또한 웹 요청이나 메소드 호출, 도메인 인스턴스에 대한 접근 등 상당히 깊은 수준의 권한 부여를 제공하고 있다.
스프링 시큐리티는 주로 서블릿 필터와 이들로 구성된 필터체인을 사용하고 있다. 서블릿 필터와 관련된 설명은 이전 포스팅을 참조 부탁드린다. 그렇다면 실제 로그인 시에 스프링 시큐리티의 동작 플로우를 바탕으로 인증과 스프링 시큐리티 아키텍처를 보자면.
개략적인 인증 과정의 흐름은
1. 사용자가 로그인 정보와 함께 인증 요청(Http Request)
2. AuthemticationFilter가 이 요청을 가로챈다. 이때 가로챈 정보를 통해 UsernamePasswordAuthenticationToken이라는 인증용 객체를 생성한다.
3. Authentication Manager의 구현체인 ProviderManager에게 UsernamePassword AuthenticationToken 객체를 전달한다.
4. 다시 AuthenticationProvider에 UsernamePasswordAuthenticationToken 객체를 전달한다.
5. 실제 데이터베이스에서 사용자 인증정보를 가져오는 UsernamePasswordAuthenticationToken 객체를 전달한다.
6. 넘겨받은 사용자 정보를 통해 DB에서 찾은 사용자 정보인 UserDetails 객체를 만든다. 이때 UserDetails는 인증용 객체와 도메인용 객체를 분리하지 않고 인증용 객체에 상속해 사용한다.
7. AuthenticationProvider는 UserDetails를 넘겨받고 사용자 정보를 비교한다.
8. 인증이 완료되면 권한 등의 사용자 정보를 담은 Authentication 객체를 반환한다.
9. 다시 최초의 AuthenticationFilter에 Authentication 객체가 반환된다.
10. Authentication 객체를 SecurityContext에 저장한다.
최종적으로 SecurityContextHolder는 세션 영역에 있는 SecurityContext에 Authentication 객체를 저장한다. 세션에 사용자 정보를 저장한다는 것은 스프링 시큐리티가 전통적인 세션-쿠키 기반의 인증 방식을 사용한다는 것이다.
실제로 스프링 시큐리티는 훨씬 다양한 필터 체인을 사용해 다양한 커스터마이징을 할 수 있도록 돕는다. 대략 적인 내용은
Json Web Token의 약자.
JSON 객체를 통해 안전하게 정보를 전송할 수 있는 웹표준(RFC7519)이다. JWT는 '.'을 구분자로 세 부분으로 구분되어 있는 문자열로 이루어져 있습니다. 각각 헤더는 토큰 타입과 해싱 알고리즘을 저장하고, 내용은 실제로 전달할 정보, 서명에는 위변조를 방지하기위한 값이 들어가게 됩니다.
구성 요소는 Header,Payload,Signature 세가지로 구성된다.
Header
Header는 토큰의 타입이나, 전자서명 시 어떤 알고리즘이 사용됐는지 저장한다.
예제 : HS512 알고리즘
Payload
Payload에는 보통 Claim이라는 토큰에서 사용할 정보들이 담겨있다.
위에 있는 사진엔 key-value 형식으로 이루어진 하나의 쌍들이 모두 Claim이다.
인증시에 토큰에서 실제로 사용될 정보를 의미한다.
여러 Claim들을 JWT 토큰 생성 시에 개발자가 어떤 Claim을 넣을지 정한 후 마음대로 넣을 수 있다.
JWT의 표준 스펙에는 7가지의 Claim이 정의되어 있다.
근데 꼭 7가지를 모두 포함해야 하는건 아니다.
1. iss(Issuer) : 토큰 발급자
2. sub(Subjec) : 토큰 제목 - 토큰에서 사용자에 대한 식별값이 된다.
3. aud(Audience) : 토큰 대상자
4. exp(Expiration Time) : 토큰 만료 시간
5. nbf(Not Before) : 토큰 활성 날짜 (이 날짜 이전의 토큰은 활성화 되지 않음을 보장)
6. iat(Issued At) : 토큰 발급 시간
7. jti(JWT Id) : JWT 토큰 식별자 (issuer가 여러 명일 때 구분하기 위한 값)
이런 식으로 표준 스펙들이 있는데 필요하면 개발자가 추가로 작성해도된다.
구현할때 토큰에서 사용자의 email를 추출하기위해 사용자 정의 claim인 'email'를 별도로 추가 하면된다.
주의 해야할점은 Payload에는 암호화가 되어 있지 않기 때문에, 민감한 정보를 담지 않아야한다.
누구나 JWT Decoding을 통해 Payload의 정보를 볼수 있기 때문에 민감한 정보를 안넣고 식별하는 정보만 담아야한다.
API 서버는 로그인 요청이 완료되면 클라이언트에게 회원을 구분할 수 있는 정보를 담은 JWT를 생성해 전달합니다. 그러고 클라이언트는 이 JWT를 헤더에 담아서 요청을 하게 됩니다. 권한이 필요한 요청이 있을때 마다 API 서버는 헤더에 담긴 JWT 값을 확인하고 권한이 있는 사용자인지 확인하고 리소스를 제공하게 된다.
이렇게 세션-쿠키 기반의 로그인이 아닌 JWT 같은 토큰 기반의 로그인을 하면 다중 서버 환경에서도 로그인을 유지할 수 있고, 한번 로그인으로 유저 정보를 공유하는 여러 도메인에서 사용할 수 있는 장점이 있다.
개발자로서 성장하는 데 큰 도움이 된 글이었습니다. 감사합니다.