인프런 커뮤니티 질문&답변

system.out.println님의 프로필 이미지

작성한 질문수

스프링 시큐리티 OAuth2

연동 구현 (1)

소셜 로그인 관련 질문드립니다!!

작성

·

727

0

안녕하세요 강사님, 강의 정말 잘 듣고 혼자 힘으로 최대한 자료 안보면서 머리 싸매면서 구현해보고 있습니다. 강의 후반부 까지 듣다가 잠시 중단하고 긴가민가 한 부분만 찾아서 다시 듣고 있는 상황입니다.

 

Screenshot 2023-08-11 at 2.34.18 AM.png

사진은 ChatGPT 로그인 화면이고 구현하고자 하는 목표입니다.

다만, Session 을 사용하지 않으려고 합니다. 거기에 많은 분들이 질문하신 소셜 로그인 이후에 토큰을 가지고 서버에 인가를 요청하는 과정을 구현하고자 합니다.

그리고 아래는 제가 이렇게 구현하면 되지 않을까? 생각한 내용이고, 한번 시간내서 봐주시면 정말 감사하겠습니다.

 

(질문시작)

AuthorizationRequestRepository<OAuth2AuthorizationRequest>

강의에서도 나오지만 OAuth 2.0 가 default 로 Authorization RequestSession 에 저장합니다. 이를 그대로 사용하지 않고 Cookie 에 저장하기 위해서 다른 블로그를 참고했습니다.

@Override
public OAuth2AuthorizationRequest loadAuthorizationRequest(HttpServletRequest request) {
    return CookieUtil.getCookie(request, OAUTH2_AUTHORIZATION_COOKIE_NAME)
            .map(cookie -> CookieUtil.deserialize(cookie, OAuth2AuthorizationRequest.class))
            .orElse(null);
}

@Override
public void saveAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest, HttpServletRequest request, HttpServletResponse response) {
    if(authorizationRequest == null) {
        CookieUtil.deleteCookie(request, response, OAUTH2_AUTHORIZATION_COOKIE_NAME);
        CookieUtil.deleteCookie(request, response, REDIRECT_URI_PARAM_COOKIE_NAME);
        return;
    }

    CookieUtil.addCookie(response, OAUTH2_AUTHORIZATION_COOKIE_NAME, CookieUtil.serialize(authorizationRequest), cookieExpireSeconds);
    String redirectUrlAfterLogin = request.getParameter(REDIRECT_URI_PARAM_COOKIE_NAME);
    if (StringUtils.isNotBlank(redirectUrlAfterLogin)) {
        CookieUtil.addCookie(response, REDIRECT_URI_PARAM_COOKIE_NAME, redirectUrlAfterLogin, cookieExpireSeconds);
    }
}

@Override
public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest request, HttpServletResponse response) {
    return this.loadAuthorizationRequest(request);
}

public void removeAuthorizationRequestCookies(HttpServletRequest request, HttpServletResponse response) {
    CookieUtil.deleteCookie(request, response, OAUTH2_AUTHORIZATION_COOKIE_NAME);
    CookieUtil.deleteCookie(request, response, REDIRECT_URI_PARAM_COOKIE_NAME);
}

이후 OAuth 2.0 로그인에 성공 이벤트를 처리할 successfulHandler 를 생성합니다.

@Component
public class OAuth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
   // Token 유효성 검사
   // Token 생성 
   // redirect_uri 로 사용자 redirect
}

여기까지 과정을 간단요약해 보겠습니다.

  1. 유저가 소셜 로그인 버튼(google, github, kakao) 을 누른다.

  2. 스프링부트 서버에서 /oauth2/authorization/${provider}?redirect_uri=http://localhost:3000/oauth/redirect 로 인가서버에게 요쳥을 보낸다.

  3. 인가서버마다 설정된 authorization_code 의 엔드포인트로 redirect 된다.

  4. 획득한 authorization_code인가서버가 다시 스프링부트 서버로 전송한다.

  5. 스프링부트 서버에서 다시 authorization_code 를 사용해서 access_token 을 요청한다.

  6. 인가서버access_token스프링부트 서버로 전송한다.

  7. 스프링부트 서버access_token 을 이용해서 리소스 서버user_info 를 요청한다. 소셜 로그인인 경우, 인가서버리소스 서버가 동일하다.

  8. 스프링부트 서버user_info 를 획득한다. 해당 정보를 DB 에 저장한다.

  9. 그리고 스프링부트 서버에서 access_tokenrefresh_token 을 생성한다.

  10. refresh_token 은 수정이 불가능한 cookie 에 저장하고, access_token 은 프론트엔드로 redirect_uriquery-string 에 담아서 보낸다. 해당 access_tokenlocalStorage 에 저장한다.

  11. 혹은 refresh_token스프링부트 서버가 가지고 있으며 DB 에 저장하고, access_tokencookie 에 담아서 프론트엔드에 보낸다.

  12. 프론트엔드 서버에서 스프링부트 서버에서 전달받은 토큰을 localStorage 에 저장하고, 다시 스프링부트 서버로 요청을 보낼시 Authorization: Bearer ${access_token} 을 HTTP 헤더에 담아서 보낸다.

  13. 스프링부트 서버에서 JwtDecoder 를 이용해서 access_token 을 검증한다. 만약 expiration date 가 지났다면 access_tokenrefresh_token 을 모두 재발급한다. (혹은 access_token 만 재발급한다)

 

