이전에 작성되었던 내용은 토큰을 전달할 때 그리고 검증할 때 사용하기 위한 부품을 만들었다 그럼 이제 그것을 사용해 보자 물론 나는 로그인에 사용할 예정이다.
시큐리티의 로그인
시큐리티에서 기본적으로 제공하는 로그인 과정은 굉장히 복잡하다. 시큐리티는 굉장히 많은 필터를 거쳐서 사용자를 인증하고 인가하면서 진행을 하는데 그중 로그인과 가장 큰 관련이 있는 UsernamePasswordAuthenticationFilter에 대해서 조금만 알아보자

- AntPathRequestMatcher
요청은 항상 URL을 보고 판단하게 되는데 이때 login 으로 시작하는 URL을 이 녀석이 잡는다고 생각하면 된다.
어쨌든 해당 URL 을 필터가 잡아야 진행이 시작된다. 만약 login 이 들어가지 않는다면 물론 다음 필터로 넘기면서 이 필터는 진행되지 않는다. - Authentication
본격적으로 인증이 시작을 해보자 우리는 로그인을 하면 어떠한 방식으로 아이디와 비밀번호를 전송한다. 그럼 그것을 이 필터가 Authentication이라는 객체에 넣는 것이다. 이제 이 객체를 이용해서 인증을 시작한다.
※ 명심해야 할것은 아직 인증이 된 것이 아니다. 그저 Authentication이라는 객체에 넣어놨을 뿐이다. - AuthenticationManager
Authentication 객체는 인증을 진행하는 Manager 에게로 가는데 Manager는 실질적으로 인증을 하지 않는다. Manager는 인증을 하는 Provider를 소개해 주는 역할이다. 이름이 매니저라는 것을 잘 생각해 보자 그럼 이제 Provider 가 인증을 진행하고 인증을 실패 혹은 성공을 가르는데
실패를 한다면 AuthenticationException이라는 예외처리가 잡히고 다시 Filter로 돌아가서 해당 예외처리를 한다
성공을 한다면 아까 만든 Authentication 객체에 성공했다는 인증을 담아서 Manager에게 보낸다
※ 이때 주는 Authentication 객체가 바로 인증된 Authentication 객체가 되시겠다 - SecurityContext
그럼 이제 인증이 완료가 된 Authentication 객체는 SecurityContext에 담겨 Holder에 저장된다. 이것은 이제 Session에 저장되어서 우리가 만드는 프로젝트 전역에서 인증확인 용으로 사용할 수 있게 된다. - SuccessHandler
로그인이 완료되었다는 것은 서버도 중요하지만 로그인을 진행한 사용자가 아는 것이 가장 중요하다. 즉, SuccessHandler에서 성공결과를 반환시킨다.
사실 순서가 어렵진 않다. 하지만 중요한 건 순서가 아니라 진행하는 그림이 그려지는 게 중요하다. 나는 여기서 Authentication 객체의 상태가 가장 헷갈렸다. 같은 객체인데 어떤 건 인증이 되었고 안되었고의 차이를 구분하기 쉽지 않았다. 해당 내용의 자세한 점은 Security 카테고리에서 더욱 자세히 다루겠다
로그인 커스텀 해보기
로그인이 어떤 방식으로 진행하는지는 대충 알아보았다. 그럼 시작해 보자
1. UserDetailService
서버의 입장에서 시작해보자 원초적으로 우리는 우리 서버 DB에 있는 사용자와 "비교"해서 인증을 확인해 주는 것이 중요하다 그럼 찾는 유저의 정보를 담아주는 곳이 필요한데 Security는 User라는 객체를 만들어 주어서 비교를 할 수 있게 만들어준다. 그럼 우린 DB에서 값을 가져와서 User 객체에 담아주면 되는 것이다.

