고민했었던 점, 아쉬웠던 점
이번에 Spring Security를 활용한 인증 부분을 구현하면서, 고민했었던 점 위주로 작성하였다.
1. 인증 과정에 대해 어느 정도까지 커스텀을 수행해야 하는가?
이번 팀 프로젝트에서 사용자 인증 방식으로 JWT token이 사용되었다.
토큰을 사용하기 위해 로그인을 수행하고, 성공 시 토큰을 발급하는 등의 인증 과정이 필요한데, 이 과정을 Spring Security에서는 UsernamePasswordAuthenticationFilter 부분에서 수행하고 있었고. 따라서 이를 커스텀한 필터 JwtAuthenticationFilter를 구현하였다.
필터를 구현할 때, 기존에 UsernamePass~%&^ 에서 사용되던 구현체들을 얼마나 커스텀할 지가 고민이었다.
기존 UsernamePasswordAuthenticationFilter 동작 방식

사용자가 username, password를 입력
→ 입력값을 토대로 UsernamePasswordAuthenticationToken 인증용 토큰 생성
→ 생성된 토큰은 AuthenticationManager로 전달된 뒤
→ 토큰 내부의 값을 이용하여 유저에 대한 검증(authenticate) 수행
→ 성공 시, SecurityContextHolder에 인증 결과(보통 유저 정보..)를 저장
커스텀을 고려한 구현체들은 다음과 같다.
- UsernamePasswordAuthenticationToken(인증용 토큰)
- AuthenticationProvider(실제 DB를 이용하여 인증을 생성하는 역할)
- AuthenticationManager(위의 provider를 관리하는 역할 )
- Jwt
- 인증용 토큰
- 로그인 성공 전
- 로그인 성공 후
일단 기존에 존재하는 UsernamePassword~Token은 폼 로그인에 대한(Spring security에서 자동으로 만들어 주는 /login 페이지) 인증을 저장하는 토큰이며, username and password authentication 방식에서 사용되기 때문에, 이번처럼 jwt를 이용하는 상황에서는 맞지 않다고 생각하여 아래와 같이 커스텀하게 되었다.

구현한 Authentication 구조
JwtAuthenticationToken(email, password, null)
JwtAuthenticationToken(JwtAuthentication(token, email), null, authorities)
→ token을 헤더에 담아 응답 전송
처음엔 이 구조에 대해 이해가 잘 안 돼서 시간을 많이 뺏겼다…..
- 3. AuthenticationProvider, AuthenticationManager
기존의 AuthenticationProvider는 위의 필터에서 전달받은 토큰을 이용하여 검증을 수행(로그인)하는 역할을 갖고 있다.
하지만 기존 흐름과는 달리, username, password 값으로 검증을 수행하는 것에 더해서, jwt 를 생성하는 과정이 추가되어야 했다.(로그인 성공 시 유저에게 Authorization 헤더를 통해 string 토큰 값을 넘겨주어야 하니까..)
그래서 검증을 수행하는 역할을 하는 JwtAuthenticationProvider를 구현하는 것에 더하여, jwt를 만들고, 검증하고, 토큰 값으로부터 authentication을 뽑아내는 역할을 하는 JwtTokenProvider를 추가로 구현하였다.
4. Jwt
Spring Security 강의 에서는 jwt 를 직접 구현하고 있다.
이 부분은 커스텀해야 하는 필요성을 딱히 느끼지 못해서 다음과 같은 라이브러리의 구현체를 가져와 사용하였다.
implementation 'io.jsonwebtoken:jjwt:0.9.1'
2. 로그인 동작 수행 시, 로직의 흐름을 어떻게 구성해야 할까?
로그인 과정을 수행하는 컨트롤러 부분은 다음과 같다.

로그인이 성공하면 응답으로 jwt 값을 전송해야 했는데, 그 때문에 authentication으로부터 토큰을 뽑아 오는 로직이 컨트롤러 부분에 추가되었다.
그리고 헥사고날 아키텍처를 적용하여 프로젝트를 진행하고 있었기 때문에, 이 로직이 컨트롤러에 존재하는 게 어색하게 느껴질 수 있겠다는 생각이 들었다.
그래서 생각해 본 다른 구성 방식과, 그 방식을 채택하지 않은 이유는
- 주어진 UserLoginRequest를 이용하여 token를 만들어내는 service를 생성한다.
→ 이런 서비스를 만들어낸다면, 그 안에 들어가는 로직은 line 6 ~ 8 이 부분이 될 것 같은데, 이 부분은 서비스로 뽑아내기에 의미가 있을까? 라는 생각이 들었다. 단지 authenticationProvider로부터 Authentication을 받아오고, 그 안에 있는 token를 꺼내는 작업만을 하고 있기 때문에..
그리고 여기에서 token은 응답 값에 해당하므로, 이 작업은 응답 값을 구성해주는 작업이라고도 볼 수 있기에 controller 부분에 있어도 되지 않을까? 라는 생각이 들었다.
결국은 기존의 방식으로 구현이 되었는데, 아직도 찜찜한 구석이 남아 있다.
authentication provider와 같은 security 부분의 구현체를 컨트롤러 단에서 사용한다는 점 등등..
필터 단에서(JwtAuthenticationFilter) 이 부분을 잡아 처리하는 게 더 낫지 않았을까?? 하는 아쉬움이 들었다.