출처는 이곳 입니다.

 

(질문1)

위의 순서가 합당하다면

강의 내용을 바탕으로 제가 추가적으로 구현할 부분은

  1. 스프링부트 서버에서 access_token, refresh_token 을 생성하는 로직

  2. (세션을 비활성화하고) 토큰을 cookie 에 담아서 보내는 로직

  3. 스프링부트 서버에서 access_token 을 검증하고 재발급하는 로직

인게 맞는건지 궁금합니다.

 

(질문2)

외부 인가서버를 사용하지 않고 OAuth 2.0 Resource Server, Client, Authorization Server 를 사용해서 스프링부트 서버가 모든 인증 & 인가의 책임을 가지게 된다면, 결국 google, github, naver 등의 외부 인가서버를 이용하는 것과 크게 차이가 나지 않는건가요?

어짜피 많은 부분을 OAuth 2.0 라이브러리가 지원해준다면, 외부 인가서버를 도입해서 커플링 시키느니 공공기관 납품하는 경우에는 직접 인가서버, 리소스서버를 구현하는 것이 더 실무에서 자주 일어나는 일인지 궁금합니다.

(* 제가 일하던 작은 si 회사에서는 참여했던 프로젝트가 공공기관 프로젝트가 폐쇄망에서만 실행되는 내부 관리자 용이여서 보안 관련된 아주 간단한 설정만을 접해봤습니다.)

(그리고 아직 강의 후반부 통합 연동부분을 다 듣지 못했습니다 ㅜ.ㅜ)

 

(질문3)

Keycloak, Okta 같은 오픈소스 인가서버를 사용하는 경우 토큰 관리, 세션 관리, 유저 관리 등이 개발자가 뭘 해줄것도 없이 인가서버에서 관리를 해줘서 굉장히 편하다는 것을 이해했습니다.

하지만 검색을 좀 해봐도 관리자가 한땀한땀 인가서버에 등록해주는 것이 아니라 일반적인 사이트에서 처럼 form 요청으로 키클록 서버에 자동으로 User 정보가 등록되는 것이 가능한지 모르겠습니다.

보안이 중요한 기관에서 로그인 요청을 보내고, 며칠뒤에 계정이 만들어지는 것이 이런 프로그램을 이용해서인가.. 싶기도 하고 궁금하네요.

 

답변 1

0

정수원님의 프로필 이미지
정수원
지식공유자

질문 1

  • 네 흐름은 정확하게 이해 하신 것 같습니다. 그리고 추가적으로 해야 할 부분도 잘 정리하신 것 같습니다.

    소셜 기관으로 부터 토큰을 발급받고 다시 사용자 정보를 가지고 온 다음 인증 처리까지 했다면 이제 이 인증을 어떻게 유지할 것인가의 문제만 남은 거라 볼 수 있습니다.

    기본적으로는 시큐리티가 세션 방식으로 인증을 유지하도록 진행합니다. 그러나 이건 선택의 문제이고 질문해 주신 것처럼 세션이 아닌 JWT 방식으로 인증을 유지해도 됩니다. 세션이냐 JWT 이냐 는 각 방식의 장단점이 있고 현 시스템의 상황에 맞게 정하시면 됩니다.

 

질문 2

  • 클라이언트, 리소스 서버, 인가서버 모두 시큐리티 모듈을 이용해도 됩니다. 다만 실 서비스에 적용한다는 의미는 많은 테스트와 안전성이 충분히 검증이 되어야 한다는 것을 의미합니다. 시큐리티 자체는 훌륭한 오픈 소스임에는 분명하지만 인가서버 같은 경우에는 1.0 으로 정식 출시한지가 아직 1년이 채 되지 않았고 안정적으로 서비스 하는 곳이 그렇게 많지 않을 확률이 큽니다. 그래서 실 서비스에 적용을 하기 위해서는 성능, 부하 등 다양한 테스트가 충분히 이루어져야 합니다.

 

질문 3

  • Keycloak, Okta 는 나름대로 많은 서비스에서 활용될 만큼 검증된 프로그램입니다. 본 강의에서 아주 깊이 다루고 있지는 않지만 공식 사이트나 문서를 참고해서 개발한다면 크게 문제 될 것은 없어 보입니다.

    모든 것이 그렇겠지만 단순히 사용방법을 익혀서 간단한 기능을 만드는 것은 어려울 것이 없지만 모든 상황을 대비해야 하는 서비스에서 제대로 된 기능이 작동되게 하기 위해서는 그 만큼 프로그램에 대한 깊은 이해와 다양한 케이스별 테스트가 이루어져야 하고 특히 오류나 예외가 발생할 경우 얼마만큼 빠른 대처가 가능한지에 대해서도 충분한 해결방안을 마련해야 합니다.

 

그럼에도 불구하고 시도하지 않는 것 보다 시도하는 것이 더 좋은 선택이라 생각합니다. 실 서비스에서 예상치 못한 어려운 난관에 부딪칠 수 있겠지만 오히려 그런 과정을 거침으로 더 좋은 결과물이 탄생할 수 있다고 생각합니다.