Spring Boot OAuth JWT (1)

Spring Boot에서 OAuth 2.0 JWT(JSON Web Token)을 적용해보려 합니다.

개발환경

AdoptOpenJDK 11

Spring Boot 2.5.5

Spring Security

OAuth 2.0

JWT

IntelliJ

Travis의 토큰 관리 방식 참조 응용

  • 로그인 시 사용자테이블에 login_yn을 ‘Y’로 업데이트하고 token을 client에게 전송함.
  • 로그아웃 시 사용자 테이블에 login_yn을 ‘N’으로 업데이트 함.
  • client에서 token을 헤더에 포함하여 요청하면 해당 token의 login_yn이 ‘Y’인지 검사하고 ‘N’이면 expired token error을 내보냄.
  • token이 도난당해도 사용자가 로그아웃한 상태면 사용할 수 없지만 사용자가 로그인한 시점에 시도하면 사용할 수 있음.
  • clinet 요청마다 login_yn을 검사해야함(아마 속도 때문에 redis로 구성하지 않았을까 생각함. key=token, value=login_yn 이런씩으로 왜냐면 token값이 로그인/로그아웃해도 몇시간이 지나도 바뀌지 않았기 때문)
  • 이 방식을 그대로 사용하지 않고 응용해봄
  • redis에 refresh token 상태값을 저장하며 로그인 시 key=token의 사용자 uid, value=refresh token를 넣고, 로그아웃 시 key=token의 사용자 uid를 삭제.
  • (1) 사용자가 로그인을 하면 access token의 만료시간을 30분으로 생성, refresh token은 30일로 생성하고 key=token의 사용자 uid, value=refresh token로 저장하고 client에게 access token과 refresh token을 보내줌. (이때 선택적으로 oauth 로그인 정보(nicname 등)은 회원 테이블에 업데이트 할 수 있음)
  • (2) 만약 client가 서버에 요청할 때 access token이 만료되었다면 client에게 만료 에러를 보내고 client가 refresh token을 서버에 전송함.
  • (3) 서버는 refresh token을 redis에 조회해서 데이터가 존재하고 refresh token이 동일한지 검사하여 유효하다면 access token을 새로 발급하여 전송함. 데이터가 없거나 유효하지 않는 토큰이면 client에게 refresh token expried error 를 보내 다시 로그인 하도록 유도하여 (1) 상태로 보냄

Spring Boot에서 OAuth2.0를 좀 더 쉽게 사용하기 위해 제공되는 Spring OAuth2 Client 라이브러리를 사용하고 지원할 로그인 플랫폼은 구글, 페이스북, 네이버, 카카오 입니다.

accessToken: 매번 인가를 받을 때 사용하는 토큰으로 보통 수명이 짧습니다.

refreshToken: accessToken의 수명이 다했을 때 accessToken을 재발행 받기 위한 토큰으로 보통 수명이 깁니다. (Authorization Code Grant, Client Credentials Grant Type으로 최초에 인가를 받았을 경우에만 발급됨.)

1. 로그인을 하면 Access Token과 Refresh Token을 모두 발급한다.

이때, Refresh Token만 서버측의 DB에 저장하며 Refresh Token과 Access Token을 쿠키에 저장한다.

2. 사용자가 인증이 필요한 API에 접근하고자 하면, 가장 먼저 토큰을 검사하는 미들웨어를 검사한다.

이때, 토큰을 검사함과 동시에 각 경우에 대해서 토큰의 유효기간을 확인하여 재발급 여부를 결정한다.

  • case1: access token과 refresh token 모두가 만료된 경우 -> 에러 발생
  • case2: access token은 만료됐지만, refresh token은 유효한 경우 -> access token 재발급
  • case3: access token은 유효하지만, refresh token은 만료된 경우 -> refresh token 재발급
  • case4: accesss token과 refresh token 모두가 유효한 경우 -> 다음 미들웨어로

3. 로그아웃을 하면 Access Token과 Refresh Token을 모두 만료시킨다.

Google OAuth Client 생성하기

(Spring boot OAuth2 Client는 Google과 Facebook에 대한 인증서버 정보만 가지고 있기 때문에 카카오와 네이버의 인증서버 정보를 추가해주어야 합니다.)

spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: <your id>
            client-secret: your secret>
            scope:
              - profile
              - email

@PostConstruct : 빈 초기화 후 한번만 실행하는 메서드를 지정하는 애노테이션으로 빈 초기화 시에 의존성 주입된 객체들을 사용할 수 있습니다 . ( 참고 페이지)

http://localhost:8080/oauth2/authorization/google?redirect_uri=http://localhost:8080/test

http://localhost:8080/api/v1/member/getMember/123

프론트 페이지에서 사용자 구글 로그인 클릭하면