우리가 받아오는 사용자의 이름을 통해 DB에서 정보를 가져와서 혹시 없다면 예외를 발생하고 있다면 User 객체에 Builder를 통해 이름과 패스워드 그리고 권한까지 모두 설정하여 보낸다 그리고 이것은 Provider 로서의 역할을 하게 될 것이다.
2. CustomUsernamePasswordAuthenticationFilter
이제 로그인을 직접적으로 맡닿아있는 필터를 작성해 보자

우선 우리는 AntPathRequestMatcher를 통해 login 이 있는 URL을 인지하고 필터가 행동하게 만들어야 한다.
그래서 CustomUsernamePasswordAuthenticationFilter를 생성자를 만들고 부모 클래스의 AbstractAuthenticationProcessingFilter()의 생성자 파라미터를 주어서 상수로 선언한 /login을 post로 받는 것을 만들어주었다. 이렇게 해주면 login으로 들어오는 요청은 이 필터가 잡게 되는 것이다.
그리고 override 한 메서드 attemptAuthentication에서는 application/json 인지 확인하고 JSON 형태로 들어온 아이디와 비밀번호를 map 형태로 전환하고 그 값을 아이디와 패스워드를 때어서 UsernamePasswordAuthenticationToken에 넣어주었다. 이 토큰은 인증된 토큰이라는 증명이기도 하다. 기존의 UsernamePasswordAuthenticationFilter에서도 사용 중으로 그대로 토큰을 사용하는 것으로 했고 이제 이 인증 토큰에 유저의 principal과 credentials를 설정해 주었다. 그럼 이제 아까 말한 Authentication 객체에 다시 들어가게 되는데 이때 들어가는 것은 UsernamePasswordAuthenticationToken이라는 인증된 토큰인 상태로 들어가게 되는 것이다.
3. LoginSuccessHandler
로그인이 인증된 코드를 들고 Authentication이 Context에 저장되었다. 그럼 이제 위에서 말했던 Successhandler 가 실행되어야 한다.

여기서 Service에서 만든 토큰을 전달하게 되는데 accessToken과 RefreshToken을 만들어서 전달한다. 이때 extractUsername을 통해 인증된 Authentication 객체에서 UserDetails 객체로 옮겨 담아 거기에 있는 내용을 빼낸다. 그래서 그곳의 아이디를 가져와서 아이디를 accessToken에 넣어둔다. 그리고 마지막으로 회원이 탈퇴한 회원인지 확인하는 조건문을 넣게 되면서 탈퇴한 회원도 다뤄보고자 했다.
4.FailureHandler
성공을 다뤘다면 이번엔 실패를 다뤄보도록 하자 인증을 실패했다는 전제이다.

이렇게 만약 로그인에 실패를 했다면 SimpleUrlAuthenticationFailureHandler를 상속받아 실패 메서드를 다뤘고 응답을 꾸며서 보냈다. 실패의 메소드 자체는 매우 간단했다. 그냥 서버에서 실패한 내용을 클라이언트에게 어떻게 전달할 것인가가 중요한 내용이었다.
5. 합체
지금까지 클래스의 단위로 잘 만들어둔 것 같다 그럼 이걸 어떻게 합쳐서 시큐리티에 적용할 수 있을까?

이런 식으로 진행하게 된다.
여기서 로그인에 대한 얘기를 하게 되는데 우리는 filter에 Manager가 있고 성공과 실패 handler 가 있다. 그리고 우리가 적용할 필터를 객체로 불러주어서 Bean으로 등록해주어야 한다.

매니저는 이런 식으로 등록하게 된다. Manager를 Bean으로 등록을 하게 되고 매니저 안에 Provider 설정을 이렇게 해줄 수도 있다. 시큐리티에서 사용하는 기본적으로 로그인을 담당하는 provider인 Dao 프로바이더를 불러와서 set을 통해 사용하는 passwordEncoder를 정해주고, UserDetailsService를 우리가 만든 Custom으로 설정해 주었다.
이렇게 Manager를 완성시키고

