soft IT life

spring security+JWT로 멀티테넌시 로그인 플로우 구조잡기 본문

카테고리 없음

spring security+JWT로 멀티테넌시 로그인 플로우 구조잡기

softPattern 2023. 10. 19. 20:27

 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)