2021. 11. 26. 14:54ㆍ작업일지
개요
지금까지는 인증 로직에 보안이 왜 필요한지, 그리고 인증의 종류와 로직의 흐름과 스프링 시큐리티의 구조를 간략하게 알아보았다. 이번에는 내가 설계했던 로그인, 회원가입, 리소스 접근 통제 관련 로직의 흐름을 스프링 시큐리티로 적용해보도록 하겠다.
INDEX
- 로그인
- 회원가입
- 인증 리소스 접근
로그인
저번 작업일지에서 Security가 필요한 서비스의 로직의 플로우 차트를 간단하게 정리한 적이 있었다. 그 중에서 로그인과 관련된 플로우 차트는 다음과 같았다.
로직의 흐름을 간략하게 설명해보면, 최초 로그인시 아이디/비밀번호를 입력 받으면,
- 해당 회원 정보가 있는지, 조회하고,
- 없으면 예외를 던지고,
- 있으면, 입력한 비밀번호가 저장된 회원의 비밀번호가 일치한지 확인한다.
- 비밀번호 검증이 이상없이 완료되면, 로그인이 성공하여, 유효한 응답 메세지를 보내고,
- 일치하지 않으면 예외를 던진다.
이제 1-5의 로직을 스프링 시큐리티로 구현하면 되는데... 스프링 시큐리티에서는 인증을 어떻게 진행할까? 저번 작업일지에서 다뤄본 적이 있다.
저번 일지에서도 언급했듯이, 스프링 시큐리티를 활용하는데, 컨텍스트의 모든 흐름을 파악할 필요는 없다. 결국 개발자가 활용하는 부분은 4-8 이다. 따라서, 스프링 시큐리티의 인증구조를 나의 로직에 맞게 적용해보면, 다음과 같을 것이다.
위의 구조와 비교해보면 UserDetails와 UserDetailsService가 없어진걸 알 수 있는데, 이유는 다음과 같다.
- 현재 진행하고 있는 프로젝트는 Token Based 인증을 진행하는데, 기존의 세션 기반의 인증 방식과는 다르다. 따라서, 기존의
DaoAuthenticationProvider
가 아니라,AuthenticationProvider
를 커스텀하여 사용해야 했다. - 따라서,
DaoAuthenticationProvider
에서 사용하는 스프링 시큐리티의UserDetailsService
를 사용하기 보다, 프로젝트에 맞는 서비스 로직을 사용해야 한다.
로그인 인증 서비스 Description
위의 그림의 1-4을 순서대로 설명해보겠다.
- AuthenticationManager.authenticate(...)
- 스프링 시큐리티의 AuthenticationManager를 통해, 스프링 시큐리티 자체의.. 혹은 커스텀한 AuthenticationProvier에게 인증 과정을 위임하는 과정이다.
- 정확히는
AuthenticationManagerDelegator
를 통해 위임을 한다. - 어떤 Provider에게 위임을 하는지 결정하는 방법은 다양하지만 가장 보편적인 방법은
WebSecurityConfigurerAdapter
를 통해 Provider를 지정하는 것이다.
- MyAuthenticationProvider.authenticate(...)
- 실질적으로 인증을 진행하는 메소드로, 스프링 시큐리티 자체의 인증 로직을 사용할 수 있지만, 개발자가 직접
AuthenticationProvider
인터페이스를 구현하여, 자기만의 인증로직을 구현할 수도 있다. - 이 과정에서
UserDetailsService
의 api를 활용하는 방법이 있지만, 무조건 사용해야하는 건 아니다.
- 실질적으로 인증을 진행하는 메소드로, 스프링 시큐리티 자체의 인증 로직을 사용할 수 있지만, 개발자가 직접
- AuthMemberService
- 토큰 기반의 인증을 진행하기 위한 서비스 로직이 들어있는 오브젝트로, 내가 커스텀한 것으로 기존의
UsersDetailsService
의 역할을 대신해준다.
- 토큰 기반의 인증을 진행하기 위한 서비스 로직이 들어있는 오브젝트로, 내가 커스텀한 것으로 기존의
- *return *authentication
- Provider의 인증로직이 이상없이 통과된다면,
Authentication
객체를 리턴한다. Authentication
는 스프링 시큐리티에서 제공하는 인증객체로 기본적으로는 시큐리티 컨텍스트에UsernamePasswordAuthenticationToken
를 제공할 수 있다.- 하지만, 경우에 따라
AbstractAuthenticationToken
를 확장하여, 커스텀한Authentication
을 사용할 수 있다.
- Provider의 인증로직이 이상없이 통과된다면,
- 그 외
- 인증 로직이 완료되면, Authentication 정보 외에 토큰 키값을 발급해준다.
- 인증 정보가 일치하지 않은 경우에 발생하는 예외는
MyAuthenticationProvider
안에서 처리 하였다.
회원가입
사실 회원가입 자체는 별로 고민할만한 요소가 없었다. 회원가입은 결국 DB에 데이터를 추가하는 것이기 때문이다. 다만, 중복확인 기능에 대한 고민을 했었는데, API 개발 특성상, 회원가입 api 자체에 중복확인 로직을 넣는 것 보다는, 회원가입은 사용자 등록 기능 자체를... 중복확인에 대한 api는 따로 분리해서 구현하는 쪽으로 방향을 잡았다.
인증 리소스 접근
인증 리소스에 접근한다는 것이 모호하다면, 게시판에 게시글을 수정하는 경우를 생각해보면 쉽다. 게시글 수정의 경우, 대체로, 글을 작성한 사용자 본인만 허용한다. 본인이 맞는지 확인하는 과정은 총 2가지이다.
- 사용자가 로그인을 했는가?
- 로그인을 했다면, 게시글 본인이 맞는가?하지만 1의 경우는 인증 관리 정책에 따라, 간단해질수도 있고, 복잡해질 수도 있다. 하지만, 내가 진행하고 있는 프로젝트의 경우, 세션ID를 쿠키에 저장하여, 세션 조회를 하는 방식이 아니라,
x-auth-token
헤더에 토큰키값을 포함하여, 리소스에 접근할 때마다, 필요하면 해당 토큰키가 세션서버에 있는지 조회하는 방식을 채택하였다. 이유는 쿠키의 경우, 웹 브라우저에서만 사용할 수 있는 로컬 데이터로, 앱 애플리케이션(안드로이드, IOS 등) 환경에서는 사용할 수 없기 때문이다. 따라서, HTTP 메세지 헤더에 세션ID를 토큰의 형태로 담아서, 세션 서버에 요청하여, 인증 상태를 확인하는 로직을 구현하고자 하였다. - 하지만, 세션 서버에 인증정보를 영구적으로 저장할 수는 없으므로, 인증 정보에 유효기간을 적용하였다. 유효기간의 경우, 너무 짧으면, 사용자에게 불편함(시도때도 없이 로그인을 해야한다.)을 줄 수 있고, 너무 길면, 악성 사용자에게 인증정보를 탈취당할 확률이 크다. 물론, 실제로 운영할 서비스를 개발하는 것이 아니라, 너무 디테일하게 신경쓸 필요는 없겠지만, 개발 지망생으로서, 집중적으로 공부를 할 수 있을 때, 디테일에 신경을 많이 쓰고 싶었다. 어쨋든... 사용자의 불편함을 해소하고, 보안을 유지하기위한 방법으로 유효기간의 경우, 다음과 같이 기준을 정하였다.
- 2의 경우는 비교적, 간단하다. 인증한 사용자의 정보와 게시글 작성자의 정보와 비교하기만 하면 되기 때문이다.
- 로그인 이후 발급 받은 토큰키의 유효기간은 기본적으로 30분으로 정하였다.
- 고정적으로 30분으로 정하는 것도 상황에 따라 사용자에게 불편함을 줄 수 있으므로,(나의 서비스의 경우, 로그인 해놓고, 30분 동안 아이쇼핑을 하고, 주문을 하는 순간, 다시 로그인을 해야할 것이다.) 만약 로그인한 사용자의 토큰이 10분미만이 남은 상태에서, 다른 리소스에 접근하면, 유효기간을 다시 30분으로 리셋하는 방식을 채택하였다.
플로우 차트를 대략적으로 설명 해보았으니, 이제 1-2의 과정을 스프링 시큐리티에 적용 해보자. 위에서 정리해본대로라면, 구현해야하는 기능은 2가지이다.
- 로그인 여부 확인
- 로그인 정보 검증
1.
의 경우는 로그인과는 과정은 다르지만, 사용자를 인증한다는 점에서는 거의 동일하다고 볼 수 있다. 다만, 전자의 경우는 아이디/비밀번호로 인증을 시도하고, 후자의 경우는x-auth-token
의 토큰 키값을 통해 인증을 한다는 것이다. 또 하나의 차이점은 인증 프로세스의 차이이다. 로그인의 경우AuthenticationProvider
를 거치지만, 후자의 경우는SecurityFilterChain
에서 인증을 한다. 저번 작업 일지에서 작성한 그 외의 Filter들 목록을 보면, 인증 상태를 확인하는 방법에 대한 방법을 짐작해볼 수 있는 필터목록이 있다. 전부다 알 필요는 없지만UsernamePasswordAuthenticationFilter
에 주목을 해보자.
SecurityFilterChain 활용하기
UsernamePasswordAuthenticationFilter
의 경우 로그인을 시도할 경우 사용하는 필터로 알고 있지만, 미인증 사용자가 인증을 요구하는 리소스에 접근하려고 할때도 작동하는 필터이다. 미인증 사용자의 경우, UsernamePasswordAuthenticationFilter
가 작동하여, 인증 실패 예외가 뜨기 때문에, 에러 페이지, 혹은 에러 메세지를 응답하도록 구현이 되어 있으며, 만약, UsernamePasswordAuthenticationFilter
를 거칠 때, 이미 요청한 사용자가 인증이 처리된 상태(즉 Security Context에 Authentication 토큰이 존재하는 상태)라면, UsernamePasswordAuthenticationFilter
는 동작하지 않고 바로 다음 필터로 넘긴다.
이러한 UsernamePasswordAuthenticationFilter
를 활용하여, 이 필터의 이전에, x-auth-token
을 가진 사용자에게 SecurityContext
지정해준다면, UsernamePasswordAuthenticationFilter
가 동작해서 사용자를 떨쳐내려고 해도, 유효한 인증정보가 있기 때문에, 결과적으로는 인증된 사용자가 인증 리소스에 접근할 수 있도록 허용을 해줄 것이다. 정리하자면, 1의 경우, 위에서 설계한 인증 리소스 접근의 플로우 차트를 구현하는 커스텀 필터를 UsernamePasswordAuthenticationFilter
바로 앞에 적용하여, 인증 리소스에 접근하는 로직을 구현할 것이다.
다음은.. 2.
의 경우이다. 이 경우엔, 관리자 페이지 접근권한을 생각하면 될 것 같다. 똑같은 회원이지만, 해당 회원이 일반 사용자느냐, 혹은 관리자의 권한을 가졌느냐에 따라, 접근을 통제해야하는 상황과 비슷할 수 있다.
스프링 시큐리티는 위와 같은 경우에 대한 권한을 관리하는 것을 'Authorization' 이라고 한다. 이 'Authorization'을 관리하는 인터페이스로AccessDecisionManager
가 있으며, 이 AccessDecisionManager
을 통해 권한을 관리하여 사용자의 리소스 접근을 통제한다. 만약 권한을 거절할 경우, AccessDeniedException
을 던진다.
Voting-Based AccessDecisionManager
AccessDecisionManager
를 직접 구현하여, 접근 통제를 완전히 통제하는 방법도 있지만, 스프링 시큐리티에는 Authorization의 process를 좀 더 체계적으로 진행할 수 있는 기준을 제공해준다. 스프링 시큐리티에선 이걸 Voting Decision Manager라고 한다.
이 Voting Decision Manager를 통해서, 여러가지의 경우에 따라, '투표'를 하여, 사용자가 리소스에 접근할 권한을 결정한다. 투표를 하는 기준에 대한 로직도 직접 구현이 가능(CustomVoter) 하지만, RoleVoter와 AuthenticatedVoter 같이 사용자의 인증여부, 혹은 사용자에게 부여된 역할에 따라, 접근 권한에 대해 '투표'를 하는 방법이 있다.
하지만, 스프링 시큐리티에서 제공하는 두가지의 voter 보다는 좀더 단순하면서 유연한 투표 정책을 적용하고 싶었다. 단순함이라 하면.. uri 자체만으로도 해당 api의 기능을 가늠할 수 있고(ex. POST : /api/member/1/order
) 그 uri에서 인증에 필요한 정보만 추출한 후(/api/member/1/order
에서 사용자 인증에 필요한 1
), 권한을 검증하여, '투표'를 하는 것이다.
그러한 의도로 UriBasedVoter
를 구현하여, 2.로그인 정보 검증
을 해결하였다. 이 부분에 대한 로직은.. 나중에 Voting-Based AccessDecisionManager
에 관해 자세하게 문서화를 할 때, 이야기를 진행해보도록 하겠다.
마무리
지금까지 스프링 시큐리티를 적용하기 위해, 검토와 학습.. 그리고 운용을 해보는 과정을 거쳤는데, 내용이 방대하여, 개인적으로 너무 어렵고 헷갈렸다. 특히 시행착오를 너무 많이 하고, 무분별한 삽질(?)을 하여, 최대한 어느정도 설명이 가능할때까지 정리를 하고, 문서화를 하는데에도 많은 시간이 걸렸다. 이외에도, 회원가입, 로그인, 마이페이지 등, 회원과 관련된 여러 기능도 있지만, 이 부분은 결국엔 그저 단순한 CRUD 로직이라 할 수 있어, 굳이 문서화를 할 필요는 없을거라 판단하여, 그 부분은 생략하고, 보안 요소를 검토하고, 스프링 시큐리티를 적용하는 과정을 중점으로 진행을 하게 되었다.
'작업일지' 카테고리의 다른 글
작업 일지#9 테스트 개선 -2- (0) | 2021.12.23 |
---|---|
작업 일지#8 테스트 개선 -1- (0) | 2021.12.09 |
작업 일지#6 서비스에 Security를 적용해보자 -2- 스프링 시큐리티의 구조 이해하기 (0) | 2021.11.25 |
작업 일지#5 서비스에 Security를 적용해보자 -1- (0) | 2021.11.14 |
Simple Handling Exception 라이브러리 : 왜 Exception Handling을 처리해야 하는가? -1- (0) | 2021.11.01 |