이렇게 성공과 실패를 다루는 Handler 또한 등록을 진행해 주었다.
그럼 이렇게 조각을 Bean을 통해 등록하고 이걸 또다시 합쳐져야 하는 Filter

CustomUsernamePasswordAuthenticationFilter를 통해 각각의 조각들을 합쳐주는 것으로 Filter 가 등록이 되면서 우리의 필터가 어디서 사용될 것인지를 정해주면 되는 것이다.

이런 식으로 우리는 기존의 UsernamePasswordAuthenticationFilter 가 존재했던 LogoutFilter 뒤에 둘 것이다.
이렇게 우리의 필터는 등록이 되고 진행이 되는 것이다. 그러면 이제 로그인을 시도하면 AccessToken을 주고, RefreshToken을 반환할 것이다..
포스트맨을 통한 테스트

이제 login을 URL에 담아서 이전에 회원가입한 회원으로 로그인을 시도해 보자

성공적으로 바디에 accessToken이라는 이름의 토큰과 refreshToken이라는 이름을 가진 토큰이 전달되는 것을 볼 수 있다. 그럼 이제 이것을 프런트에서 저장하면 되는 것이다.

그리고 이번 로그인을 통해 통과한 필터들이다. 이중에 우리가 집중해서 봐야 할 것은 LogoutFilter뒤에 있는 CustomUsernamePasswordAuthenticationFilter 가 정상적으로 잘 등록되었다는 것이다
'프로젝트 > 팀 프로젝트' 카테고리의 다른 글
밥풀 프로젝트 시연 영상 (0) | 2023.08.01 |
---|---|
babpool 8 . JWT Filter 만들기 (0) | 2023.07.13 |
babpool 5. SecurityFilterChain 설정 (0) | 2023.07.11 |
babpool 6 . JWT Filter 만들기 전에 부품만들기 (0) | 2023.07.11 |
BabPool 4 . 회원가입 (0) | 2023.07.11 |
이전에 작성되었던 내용은 토큰을 전달할 때 그리고 검증할 때 사용하기 위한 부품을 만들었다 그럼 이제 그것을 사용해 보자 물론 나는 로그인에 사용할 예정이다.
시큐리티의 로그인
시큐리티에서 기본적으로 제공하는 로그인 과정은 굉장히 복잡하다. 시큐리티는 굉장히 많은 필터를 거쳐서 사용자를 인증하고 인가하면서 진행을 하는데 그중 로그인과 가장 큰 관련이 있는 UsernamePasswordAuthenticationFilter에 대해서 조금만 알아보자

