soft IT life
spring security+JWT로 멀티테넌시 로그인 플로우 구조잡기 본문
JWT란 한마디로, JWT기반 자격증명방식.
인증에 필요한 정보들을 암호화(인코딩)하여 JSON 토큰으로 해당 토큰을 HTTP header에 실어서 서버가 클라이언트를 식별하는 방식이다.
클라이언트는 처음 인증을 받을 때 Access Token, Refresh Token 두 가지 모두 받게 된다.
(두 가지는 같은 JWT 토큰이지만 저장되는 위치와, 관리의 차이가 있다.)

Access Token : 접근, 실질적인 사용자의 자격 증명 정보가 담긴 토큰이다. 보호된 정보들에 접근할 수있는 권한부여에 사용한다. 클라이언트에서 요청을 보낼 시 헤더에 해당 토큰을 함께 보내기 때문에, 서버에서는 토큰의 정보를 활용하여 사용자를 검증하거나 응답을 진행한다.
Refresh Token : 재발급, 새로운 Access Token을 발급해주기 위해 사용하는 토큰으로 보통 DB에 유저 정보와 함께 기록된다.
왜 두가지를 사용하느냐? Access에는 자격 증명 정보가 들어가 있으므로 보안상 이유로 유효기간을 짧게 설정한다. -> 유효기간이 만료되면 다시 서버에 인증을 거쳐서 Access Token을 받아내야할까?

실제 코드 구현
SecurityConfig
- Bean 등록, filterChain 정의(요청에 대한 처리방법, 보안 설정, session 저장유무 설정 등)
.authorizeHttpRequests {
it.requestMatchers("/auth/user/basicinfo", "/auth/logout", "/getUserOrgList", "/getUserOrgRoleList")
.authenticated()
.anyRequest().permitAll()
}
.addFilterBefore(
JwtAuthenticationFilter(jwtTokenProvider = jwtTokenProvider),
UsernamePasswordAuthenticationFilter::class.java
)
.exceptionHandling {
it.authenticationEntryPoint(JwtAuthenticationEntryPoint())
it.accessDeniedHandler(JwtAccessDeniedHandler())
}
- .authorizeHttpRequests : URL별 권한 설정. 등록해놓은 url은 인증된 사용자에게만 허용. 다른 모든 요청은 모든 사용자에게 허용.
- .addFilterBefor : JWT 인증을 수행하는 필터 추가. 이 필터는 사용자의 JWT 토큰을 확인하여 인증을 수행.
- .exceptionHandling : 예외 처리
JwtAuthenticationFilter
- custom Filter 정의
override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) {
val accessToken = resolveAccessToken(request)
if(accessToken != null && jwtTokenProvider.validateToken(accessToken)) {
if(jwtTokenProvider.checkDBAccessToken(accessToken)) {
val authentication = jwtTokenProvider.getAuthentication(accessToken)
SecurityContextHolder.getContext().authentication = authentication
}
}
filterChain.doFilter(request,response);
}
- fun doFilterInternal : 실제 필터링 로직 구현.
- val accessToken = resolveAccessToken(request : HttpServletRequest) 으로 accessToken 추출.
- fun resolveAccessToken : HTTP요청의 header("Authentication")에서 Access token을 추출한다. (해당 토큰은 Baerer key로 넘겨오기 때문에 접두어에 "Baerer"가 있는지 확인한다.)
- Access Token의 유효성 검사
- access token 유효한지 검사
- access token이 DB에서도 유효한지 검사
- 유효하고 DB에서도 확인이 되었다면, jwtTokenProvider.getAuthentication(accessToken)으로 사용자의 인증 정보를 가져옴, 가져온 사용자의 인증 정보로 Spring Security의 인증 객체로 설정.(사용자는 인증되고 권한이 부여된 것! = SecurityContext에 올라갔다(?)) ==> AuthController에서 @AuthenticationPrincipal user : CustomSecurityUser 어노테이션을 사용하여 인증 및 권한 부여된 user 객체 사용 가능)
- 마지막으로, filterChain을 호출하여 다음 필터 또는 요청핸들러로 요청을 계속 전달한다.
jwtTokenProvider
- JWT 토큰을 생성, 인증, 검증 등을 관리하는 곳
- 실제로 사용자의 인증 및 권한 확인을 위한 소스코드로 구성된 클래스.
- Token 생성 : userKey를 가지고 AccessToken, RefreshToken을 만들어서 DB에 저장하고 return 해준다.
- Token 유효성 검증
- Token DB 데이터 존재 검증
- 주어진 Token을 가지고 사용자의 Spring Security 인증 정보를 가져온다.
- token에서 사용자의 userKey를 추출하고, DB에서 해당 사용자의 정보를 가져와 UserDetails 객체로 변환한다.
- Refresh token을 이용하여 새로운 Access Token을 생성. (Refresh Token이 유효하지 않으면 exception throw)