[COGO]OAuth2와 Spring Security의 필터들, JWT, SecurityContext

hwee·2024년 4월 11일
0

COGO개발과정

목록 보기
3/12
post-thumbnail

제가 현재 진행하고 있는 COGO 프로젝트를 기준으로 작성하였습니다.

세줄 요약

  1. 소셜 서버에게 Code와 Access token(리소스 서버 접근용)을 WAS가 받고, 처리한다.
  2. 리소스 서버로 받은 정보로 생성한 Authentication 객체는 Security Context에 저장한다.
  3. 해당 Authentication 객체는 스레드 내에서 언제나 접근 가능하며, username과 role을 꺼내 쓸 수 있다.

기본 용어

인증 Authentication : 해당 사용자가 본인이 맞는지를 확인하는 절차
인가 Authorization : 인증된 사용자가 요청한 자원에 접근 가능한지를 결정하는 절차
접근 주체 Principal : 보호받는 Resource에 접근하는 대상
비밀번호 Credential : Resource에 접근하는 대상의 비밀번호
권한(Role+Authority) : 기능 단위의 권한(ex. Read, Delete...)인 Authority들을 각각의 Role마다 다르게 들고 있다. (ex. ROLE_ADMIN은 모든 authority를, ROLE_USER은 read authority만을 가진다.)

JWT발급까지의 로직(OAuth2)


클라이언트로부터 로그인 요청이 들어오면 아래의 9단계를 거쳐 JWT를 클라이언트에 반환하게 됩니다.
1~3 : OAuthAuthorizationRequestRedirectFilter 역할
4~7 : OAuth2LoginAuthenticationFilter 역할
8 : LoginSuccessHandler 역할

단계별 역할

  1. 소셜 로그인 요청이 들어오면 인증 요청(state)을 생성하여 세션에 저장한다.
  2. 인증 요청 URI로 인증 요청을 redirect하여 사용자에게 로그인 페이지로 안내한다.
  3. 로그인에 성공하면 발급되는 코드를 YML에 작성해두었던 redirect-uri로 반환하게끔 하여 OAuth2LoginAuthenticationFilter가 받는다.
  4. 인증 서버로부터 받은 URL에 포함된 state를 전에 생성한 인증 요청의 state와 일치하는지 검증하여 CSRF공격을 방지하고, 일치한다면 URL에 포함된 Code로 인증서버에 액세스 토큰(OAuth2AuthenticationToken)을 요청한다.
  5. 인증서버로부터 발급받은 액세스 토큰과 소셜 인증 제공자의 리소스 서버와 관련된 정보를 합쳐 OAuth2UserRequest 객체를 생성한다.
  6. OAuth2LoginAuthenticationProvider에서 OAuth2UserRequest 객체를 override한 OAuth2Service의 loadUser의 파라미터에 넣어 호출하면5번에서 생성한 OAuth2UserRequest객체를 파라미터로 넣어 호출하면 유저의 정보들을 OAuth2User 객체 형태로 받아오고, 각 소셜마다 정보를 주는 방식에 따라 해석하여 유저의 정보를 DB에 따로 저장하며 커스텀한 OAuth2User객체에 Role과 같은 추가 정보들을 기입하여 반환한다.
  7. 이렇게 생성된 CustomOAuth2User객체는 한 스레드 내라면 어디서든 접근할 수 있는 Authentication으로써 사용되며, SecurityContext에 저장된다.

    (이렇게 스레드 내에서 어디서나 접근 가능하다)
  8. 이렇게 저장된 Authentication에 LoginSuccessHandler가 접근하여 JWT를 생성하고, 클라이언트에 반환한다.

요청이 들어왔을 때 처리 로직

1. 토큰 분해

JWT(Access Token)와 함께 요청이 들어오면, JWT Filter에서 토큰을 검증하고, 유효하다면 토큰에서 JWT Util을 사용하여 Principal의 정보들(ID or email)과 Authority(ROLE)을 꺼낸다.

2. Principle 만들기

토큰에서 꺼낸 정보들을 조합하여 Principle을 만든다.
(소셜 로그인시 OAuth2User, 일반 로그인시 UserDetails)

3. Authentication 객체 만들기

생성한 Principle과 Credentials, Authorities를 조합하여 Authentication 객체를 생성한다. 이 때, Credentials는 사용자의 비밀번호같이 민감한 정보이기 때문에 null로 대입한다.
Authentication을 구현하기 위하여 UsernamePasswordAuthenticationToken를 사용하는데, 이 토큰은 Spring Security의 인증 매니저 AuthenticationManager에 의해 처리된다.

Authentication authToken = new UsernamePasswordAuthenticationToken(customOAuth2User, null,
			customOAuth2User.getAuthorities());

4. SecurityContextHolder의 SecurityContext에 Authentication을 저장한다.

5. 전체 로직 구조도

SecurityContextHolder?

위의 과정을 거쳐 사용자가 인증되면 해당 결과를 토큰(UsernamePasswordAuthenticationToken)에 담게 되는데, 이것이 Authentication이다.
이 Authentication은 SecurityContextHolder에 의하여 전역에서 사용할 수 있다.

SecurityContext

SecurityContext는 단일 Authentication을 저장한다.
SecurityContextHolder는 단일 SecurityContext를 관리하는데, 애플리케이션 내에서 SecurityContext의 동작 방식을 3가지 모드로 관리한다.
1. MODE_THREADLOCAL : Default값이며, 각 스레드가 자체적인 SecurityContext를 가진다.
2. MODE_INHERITABLETHREADLOCAL : 자식 스레드가 부모 스레드의 SecurityContext를 상속받을 수 있다. (비동기 처리에 유리)
3. MODE_GLOBAL : 애플리케이션에서 단일 전역 SecurityContext 사용(비추천)
즉, 모드마다 ThreadLocal(스레드마다 가지는 저장공간)의 사용 방식이 다른데, 1번은 SecurityContext가 ThreadLocal에 저장되고, 2번은 자식스레드들이 부모스레드의 ThreadLocal에 있는 SecurityContext를 자식스레드가 복사한다. 3번의 경우 모든 스레드가 단일 SecurityContext를 사용하므로 ThreadLocal을 사용하지 않는다.
즉, SecurityContextHolder에서 모드를 1번으로 잡는다면, 각 요청마다 각각의 스레드를 가지기 때문에, 각자의 ThreadLocal에 SecurityContext가 저장되어 다른 스레드가 침범하지 못하고, 안에 저장되어 있는 Authentication을 스레드 내부의 전역에서 사용할 수 있게 된다고 이해하면 될 것 같다.

전체 구조도

Reference

https://velog.io/@dailylifecoding/spring-security-authentication-registry
https://wildeveloperetrain.tistory.com/163
https://minholee93.tistory.com/entry/Spring-Security-Authorities-Role

profile
https://fuzzy-hose-356.notion.site/1ee34212ee2d42bdbb3c4a258a672612

0개의 댓글