- AntPathRequestMatcher
요청은 항상 URL을 보고 판단하게 되는데 이때 login 으로 시작하는 URL을 이 녀석이 잡는다고 생각하면 된다.
어쨌든 해당 URL 을 필터가 잡아야 진행이 시작된다. 만약 login 이 들어가지 않는다면 물론 다음 필터로 넘기면서 이 필터는 진행되지 않는다. - Authentication
본격적으로 인증이 시작을 해보자 우리는 로그인을 하면 어떠한 방식으로 아이디와 비밀번호를 전송한다. 그럼 그것을 이 필터가 Authentication이라는 객체에 넣는 것이다. 이제 이 객체를 이용해서 인증을 시작한다.
※ 명심해야 할것은 아직 인증이 된 것이 아니다. 그저 Authentication이라는 객체에 넣어놨을 뿐이다. - AuthenticationManager
Authentication 객체는 인증을 진행하는 Manager 에게로 가는데 Manager는 실질적으로 인증을 하지 않는다. Manager는 인증을 하는 Provider를 소개해 주는 역할이다. 이름이 매니저라는 것을 잘 생각해 보자 그럼 이제 Provider 가 인증을 진행하고 인증을 실패 혹은 성공을 가르는데
실패를 한다면 AuthenticationException이라는 예외처리가 잡히고 다시 Filter로 돌아가서 해당 예외처리를 한다
성공을 한다면 아까 만든 Authentication 객체에 성공했다는 인증을 담아서 Manager에게 보낸다
※ 이때 주는 Authentication 객체가 바로 인증된 Authentication 객체가 되시겠다 - SecurityContext
그럼 이제 인증이 완료가 된 Authentication 객체는 SecurityContext에 담겨 Holder에 저장된다. 이것은 이제 Session에 저장되어서 우리가 만드는 프로젝트 전역에서 인증확인 용으로 사용할 수 있게 된다. - SuccessHandler
로그인이 완료되었다는 것은 서버도 중요하지만 로그인을 진행한 사용자가 아는 것이 가장 중요하다. 즉, SuccessHandler에서 성공결과를 반환시킨다.
사실 순서가 어렵진 않다. 하지만 중요한 건 순서가 아니라 진행하는 그림이 그려지는 게 중요하다. 나는 여기서 Authentication 객체의 상태가 가장 헷갈렸다. 같은 객체인데 어떤 건 인증이 되었고 안되었고의 차이를 구분하기 쉽지 않았다. 해당 내용의 자세한 점은 Security 카테고리에서 더욱 자세히 다루겠다
로그인 커스텀 해보기
로그인이 어떤 방식으로 진행하는지는 대충 알아보았다. 그럼 시작해 보자
1. UserDetailService
서버의 입장에서 시작해보자 원초적으로 우리는 우리 서버 DB에 있는 사용자와 "비교"해서 인증을 확인해 주는 것이 중요하다 그럼 찾는 유저의 정보를 담아주는 곳이 필요한데 Security는 User라는 객체를 만들어 주어서 비교를 할 수 있게 만들어준다. 그럼 우린 DB에서 값을 가져와서 User 객체에 담아주면 되는 것이다.

우리가 받아오는 사용자의 이름을 통해 DB에서 정보를 가져와서 혹시 없다면 예외를 발생하고 있다면 User 객체에 Builder를 통해 이름과 패스워드 그리고 권한까지 모두 설정하여 보낸다 그리고 이것은 Provider 로서의 역할을 하게 될 것이다.
2. CustomUsernamePasswordAuthenticationFilter
이제 로그인을 직접적으로 맡닿아있는 필터를 작성해 보자

우선 우리는 AntPathRequestMatcher를 통해 login 이 있는 URL을 인지하고 필터가 행동하게 만들어야 한다.
그래서 CustomUsernamePasswordAuthenticationFilter를 생성자를 만들고 부모 클래스의 AbstractAuthenticationProcessingFilter()의 생성자 파라미터를 주어서 상수로 선언한 /login을 post로 받는 것을 만들어주었다. 이렇게 해주면 login으로 들어오는 요청은 이 필터가 잡게 되는 것이다.
그리고 override 한 메서드 attemptAuthentication에서는 application/json 인지 확인하고 JSON 형태로 들어온 아이디와 비밀번호를 map 형태로 전환하고 그 값을 아이디와 패스워드를 때어서 UsernamePasswordAuthenticationToken에 넣어주었다. 이 토큰은 인증된 토큰이라는 증명이기도 하다. 기존의 UsernamePasswordAuthenticationFilter에서도 사용 중으로 그대로 토큰을 사용하는 것으로 했고 이제 이 인증 토큰에 유저의 principal과 credentials를 설정해 주었다. 그럼 이제 아까 말한 Authentication 객체에 다시 들어가게 되는데 이때 들어가는 것은 UsernamePasswordAuthenticationToken이라는 인증된 토큰인 상태로 들어가게 되는 것이다.
3. LoginSuccessHandler
로그인이 인증된 코드를 들고 Authentication이 Context에 저장되었다. 그럼 이제 위에서 말했던 Successhandler 가 실행되어야 한다.

