Spring , JPA

[Spring Cloud Gateway] JWT & Opaque Token

seulseul 2023. 1. 4. 00:31

목차

 

    1. Opaque Token 이란 ?

    • 전달하는 정보 측면에서 불투명한 토큰
    • 토큰은 인증 서버에 저장된 정보를 가리키는 식별자일 뿐이며 서버 측에서 자체 검사를 통해 유효성을 검사함

    2. Opaque Token vs JWT

    Opaque Token 을 사용하는 이유는 JWT (Json Web Token) 은 만료 시간 (expires_in) 이 되기 전에는 탈취 당하더라도 해당 Token 을 무효화 할 수 없기 때문이다.

    실생활 예를 든다면, 고객이 카드를 잃어버린 것은 고객의 잘못이기 때문에 카드를 잃어버림으로서 일어나는 손해는 고객이 일부 책임을 져야 한다.

    그러나 고객이 카드 분실 신고를 했을 경우 카드사는 해당 카드를 사용 정지를 해야 한다.
    이를 구현하려면 JWT 를 DB 로 관리하여 무효화 하거나, Opaque Token 을 이용하여 JWT 자체 만료시간을 무효화 시키는 방법이 있다.

    JWT 를 DB 로 관리하는 것은 결국 Stateless 라는 장점을 버리게 되므로 , JWT 를 사용할 필요가 없어진다.
    대신 Opaque Token 을 이용하여 강제 무효화 할 수 있다.
    Opaque Token 은 불특정한 토큰이기 때문에 어떤 형태의 Token 이든 활용 가능하다.

    3. Spring Cloud Gateway 에서 Opaque Token 구현

     

    opaque-token-scg

    3.1. build.gradle

     

    1. dependencyManagement : Spring Cloud Gateway 를 사용하려면 Spring Cloud dependency 가 필요하다.
    2. oauth2-resource-server , oauth2-client 차이점은 후의 포스팅에서 설명.
    3. spring-boot-starter-security dependency 가 필요


    application.yaml (Opaque Token version)

    Oath2 Auth Server 는 Open Source 인 Keycloak 를 활용하여 진행했다.

    • introspection-uri : keycloak 의 introspection-uri 를 작성해준다.
      • {keycloak-server}/auth/realms/{realms-name}/protocol/openid-connect/token/introspect
    • client-id : keycloak 의 client 명
    • clinet-secret : keycloak 의 client secret

    application.yaml (JWT Token version)

    • jwt issuer-uri 만 적어주면 된다.
      • issuer-uri : http://localhost:8080/auth/realms/{realms-name}

    3.2. SecurityConfig.java

    Spring 공식문서에서 SecurityConfig 관련 다양한 Example Code 를 볼 수 있다.

     

    3.3. KeycloakReactiveTokenInstrospector.java

     

    ReactiveTokenIntrospector 를 Custom 해서 Keycloak 의 realm , role 을 체크한다.

     

    Spring 공식문서 에 OpaqueTokenIntrospector 관련 Example Code 가 있다.

     

    3.4. keycloak 에서 user logout

    • keycloak 관리자 홈페이지의 sessions 탭에서 logout all 버튼 클릭 (로컬 테스트에서만 진행)


    Users > Sessions Tab 에서 Logout 버튼을 누르면 개별 User Session 만 지울 수 있다.

    • keycloak 서버로 Rest API 요청
    ###
    # logout api [grant type = password]
    # user session 삭제
    # POST /auth/realms/{realm-name}/protocol/openid-connect/logout
    POST /auth/realms/demo/protocol/openid-connect/logout HTTP/1.1
    Host: localhost:8080
    Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJoMUFMZDh5RHFUcEZZTTJ0MHRzNldLTUc5aHZ1US1GVzVoVG9TNVNOSlNRIn0.eyJleHAiOjE2NzIzNzU0ODAsImlhdCI6MTY3MjM3NTE4MCwianRpIjoiMzg5NGJlODUtNzNlZS00MTQyLWI2NzAtYjI4ZDUzZWFmNjAxIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL2RlbW8iLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMjU3ZTJhMzctNTNiYy00NTQ4LTg5N2UtNzQ1YjRlYjczZjJhIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiZGVtby1jbGllbnQiLCJzZXNzaW9uX3N0YXRlIjoiNDc3ZjgxMmItMDZkNi00MmM0LTgxZDAtMTU3MTU4NDg1ZWE0IiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIiwiZGVmYXVsdC1yb2xlcy1kZW1vIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiZGVtby1jbGllbnQiOnsicm9sZXMiOlsiUk9MRV9VU0VSIl19LCJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJzaWQiOiI0NzdmODEyYi0wNmQ2LTQyYzQtODFkMC0xNTcxNTg0ODVlYTQiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6ImRlbW9fdXNlciJ9.hva2wLG6FdVUUq5ZlEoKlBiIKlRGtS8oyN_aRszAh93Tkl2CAcuyOq8jnIj5w-Sku81MS4fVIO2lVKLI3UIvTke6_gg-90aAonmw27q5R1N1cx4d82BV11KjEmh4THcMGQYG_PFAdcfJzkUc9fancFn64pmDdrPDaH7NsXBDKLl3oDDQFXbB6chthRollEF4PVgfQq5l3XqJ7Fc9xQyrSo8bDIXN-jb6dpE2KNanyrEreXVy135JhQ8X7qS5rcwAz7cT-J--InbdfhJUxiSgp5MULXMIQnv87N_hKSiCVWBOlrJDjPXXBtwwc3nnZ88SVbNLytb_7ZClHztDXUDlNQ
    Content-Type: application/x-www-form-urlencoded
    
    client_secret=83f3e948-3f8c-49da-af7f-87a42d5ea6fd&grant_type=password&client_id=demo-client&username=demo_user&password=1234&refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJiZWMyYTc5Yy0yMjJhLTQ3NzMtOTI2OS03NmVkYTM2MDgxOGQifQ.eyJleHAiOjE2NzIzNzY5ODAsImlhdCI6MTY3MjM3NTE4MCwianRpIjoiMzIwODJhMTctMDY0NS00OGY3LTg1MmYtZWM2NTFhYjA4NjU4IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL2RlbW8iLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvZGVtbyIsInN1YiI6IjI1N2UyYTM3LTUzYmMtNDU0OC04OTdlLTc0NWI0ZWI3M2YyYSIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJkZW1vLWNsaWVudCIsInNlc3Npb25fc3RhdGUiOiI0NzdmODEyYi0wNmQ2LTQyYzQtODFkMC0xNTcxNTg0ODVlYTQiLCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJzaWQiOiI0NzdmODEyYi0wNmQ2LTQyYzQtODFkMC0xNTcxNTg0ODVlYTQifQ.kJXrw45bzxTM1CqCMPvx3XC8xWeLiXJNgpJCiZBggio


    아래 reqeust.http 에서 같은 jwt 토큰으로 요청을 보내면 application - opaquetoken 으로 설정할경우 403 에러코드가 나오지만,
    application - jwt 로 설정할경우 정상적인 응답으로 나오게된다.
    이는 JWT 토큰이 expires_in 시간에만 의거하여 무효화가 되기 때문이다.

    request.http

    ###
    # keycloak jwt token 발급 <grant type = password>
    POST /auth/realms/demo/protocol/openid-connect/token HTTP/1.1
    Host: localhost:8080
    Content-Type: application/x-www-form-urlencoded
    
    client_secret=83f3e948-3f8c-49da-af7f-87a42d5ea6fd&grant_type=password&client_id=demo-client&username=demo_user&password=1234
    
    ###
    # board list 요청 (token O)
    GET /api/boards HTTP/1.1
    Host: localhost:19090
    Authorization: Bearer <jwt-token>
    
    
    ###
    # logout api <grant type = password>
    # session 삭제
    POST /auth/realms/demo/protocol/openid-connect/logout HTTP/1.1
    Host: localhost:8080
    Authorization: Bearer <jwt-token>
    Content-Type: application/x-www-form-urlencoded
    
    client_secret=83f3e948-3f8c-49da-af7f-87a42d5ea6fd&grant_type=password&client_id=demo-client&username=demo_user&password=1234&refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJiZWMyYTc5Yy0yMjJhLTQ3NzMtOTI2OS03NmVkYTM2MDgxOGQifQ.eyJleHAiOjE2NzIzNzY5ODAsImlhdCI6MTY3MjM3NTE4MCwianRpIjoiMzIwODJhMTctMDY0NS00OGY3LTg1MmYtZWM2NTFhYjA4NjU4IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL2F1dGgvcmVhbG1zL2RlbW8iLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvYXV0aC9yZWFsbXMvZGVtbyIsInN1YiI6IjI1N2UyYTM3LTUzYmMtNDU0OC04OTdlLTc0NWI0ZWI3M2YyYSIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJkZW1vLWNsaWVudCIsInNlc3Npb25fc3RhdGUiOiI0NzdmODEyYi0wNmQ2LTQyYzQtODFkMC0xNTcxNTg0ODVlYTQiLCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJzaWQiOiI0NzdmODEyYi0wNmQ2LTQyYzQtODFkMC0xNTcxNTg0ODVlYTQifQ.kJXrw45bzxTM1CqCMPvx3XC8xWeLiXJNgpJCiZBggio

    참고

    'Spring , JPA' 카테고리의 다른 글

    [spring] Spring Webflux CRUD  (0) 2023.01.16
    [kafka] kafka Install  (0) 2023.01.09
    Webflux with MDC  (0) 2023.01.04
    ConfigMap Auto Refresh, Spring Boot  (0) 2022.12.29
    @Slf4j 사용시 log cannot be resolved 에러 처리  (0) 2022.02.15