Upgrade to Pro — share decks privately, control downloads, hide ads and more …

JUGNsk Meetup#9. Евгений Горбачёв: "Как натянуть сову на глобус — кластеризуем Spring OAuth2"

jugnsk
June 13, 2019

JUGNsk Meetup#9. Евгений Горбачёв: "Как натянуть сову на глобус — кластеризуем Spring OAuth2"

Контекст: OpenID/OAuth2 Workflow с использованием Google в качестве OpenID-провайдера и Redis в качестве хранилища сессий.

Всё прекрасно работало на локальной машине и (па-бам!) сломалось при выкатке на прод на кластер.

Дальше закрутилось: поиск, где порылась собака; расколупывание реализации OpenID/OAuth2 в Spring; установка диагноза; поиск лечения.

Детективный рассказ-боль, с элементами теории OpenID/OAuth2 и его реализации в Spring.

jugnsk

June 13, 2019
Tweet

More Decks by jugnsk

Other Decks in Programming

Transcript

  1. Sun JavaSoft • Classic VM HotSpot Client/Server VMs under Lars

    Bak’s tech leadership Initially as an add-on for Java 1.2, the part of Java since 1.3 • Serviceability Team ❖ Logging ❖ JPDA: JVMTI (JVMDI + JVMPI), JDWP, JDI ❖ Dtrace ❖ etc. -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8020 2
  2. What Is OAuth2 & OpenID Connect ⚫ OAuth 2.0 is

    the industry-standard protocol for authorization that enables Clients to obtain limited access to User accounts on an HTTP service. It works by delegating user authentication to the Service that hosts the User account, and authorizing third-party Clients to access the User account. ⚫ OpenID Connect 1.0 (OIDC) is a simple identity layer on top of the OAuth 2.0. It allows Clients to verify the identity of the User based on the authentication performed by an Authorization Server, as well as to obtain profile information about the User. ⚫ Single Sign-On (SSO) 3
  3. Filtering System of the Spring Security o.springframework.security.web.FilterChainProxy : ... at

    position 2 of 15 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter' o.s.s.web.context.HttpSessionSecurityContextRepository : HttpSession returned null object for SPRING_SECURITY_CONTEXT o.s.s.web.context.HttpSessionSecurityContextRepository : No SecurityContext was available from the HttpSession: org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWra pper$HttpSessionWrapper@b4e842f. A new one will be created. o.springframework.security.web.FilterChainProxy : ... at position 3 of 15 in additional filter chain; firing Filter: 'HeaderWriterFilter' ... o.springframework.security.web.FilterChainProxy : ... at position 12 of 15 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter' o.s.s.web.authentication.AnonymousAuthenticationFilter : Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken@915d6074: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@fffde5d4: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: c6b30a8e-d652-44f1-bfe2-37b4bfb8764a; Granted Authorities: ROLE_ANONYMOUS' 13
  4. Filtering System of the Spring Security o.springframework.security.web.FilterChainProxy : ... at

    position 2 of 15 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter' o.s.s.web.context.HttpSessionSecurityContextRepository : HttpSession returned null object for SPRING_SECURITY_CONTEXT o.s.s.web.context.HttpSessionSecurityContextRepository : No SecurityContext was available from the HttpSession: org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWra pper$HttpSessionWrapper@b4e842f. A new one will be created. o.springframework.security.web.FilterChainProxy : ... at position 3 of 15 in additional filter chain; firing Filter: 'HeaderWriterFilter' ... o.springframework.security.web.FilterChainProxy : ... at position 12 of 15 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter' o.s.s.web.authentication.AnonymousAuthenticationFilter : Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken@915d6074: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@fffde5d4: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: c6b30a8e-d652-44f1-bfe2-37b4bfb8764a; Granted Authorities: ROLE_ANONYMOUS' 14
  5. Implementation of Saving public class OAuth2ClientContextSavingRedirectStrategy extends DefaultRedirectStrategy { public

    void sendRedirect(HttpServletRequest request, HttpServletResponse response, String url) throws IOException { HttpSession session = request.getSession(false); AccessTokenRequest accessTokenRequest = oAuth2Context.getAccessTokenRequest(); String stateKey = accessTokenRequest.getStateKey(); Object preservedState = accessTokenRequest.getPreservedState(); session.setAttribute(OAuth2.CLIENT_CONTEXT_STATE_ATTR_NAME, stateKey); session.setAttribute(OAuth2.CLIENT_CONTEXT_PRESERVED_URI_ATTR_NAME, preservedState); super.sendRedirect(request, response, url); } 16
  6. Implementation of Restoring public class OAuth2ClientContextRestoringFilter extends OncePerRequestFilter { public

    void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { HttpSession session = request.getSession(false); String contextKey = session.getAttribute(OAuth2.CLIENT_CONTEXT_STATE_ATTR_NAME); String contextState = session.getAttribute(OAuth2.CLIENT_CONTEXT_PRESERVED_URI_ATTR_NA ME); oAuth2Context.setPreservedState(contextKey, contextState); filterChain.doFilter(request, response); } 18
  7. Authorization Process initiated on Node 1 org.springframework.security.web.FilterChainProxy : ... firing

    Filter: 'OAuth2ClientContextFilter' ... c.t.accounts.uaa.oidc.OIdCAuthenticationProcessingFilter : Getting an access token c.t.a.uaa.oidc.OAuth2ClientContextSavingRedirectStrategy : The user's authorization required by redirecting to https://accounts.google.com/o/oauth2/v2/auth?client_id=843319630440-v2brjoe8qmp2fp c50842k5vejjitpj50.apps.googleusercontent.com&redirect_uri=http://localhost:8503/oidc -google-login&response_type=code&scope=openid%20email%20profile&state=RPFhAe c.t.a.uaa.oidc.OAuth2ClientContextSavingRedirectStrategy : Preserving the key 'RPFhAe' and state 'http://localhost:8503/oidc-google-login' for the session '4c8092c2-565d-419f-8304-5ece562b7b99' c.t.a.uaa.oidc.OAuth2ClientContextSavingRedirectStrategy : Redirecting to 'https://accounts.google.com/o/oauth2/v2/auth?client_id=843319630440-v2brjoe8qmp2f pc50842k5vejjitpj50.apps.googleusercontent.com&redirect_uri=http://localhost:8503/oid c-google-login&response_type=code&scope=openid%20email%20profile&state=RPFhAe' 19
  8. Authorization Process continues on Node 2 org.springframework.security.web.FilterChainProxy : /oidc-google-login?state=RPFhAe&code=4%2FYwE2GohNeOxDmTgkYbHaI-1QD2ynCbzacAfeobw33tlA4YzB2yPoQE4jT Pgux00bBki3Dm2oNHKky-7gH01fIEc&scope=email+profile+openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2

    Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile&authuser=0&session_state=1ac09b dacdb6923592c725f6fd8756b4b182ba6d..441c&prompt=consent at position 6 of 15 in additional filter chain; firing Filter: 'OAuth2ClientContextRestoringFilter' c.t.accounts.uaa.oidc.OIdCAuthenticationProcessingFilter : Restoring the key 'RPFhAe' and state 'http://localhost:8503/oidc-google-login' for the session '4c8092c2-565d-419f-8304-5ece562b7b99' org.springframework.security.web.FilterChainProxy : ... at position 7 of 15 in additional filter chain; firing Filter: 'OAuth2ClientContextFilter' ... c.t.accounts.uaa.oidc.OIdCAuthenticationProcessingFilter : Getting an access token o.s.s.o.c.t.grant.code.AuthorizationCodeAccessTokenProvider : Retrieving token from https://oauth2.googleapis.com/token org.springframework.web.client.RestTemplate : Created POST request for "https://oauth2.googleapis.com/token" o.s.s.o.c.t.grant.code.AuthorizationCodeAccessTokenProvider : Encoding and sending form: {grant_type=[authorization_code], code=[4/YwE2GohNeOxDmTgkYbHaI-1QD2ynCbzacAfeobw33tlA4YzB2yPoQE4jTPgux00bBki3Dm2oNHKky-7gH01fIEc], redirect_uri=[http://localhost:8503/oidc-google-login]} org.springframework.web.client.RestTemplate : POST request for "https://oauth2.googleapis.com/token" resulted in 200 (OK) o.springframework.web.client.HttpMessageConverterExtractor : Reading [interface org.springframework.security.oauth2.common.OAuth2AccessToken] as ... 20
  9. Summary ⚫ The problem is obvious, but amazingly Spring OAuth2

    is no clustered. ⚫ The proper way is to implement Redis and/or JDBC versions of the interface org.springframework.security.oauth2.client.OAuth2ClientContext But surprisingly nobody did. ⚫ The implemented solution: ⚫ Saving the state in the overwritten method sendRedirect() of DefaultRedirectStrategy when OAuth2ClientContext is fully formed. ⚫ Restoring the state in the overwritten method doFilterInternal() of OncePerRequestFilter before the Spring checks its value in OAuth2ClientContextFilter. 21