여기서 Service에서 만든 토큰을 전달하게 되는데 accessToken과 RefreshToken을 만들어서 전달한다. 이때 extractUsername을 통해 인증된 Authentication 객체에서 UserDetails 객체로 옮겨 담아 거기에 있는 내용을 빼낸다. 그래서 그곳의 아이디를 가져와서 아이디를 accessToken에 넣어둔다. 그리고 마지막으로 회원이 탈퇴한 회원인지 확인하는 조건문을 넣게 되면서 탈퇴한 회원도 다뤄보고자 했다.
4.FailureHandler
성공을 다뤘다면 이번엔 실패를 다뤄보도록 하자 인증을 실패했다는 전제이다.

이렇게 만약 로그인에 실패를 했다면 SimpleUrlAuthenticationFailureHandler를 상속받아 실패 메서드를 다뤘고 응답을 꾸며서 보냈다. 실패의 메소드 자체는 매우 간단했다. 그냥 서버에서 실패한 내용을 클라이언트에게 어떻게 전달할 것인가가 중요한 내용이었다.
5. 합체
지금까지 클래스의 단위로 잘 만들어둔 것 같다 그럼 이걸 어떻게 합쳐서 시큐리티에 적용할 수 있을까?

이런 식으로 진행하게 된다.
여기서 로그인에 대한 얘기를 하게 되는데 우리는 filter에 Manager가 있고 성공과 실패 handler 가 있다. 그리고 우리가 적용할 필터를 객체로 불러주어서 Bean으로 등록해주어야 한다.

매니저는 이런 식으로 등록하게 된다. Manager를 Bean으로 등록을 하게 되고 매니저 안에 Provider 설정을 이렇게 해줄 수도 있다. 시큐리티에서 사용하는 기본적으로 로그인을 담당하는 provider인 Dao 프로바이더를 불러와서 set을 통해 사용하는 passwordEncoder를 정해주고, UserDetailsService를 우리가 만든 Custom으로 설정해 주었다.
이렇게 Manager를 완성시키고

이렇게 성공과 실패를 다루는 Handler 또한 등록을 진행해 주었다.
그럼 이렇게 조각을 Bean을 통해 등록하고 이걸 또다시 합쳐져야 하는 Filter

CustomUsernamePasswordAuthenticationFilter를 통해 각각의 조각들을 합쳐주는 것으로 Filter 가 등록이 되면서 우리의 필터가 어디서 사용될 것인지를 정해주면 되는 것이다.

이런 식으로 우리는 기존의 UsernamePasswordAuthenticationFilter 가 존재했던 LogoutFilter 뒤에 둘 것이다.
이렇게 우리의 필터는 등록이 되고 진행이 되는 것이다. 그러면 이제 로그인을 시도하면 AccessToken을 주고, RefreshToken을 반환할 것이다..
포스트맨을 통한 테스트

이제 login을 URL에 담아서 이전에 회원가입한 회원으로 로그인을 시도해 보자

성공적으로 바디에 accessToken이라는 이름의 토큰과 refreshToken이라는 이름을 가진 토큰이 전달되는 것을 볼 수 있다. 그럼 이제 이것을 프런트에서 저장하면 되는 것이다.

그리고 이번 로그인을 통해 통과한 필터들이다. 이중에 우리가 집중해서 봐야 할 것은 LogoutFilter뒤에 있는 CustomUsernamePasswordAuthenticationFilter 가 정상적으로 잘 등록되었다는 것이다
'프로젝트 > 팀 프로젝트' 카테고리의 다른 글
밥풀 프로젝트 시연 영상 (0) | 2023.08.01 |
---|---|
babpool 8 . JWT Filter 만들기 (0) | 2023.07.13 |
babpool 5. SecurityFilterChain 설정 (0) | 2023.07.11 |
babpool 6 . JWT Filter 만들기 전에 부품만들기 (0) | 2023.07.11 |
BabPool 4 . 회원가입 (0) | 2023.07.11 |