<a href="http://localhost:8080/oauth2/authorization/google">구글 로그인</a>

다음과 같은 주소로 리다이렉션 됩니다.

https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=26xxxxxxxxxxxxxxxxxxxxxxxxxx6uvi.apps.googleusercontent.com&scope=profile%20email&state=BSq_Rp2SuOYVCE4UmIFmgbTyE2zQt8HqzXev5dMssUA%3D&redirect_uri=http://localhost:8080/login/oauth2/code/google

image-20211027141234005

image-20211027141056211

response_type: Authorization Server의 응답요청을 지정하며 Authorization Code와 Implicit 방식에서 사용됩니다.

code 또는 token 구분값을 사용합니다.

token : Implicit 방식을 말하며 Client가 Authorization Server에게 최초의 요청만으로 토큰을 발행합니다. 주로 프론트 페이지(Javascript)에서 백엔드 서버를 통하지 않고 직접 API를 액세스해야 할 때 사용됩니다. 토큰 리턴 방식이 쿼리스트링으로 전달되기 때문에 보안이 취약할 수 있어 신뢰도가 높은 애플리케이션(모바일앱, 단말기 등)에서 사용됩니다.

code : Authorization Code 방식을 말하며 1번의 요청만으로 토큰이 발행되지는 않고 code라는 정보를 바탕으로 토큰에 대한 발행을 실요청합니다. 주로 백엔드 서버를 통해 액세스되는 경우입니다.

scope(optional) : OAuth2 Provider(구글, 페이스북, 네이버, 카카오 등)에서 제공하는 Resource(메일, 프로필, 기타 정보 등)에서 어떤 항목들을 Client에서 가져갈 수 있는지 리소스의 범위를 정의하는 것입니다. 즉 Client별로 접근이 가능한 리소스를 구별해놓는 정보입니다.

state(optional): 사이트 간 요청 위조(csrf 공격)를 방지하기 위해 매개 변수를 사용합니다. Client가 랜덤한 값을 Authorization Server로 전달하면 Authorization Server는 토큰을 발행할 때 Client로 받은 state값을 함께 Response합니다.

redirect_uri : client가 Authorization Server로 전달하는 값으로 Authorization Server가 토큰 발행 후 정보가 리다이렉트되는 url이며, 토큰 발행 시에 검증값으로도 사용됩니다.

Grant Type: 허가를 받는 유형

리다이렉션 된 페이지에는 계정을 선택하는 화면이 나옵니다.

계정에 로그인을 하면 SpringSecurity에 설정된 OAuth Service로 응답이 옵니다.


registrationId: google
resourceServerUri: https://www.googleapis.com/oauth2/v3/userinfo
accessToken: ya29.a0ARrdaM9cdBoyisrxiYb-8trWaf6-j24IhINly1X8gJ3sQvCpr-qA7tQi_oz-9jxu3-dJmhyRX-sA8a2H614rm5h1bKw1vcXGBVXjqoHYwNkyrIwZqDF-7f7NlODJXYZx9A8uMBp_IuwLEdAvqW2dZnOJWfAW
userNameAttributeName: sub
    
attributes={
    sub=109020890355219671248, 
    name=아직한발 남았다, 
    given_name=한발 남았다, 
    family_name=아직, 
    picture=https://lh3.googleusercontent.com/a-/AOh14Ghgj4AEhjQ-PSkvb1VJHgDXXDayqceqwH1jDIWE=s96-c,   
    email=donghyeok.jh@gmail.com, 
    email_verified=true, 
    locale=ko
}

redirect_uri에 쿼리스트링을 추가해 백엔드에서 프론트엔드로 토큰을 전달해준다.

참고사이트

https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1

https://deeplify.dev/back-end/spring/oauth2-social-login

https://godekdls.github.io/Spring%20Security/oauth2/

https://zkdlu.tistory.com/12

https://github.com/boyd-dev/SimpleSpringBoot

https://ozofweird.tistory.com/entry/Spring-Boot-Spring-Boot-JWT-OAuth2-2

JWT 쿠키 저장 방식에 관해

https://dev.to/cotter/localstorage-vs-cookies-all-you-need-to-know-about-storing-jwt-tokens-securely-in-the-front-end-15id

https://ko.wikipedia.org/wiki/JSON_%EC%9B%B9%ED%86%A0%ED%81%B0#%ED%91%9C%EC%A4%80%ED%95%84%EB%93%9C

https://pronist.tistory.com/143

https://velog.io/@mygomi/TIL-50-JWT%EC%97%90-%EB%8C%80%ED%95%B4-%EB%B0%9C%ED%91%9C%ED%95%B4%EB%B3%B4%EA%B2%A0%EC%8A%B5%EB%8B%88%EB%8B%A4

https://velog.io/@guswns3371/%EC%BA%A1%EB%94%94

댓글남기기