Spring Security 6 OAuth2.0 ๋ฉ”๋ชจ

์ •๋ฏผ๊ตยท2024๋…„ 10์›” 16์ผ

๐Ÿ“’OAuth2 ๋กœ๊ทธ์ธ

Google์˜ OAuth 2.0 ์ธ์ฆ ์‹œ์Šคํ…œ์„ ๋กœ๊ทธ์ธ์— ์‚ฌ์šฉํ•˜๋ ค๋ฉด Google API ์ฝ˜์†”์—์„œ ํ”„๋กœ์ ํŠธ๋ฅผ ์„ค์ •ํ•˜์—ฌ OAuth 2.0 credentials(ํด๋ผ์ด์–ธํŠธ ID์™€ Secret)์„ ์–ป์–ด์•ผ ํ•จ

Google OAuth 2.0์€ OIDC ์ŠคํŽ™์„ ์ค€์ˆ˜ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, Open ID ์ธ์ฆ์„ ๋ฐ›์Œ

Redirect URI๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ {baseUrl}/login/oauth2/code/{registrationId} ๋กœ ์ •ํ•ด์ ธ์žˆ์Œ

์ธ์ฆ ํ๋ฆ„์— ์ƒ์„ฑํ•œ OAuth ํด๋ผ์ด์–ธํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด application.yml ํŒŒ์ผ์— ํ”„๋กœํผํ‹ฐ ์„ค์ •์„ ํ•ด์ค˜์•ผ ํ•จ

์‚ฌ์šฉ์ž๊ฐ€ Google ์ธ์ฆ์„ ์™„๋ฃŒํ•˜๋ฉด ์‚ฌ์šฉ์ž๋Š” Oauth ํด๋ผ์ด์–ธํŠธ์— ์ ‘๊ทผ ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•˜๊ณ , OAuth ํด๋ผ์ด์–ธํŠธ๋Š” UserInfo Endpoint์—์„œ ์ด๋ฉ”์ผ ์ฃผ์†Œ์™€ ๊ธฐ๋ณธ ํ”„๋กœํ•„ ์ •๋ณด ๋“ฑ์„ ๊ฐ€์ ธ์™€ ์ธ์ฆ๋œ ์„ธ์…˜์„ ์„ค์ •ํ•จ

โœ”๏ธCommonOAuth2Provider

CommonOAuth2Provider๋Š” Google, GitHub, Facebook, Okta์™€ ๊ฐ™์€ ์ž˜ ์•Œ๋ ค์ง„ ์ œ๊ณต์ž๋“ค์— ๋Œ€ํ•œ ๊ธฐ๋ณธ ํด๋ผ์ด์–ธํŠธ ์†์„ฑ์„ ๋ฏธ๋ฆฌ ์ •์˜ํ•ด ๋†“์•˜์Œ.

์˜ˆ๋ฅผ ๋“ค์–ด, ์ œ๊ณต์ž์˜ authorization-uri, token-uri, user-info-uri๋Š” ์ž์ฃผ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๊ธฐ๋ณธ ๊ฐ’์„ ์ œ๊ณตํ•˜์—ฌ ํ•„์š”ํ•œ ์„ค์ •์„ ์ค„์ผ ์ˆ˜ ์žˆ์Œ.

์ด๋กœ ์ธํ•ด, Google ํด๋ผ์ด์–ธํŠธ๋ฅผ ์„ค์ •ํ•  ๋•Œ, client-id์™€ client-secret ์†์„ฑ๋งŒ ๊ฐ€์ง€๊ณ  Google OAuth ํด๋ผ์ด์–ธํŠธ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ.

spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: google-client-id
            client-secret: google-client-secret

OAuth ํด๋ผ์ด์–ธํŠธ ์†์„ฑ์˜ ์ž๋™ ๊ธฐ๋ณธ๊ฐ’ ์„ค์ •์€ registrationId(google)๊ฐ€ CommonOAuth2Provider์—์„œ GOOGLE ์—ด๊ฑฐํ˜•(๋Œ€์†Œ๋ฌธ์ž ๊ตฌ๋ถ„ ์—†์Œ)๊ณผ ์ผ์น˜ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ž˜ ๋™์ž‘ํ•œ๋‹ค.

registrationId๋ฅผ ๋‹ค๋ฅด๊ฒŒ ์ง€์ •ํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ์—๋„, provider ์†์„ฑ์— ์–ด๋–ค ์ œ๊ณต์ž์˜ ์„ค์ •์„ ์ฐธ์กฐํ•˜๋„๋ก ํ• ์ง€ ์„ค์ •ํ•˜์—ฌ, OAuth ํด๋ผ์ด์–ธํŠธ ์†์„ฑ์˜ ์ž๋™ ๊ธฐ๋ณธ๊ฐ’ ์„ค์ •์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค.

spring:
  security:
    oauth2:
      client:
        registration:
          google-login:	
            provider: google	
            client-id: google-client-id
            client-secret: google-client-secret

registrationId๋Š” google-login์œผ๋กœ ์„ค์ •๋˜์–ด ์žˆ๋‹ค.

provider ์†์„ฑ์€ google๋กœ ์„ค์ •๋˜์–ด ์žˆ์œผ๋ฉฐ, ์ด๋ฅผ ํ†ตํ•ด CommonOAuth2Provider.GOOGLE.getBuilder()์—์„œ ์„ค์ •๋œ ํด๋ผ์ด์–ธํŠธ ์†์„ฑ์˜ ์ž๋™ ๊ธฐ๋ณธ๊ฐ’์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

โœ”๏ธConfiguring Custom Provider Properties

OAuth ํด๋ผ์ด์–ธํŠธ ์ž๋™ ๊ตฌ์„ฑ ํด๋ž˜์Šค๋Š” OAuth2ClientAutoConfiguration์ด๋‹ค.

์ž๋™ ๊ตฌ์„ฑ ํด๋ž˜์Šค์—์„œ ํ•˜๋Š” ์ผ์€

  1. ๊ตฌ์„ฑ๋œ OAuth ํด๋ผ์ด์–ธํŠธ ์†์„ฑ์—์„œ ClientRegistration(๋“ค)์„ ๊ตฌ์„ฑํ•˜์—ฌ ClientRegistrationRepository๋ฅผ @Bean์œผ๋กœ ๋“ฑ๋ก
  2. SecurityFilterChain์„ @Bean์œผ๋กœ ๋“ฑ๋กํ•˜๊ณ , httpSecurity.oauth2Login()์„ ํ†ตํ•ด OAuth 2.0 ๋กœ๊ทธ์ธ์„ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.

์ž๋™ ๊ตฌ์„ฑ์„ ์žฌ์ •์˜ ํ•˜๋ ค๋ฉด

  1. ClientRegistrationRepository๋ฅผ @Bean์œผ๋กœ ๋“ฑ๋ก
  2. SecurityFilterChain์„ @Bean์œผ๋กœ ๋“ฑ๋ก
  3. ์ž๋™ ๊ตฌ์„ฑ์„ ์™„์ „ํžˆ ์žฌ์ •์˜

์œ„ ๋ฐฉ๋ฒ•์œผ๋กœ ์žฌ์ •์˜๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.

๋ณดํ†ต์€ 2๋ฒˆ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•ด์„œ ์ธ์ฆ ๋กœ์ง์„ ์ปค์Šคํ…€ํ•˜๋Š” ๊ฒƒ ๊ฐ™๋‹ค.

โœ”๏ธHttpSecurity.oauth2Login()

HttpSecurity.oauth2Login()์€ OAuth2 ๋กœ๊ทธ์ธ์„ ์ปค์Šคํ…€ํ•˜๊ธฐ ์œ„ํ•œ ๋‹ค์–‘ํ•œ ๊ตฌ์„ฑ ์˜ต์…˜์„ ์ œ๊ณตํ•œ๋‹ค.

์ฃผ์š” ๊ตฌ์„ฑ ์˜ต์…˜์€ ํ”„๋กœํ† ์ฝœ ์—”๋“œํฌ์ธํŠธ์— ํ•ด๋‹นํ•˜๋Š” ๊ทธ๋ฃน์œผ๋กœ ๋‚˜๋‰œ๋‹ค.

@Configuration
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .oauth2Login(oauth2 -> oauth2
                .authorizationEndpoint(authorization -> authorization
                        ...
                )
                .redirectionEndpoint(redirection -> redirection
                        ...
                )
                .tokenEndpoint(token -> token
                        ...
                )
                .userInfoEndpoint(userInfo -> userInfo
                        ...
                )
            );
        return http.build();
    }
}

๐Ÿ“Œ์—”๋“œํฌ์ธํŠธ

๐Ÿ‘‰Authorization EndPoint

OAuth ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์‚ฌ์šฉ์ž๋ฅผ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ๋ณด๋‚ด์„œ ๊ถŒํ•œ์„ ๋ถ€์—ฌ๋ฐ›๋Š” ๋ฐ ์‚ฌ์šฉํ•˜๋Š” ์—”๋“œํฌ์ธํŠธ๋‹ค.

์‚ฌ์šฉ์ž๋Š” ์—ฌ๊ธฐ์„œ ๋กœ๊ทธ์ธํ•˜๊ณ , ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์š”์ฒญํ•œ ์ ‘๊ทผ ๊ถŒํ•œ์„ ์Šน์ธํ•œ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ, ์ธ์ฆ์ด ์™„๋ฃŒ๋˜๋ฉด Redirection URL๋กœ ์ด๋™ํ•˜๊ฒŒ ๋œ๋‹ค.

authorizationEndpoint์˜ baseUri() ๋ฉ”์†Œ๋“œ๋กœ authorization endpoint์˜ ๊ธฐ๋ณธ ๊ฒฝ๋กœ๋ฅผ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๋‹ค.

๊ธฐ๋ณธ ๊ฒฝ๋กœ๋Š” OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI + "/{registrationId}" ๋กœ ์ง€์ •๋˜์–ด ์žˆ๋‹ค.

์ด ๊ฐ’์˜ ๊ธฐ๋ณธ๊ฐ’์€ /oauth2/authorization์ด๊ณ ,

{registrationId}๋Š” ๋“ฑ๋กํ•œ ๊ฐ OAuth ํด๋ผ์ด์–ธํŠธ(์˜ˆ: google, github ๋“ฑ)์˜ ๊ณ ์œ  ์‹๋ณ„์ž๋ฅผ ๋งํ•œ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, Google OAuth ํด๋ผ์ด์–ธํŠธ๋ฅผ ๋“ฑ๋กํ–ˆ์œผ๋ฉด ์ด ๊ฐ’์€ "google"์ด ๋œ๋‹ค.

๐Ÿ‘‰Token Endpoint:

ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๊ถŒํ•œ ๋ถ€์—ฌ ์ฝ”๋“œ๋ฅผ ์•ก์„ธ์Šค ํ† ํฐ์œผ๋กœ ๊ตํ™˜ํ•˜๊ธฐ ์œ„ํ•ด, ์ผ๋ฐ˜์ ์œผ๋กœ ํด๋ผ์ด์–ธํŠธ ์ธ์ฆ์„ ํ†ตํ•ด ์‚ฌ์šฉํ•˜๋Š” ์—”๋“œํฌ์ธํŠธ๋‹ค.

๐Ÿ‘‰Redirection Endpoint:

์ธ์ฆ ์„œ๋ฒ„๊ฐ€ ๋ฆฌ์†Œ์Šค ์†Œ์œ ์ž(์‚ฌ์šฉ์ž) ์‚ฌ์šฉ์ž ์—์ด์ „ํŠธ(๋ธŒ๋ผ์šฐ์ €)๋ฅผ ํ†ตํ•ด ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ธ์ฆ ์ž๊ฒฉ์„ ํฌํ•จํ•œ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•˜๋Š” ์—”๋“œํฌ์ธํŠธ๋‹ค.

OAuth 2.0 ๋กœ๊ทธ์ธ์€ Authorization Code Grant ๋ฐฉ์‹์„ ํ™œ์šฉํ•˜๋ฏ€๋กœ, ์ธ์ฆ ์ž๊ฒฉ ์ฆ๋ช…์€ Authorization Code๋‹ค.

๊ธฐ๋ณธ์ ์œผ๋กœ Authorization Response์˜ baseUri(๋ฆฌ๋””๋ ‰์…˜ ์—”๋“œํฌ์ธํŠธ)๋Š” /login/oauth2/code/{registrationId}์ด๋ฉฐ, ์ด๋Š” OAuth2LoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI์— ์ •์˜๋˜์–ด ์žˆ๋‹ค.

Authorization Response baseUri๋ฅผ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•˜๋ ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

@Configuration
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .oauth2Login(oauth2 -> oauth2
                .redirectionEndpoint(redirection -> redirection
                    .baseUri("/login/oauth2/callback/*")  // ์ปค์Šคํ…€ ์—”๋“œํฌ์ธํŠธ ์„ค์ •
                    ...
                )
            );
        return http.build();
    }
}

Authorization Respoinse BaseUri๋ฅผ ์ปค์Šคํ…€ ํ–ˆ๋‹ค๋ฉด, ClientRegistration.redirectUri๊ฐ€ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•๋œ Authorization Response baseUri์™€ ์ผ์น˜ํ•˜๋Š”์ง€๋„ ํ™•์ธํ•ด์•ผ ํ•œ๋‹ค.

๐Ÿ‘‰UserInfo Endpoint:

OpenID Connect Core 1.0 ์‚ฌ์–‘์—์„œ UserInfo Endpoint๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ •์˜ํ•˜๊ณ  ์žˆ๋‹ค

UserInfo ์—”๋“œํฌ์ธํŠธ๋Š” ์ธ์ฆ๋œ ์ตœ์ข… ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ํด๋ ˆ์ž„(Claims)์„ ๋ฐ˜ํ™˜ํ•˜๋Š” OAuth 2.0 ๋ณดํ˜ธ ๋ฆฌ์†Œ์Šค๋กœ,

์ธ์ฆ๋œ ์ตœ์ข… ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ํด๋ ˆ์ž„์„ ๋ฐ˜ํ™˜ํ•˜๋Š” OAuth 2.0 ๋ณดํ˜ธ ๋ฆฌ์†Œ์Šค๋กœ, ํด๋ผ์ด์–ธํŠธ๋Š” OpenID Connect ์ธ์ฆ์„ ํ†ตํ•ด ์–ป์€ ์•ก์„ธ์Šค ํ† ํฐ์„ ์‚ฌ์šฉํ•˜์—ฌ UserInfo Endpoint์— ์š”์ฒญ์„ ๋ณด๋‚ธ๋‹ค.

์ด ํด๋ ˆ์ž„์€ ์ผ๋ฐ˜์ ์œผ๋กœ ํด๋ ˆ์ž„์— ๋Œ€ํ•œ ์ด๋ฆ„-๊ฐ’ ์Œ ์ปฌ๋ ‰์…˜์„ ํฌํ•จํ•˜๋Š” JSON ๊ฐ์ฒด๋กœ ํ‘œํ˜„๋œ๋‹ค.

UserInfo ์—”๋“œํฌ์ธํŠธ์™€์˜ ํ†ต์‹ ์€ ๋ฐ˜๋“œ์‹œ TLS๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.(์„น์…˜ 16.17์„ ์ฐธ์กฐ)

UserInfo ์—”๋“œํฌ์ธํŠธ๋Š” ๋ฐ˜๋“œ์‹œ RFC 7231 [RFC7231]์— ์ •์˜๋œ HTTP GET ๋ฐ HTTP POST ๋ฉ”์„œ๋“œ๋ฅผ ์ง€์›ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
UserInfo ์—”๋“œํฌ์ธํŠธ๋Š” ๋ฐ˜๋“œ์‹œ OAuth 2.0 ๋ฒ ์–ด๋Ÿฌ ํ† ํฐ ์‚ฌ์šฉ๋ฒ• [RFC6750]์— ๋”ฐ๋ผ ์•ก์„ธ์Šค ํ† ํฐ์„ ๋ฐ›์•„๋“ค์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.
UserInfo ์—”๋“œํฌ์ธํŠธ๋Š” ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํด๋ผ์ด์–ธํŠธ์™€ ๊ธฐํƒ€ ๋ธŒ๋ผ์šฐ์ € ๊ธฐ๋ฐ˜ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก CORS [CORS] ๋˜๋Š” ๊ธฐํƒ€ ์ ์ ˆํ•œ ๋ฐฉ๋ฒ•์„ ์ง€์›ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

UserInfo ์—”๋“œํฌ์ธํŠธ๋Š” ๋‹ค์Œ ํ•˜์œ„ ์„น์…˜์—์„œ ์„ค๋ช…๋œ ์—ฌ๋Ÿฌ ๊ตฌ์„ฑ ์˜ต์…˜์„ ํฌํ•จํ•˜๊ณ  ์žˆ๋‹ค.

  • ์‚ฌ์šฉ์ž ๊ถŒํ•œ ๋งคํ•‘ (Mapping User Authorities)
  • OAuth 2.0 UserService
  • OpenID Connect 1.0 UserService

โœ”๏ธOAuth 2.0 UserService

DefaultOAuth2UserService๋Š” ํ‘œ์ค€ OAuth 2.0 ์ œ๊ณต์ž(Provider)๋ฅผ ์ง€์›ํ•˜๋Š” OAuth2UserService์˜ ๊ตฌํ˜„์ฒด๋‹ค.

OAuth2UserService๋Š” UserInfo ์—”๋“œํฌ์ธํŠธ์—์„œ ์ตœ์ข… ์‚ฌ์šฉ์ž(๋ฆฌ์†Œ์Šค ์†Œ์œ ์ž)์˜ ์†์„ฑ์„ ๋ฐ›์•„์˜จ๋‹ค.(์ด๋•Œ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ธ์ฆ ํ๋ฆ„ ์ค‘์— ๋ถ€์—ฌ๋ฐ›์€ ์•ก์„ธ์Šค ํ† ํฐ์„ ์‚ฌ์šฉํ•œ๋‹ค.) ๊ทธ๋ฆฌ๊ณ  ๊ทธ ๊ฒฐ๊ณผ๋กœ OAuth2User ํ˜•ํƒœ์˜ AuthenticatedPrincipal์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

DefaultOAuth2UserService๋Š” UserInfo ์—”๋“œํฌ์ธํŠธ์— ์š”์ฒญํ•  ๋•Œ RestOperations(์ธํ„ฐํŽ˜์ด์Šค) ์ธ์Šคํ„ด์Šค๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

DefaultOAuth2UserService๋ฅผ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•˜๊ฑฐ๋‚˜ OAuth2UserService์˜ ์ž์ฒด ๊ตฌํ˜„์„ ์ œ๊ณตํ•˜๋Š” ๊ฒฝ์šฐ, ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ตฌ์„ฑํ•ด์•ผ ํ•œ๋‹ค.

@Configuration
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .oauth2Login(oauth2 -> oauth2
                .userInfoEndpoint(userInfo -> userInfo
                    .userService(this.oauth2UserService())
                    ...
                )
            );
        return http.build();
    }

    private OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
        ...
    }
}

OidcUserService ์ž์ฒด ๊ตฌํ˜„์„ ์ œ๊ณตํ•˜๋ ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ตฌ์„ฑํ•ด์•ผ ํ•œ๋‹ค.

@Configuration
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .oauth2Login(oauth2 -> oauth2
                .userInfoEndpoint(userInfo -> userInfo
                    .oidcUserService(this.oidcUserService())
                    ...
                )
            );
        return http.build();
    }

    private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
        ...
    }
}

๐Ÿ“ŒOAuth2 ๋กœ๊ทธ์ธ ๊ตฌ์„ฑ ์˜ต์…˜

๋‹ค์Œ ์ฝ”๋“œ๋Š” oauth2Login() DSL์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ „์ฒด ๊ตฌ์„ฑ ์˜ต์…˜์ด๋‹ค. ์ฐธ๊ณ ๋งŒ ํ•˜๋ฉด ๋  ๊ฒƒ ๊ฐ™๋‹ค. ๋ฌผ๋ก  DSL์™ธ์—๋„ xml ์„ค์ •์œผ๋กœ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค. ๊ตณ์ด ์‚ดํŽด๋ณด์ง„ ์•Š์•˜๋‹ค.

@Configuration
@EnableWebSecurity
public class OAuth2LoginSecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .oauth2Login(oauth2 -> oauth2
                .clientRegistrationRepository(this.clientRegistrationRepository())
                .authorizedClientRepository(this.authorizedClientRepository())
                .authorizedClientService(this.authorizedClientService())
                .loginPage("/login")
                .authorizationEndpoint(authorization -> authorization
                    .baseUri(this.authorizationRequestBaseUri())
                    .authorizationRequestRepository(this.authorizationRequestRepository())
                    .authorizationRequestResolver(this.authorizationRequestResolver())
                )
                .redirectionEndpoint(redirection -> redirection
                    .baseUri(this.authorizationResponseBaseUri())
                )
                .tokenEndpoint(token -> token
                    .accessTokenResponseClient(this.accessTokenResponseClient())
                )
                .userInfoEndpoint(userInfo -> userInfo
                    .userAuthoritiesMapper(this.userAuthoritiesMapper())
                    .userService(this.oauth2UserService())
                    .oidcUserService(this.oidcUserService())
                )
            );
        return http.build();
    }
}

๐Ÿ“ŒOAuth 2.0 Login Page

Spring Security๋Š” OAuth2 ์ธ์ฆ์„ ์œ„ํ•œ ๊ธฐ๋ณธํŽ˜์ด์ง€๋ฅผ ๊ตฌ์„ฑํ•˜๊ณ  ์žˆ๋‹ค. loginPage() api๋ฅผ ํ†ตํ•ด ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ๊ฒฝ๋กœ ์„ค์ •์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

OAuth2 ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋Š” DefaultLoginPageGeneratingFilter์— ์˜ํ•ด ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋œ๋‹ค.

ํ”„๋ก ํŠธ๊ฐ€ ๋”ฐ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ๋‹ค๋ฉด, ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ์„ค์ •์€ ๋นผ๋„ ๋œ๋‹ค.

๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ์„ค์ •์„ ๋นผ๋„ ๊ธฐ๋ณธ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋Š” ์ œ๊ณต๋˜๋ฉฐ ๊ธฐ๋ณธ ๊ฒฝ๋กœ๋Š” "/login" ๊ฒฝ๋กœ๊ฐ€ ๋œ๋‹ค. ํ”„๋ก ํŠธ๊ฐ€ ๋”ฐ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ๋‹ค๋ฉด, ํ•ด๋‹น ๊ฒฝ๋กœ์— ์ ‘๊ทผํ•˜์ง€ ๋ชปํ•˜๋„๋ก ์„ค์ •ํ•  ํ•„์š”๋„ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค.

๐Ÿ“’ํ•ต์‹ฌ ์ธํ„ฐํŽ˜์ด์Šค์™€ ํด๋ž˜์Šค๋“ค

โœ”๏ธClientRegistration

ClientRegistration์€ OAuth 2.0 ๋˜๋Š” OpenID Connect 1.0 ๊ณต๊ธ‰์ž์™€ ๋“ฑ๋ก๋œ ํด๋ผ์ด์–ธํŠธ๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค.

ClientRegistration ๊ฐ์ฒด๋Š” ํด๋ผ์ด์–ธํŠธ ID, ํด๋ผ์ด์–ธํŠธ secret, ์ธ์ฆ ๋ถ€์—ฌ ๋ฐฉ์‹, ๋ฆฌ๋””๋ ‰์…˜ URI, ์Šค์ฝ”ํ”„(scope), ์ธ์ฆ URI, ํ† ํฐ URI ๋ฐ ๊ธฐํƒ€ ์„ธ๋ถ€ ์ •๋ณด๋ฅผ ํฌํ•จํ•˜๋Š” ์ •๋ณด๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.

ClientRegistration ๋ฐ ๊ทธ ์†์„ฑ๋“ค์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ •์˜๋˜์–ด ์žˆ๋‹ค.

public final class ClientRegistration {
	private String registrationId;	
	private String clientId;	
	private String clientSecret;	
	private ClientAuthenticationMethod clientAuthenticationMethod;	
	private AuthorizationGrantType authorizationGrantType;	
	private String redirectUri;	
	private Set<String> scopes;	
	private ProviderDetails providerDetails;
	private String clientName;	

	public class ProviderDetails {
		private String authorizationUri;	
		private String tokenUri;	
		private UserInfoEndpoint userInfoEndpoint;
		private String jwkSetUri;	
		private String issuerUri;	
        private Map<String, Object> configurationMetadata;  

		public class UserInfoEndpoint {
			private String uri;	
            private AuthenticationMethod authenticationMethod;  
			private String userNameAttributeName;	
		}
	}
}

๊ณต์‹๋ฌธ์„œ ์ฐธ์กฐ

โœ”๏ธClientRegistrationRepository

ClientRegistrationRepository๋Š” OAuth 2.0 / OpenID Connect 1.0์˜ ClientRegistration ์ •๋ณด๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์ €์žฅ์†Œ ์—ญํ• ์„ ํ•œ๋‹ค.

ํด๋ผ์ด์–ธํŠธ ๋“ฑ๋ก ์ •๋ณด๋Š” ๊ถ๊ทน์ ์œผ๋กœ ๊ด€๋ จ๋œ ์ธ์ฆ ์„œ๋ฒ„(Authorization Server)์— ์ €์žฅ๋˜๊ณ  ์†Œ์œ ๋ฉ๋‹ˆ๋‹ค. ์ด ์ €์žฅ์†Œ๋Š” ์ธ์ฆ ์„œ๋ฒ„์— ์ €์žฅ๋œ ๊ธฐ๋ณธ ํด๋ผ์ด์–ธํŠธ ๋“ฑ๋ก ์ •๋ณด ์ค‘ ์ผ๋ถ€๋ฅผ ๊ฒ€์ƒ‰ํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

Spring Boot ์ž๋™ ์„ค์ •์€ spring.security.oauth2.client.registration.[registrationId] ์†์„ฑ ์•„๋ž˜์— ์žˆ๋Š” ๊ฐ๊ฐ์˜ ํ”„๋กœํผํ‹ฐ๋“ค์„ ClientRegistration ์ธ์Šคํ„ด์Šค๋กœ ๋ฐ”์ธ๋”ฉํ•˜๊ณ , ๊ฐ๊ฐ์˜ ClientRegistration ์ธ์Šคํ„ด์Šค๋ฅผ ClientRegistrationRepository ๋‚ด์— ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.

ClientRegistrationRepository์˜ ๊ธฐ๋ณธ ๊ตฌํ˜„์ฒด๋Š” InMemoryClientRegistrationRepository์ž…๋‹ˆ๋‹ค.

์ž๋™ ์„ค์ •์€ ๋˜ํ•œ ClientRegistrationRepository๋ฅผ @Bean์œผ๋กœ ApplicationContext์— ๋“ฑ๋กํ•˜์—ฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ํ•„์š” ์‹œ ์˜์กด์„ฑ ์ฃผ์ž…์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ์€ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค:

@Controller
public class OAuth2ClientController {

	@Autowired
	private ClientRegistrationRepository clientRegistrationRepository;

	@GetMapping("/")
	public String index() {
		ClientRegistration oktaRegistration =
			this.clientRegistrationRepository.findByRegistrationId("okta");

		...

		return "index";
	}
}

โœ”๏ธOAuth2AuthorizedClient

OAuth2AuthorizedClient๋Š” ์Šน์ธ๋œ ํด๋ผ์ด์–ธํŠธ๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์Šน์ธ๋˜์—ˆ๋‹ค๊ณ  ๊ฐ„์ฃผ๋˜๋Š” ๊ฒฝ์šฐ๋Š” ์ตœ์ข… ์‚ฌ์šฉ์ž(๋ฆฌ์†Œ์Šค ์†Œ์œ ์ž)๊ฐ€ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ณดํ˜ธ๋œ ๋ฆฌ์†Œ์Šค์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ถŒํ•œ์„ ๋ถ€์—ฌํ–ˆ์„ ๋•Œ์ž…๋‹ˆ๋‹ค.

OAuth2AuthorizedClient๋Š” OAuth2AccessToken(๋ฐ ์„ ํƒ์ ์œผ๋กœ OAuth2RefreshToken)์„ ClientRegistration(ํด๋ผ์ด์–ธํŠธ)๊ณผ ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•œ ์ตœ์ข… ์‚ฌ์šฉ์ž(Principal)์—๊ฒŒ ์—ฐ๊ฒฐํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.

OAuth2AuthorizedClientRepository์™€ OAuth2AuthorizedClientService
OAuth2AuthorizedClientRepository๋Š” ์›น ์š”์ฒญ ๊ฐ„์— OAuth2AuthorizedClient๋ฅผ ์ €์žฅํ•˜๋Š” ์—ญํ• ์„ ๋‹ด๋‹นํ•˜๋ฉฐ, OAuth2AuthorizedClientService๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ˆ˜์ค€์—์„œ OAuth2AuthorizedClient๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์ฃผ์š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.

๊ฐœ๋ฐœ์ž ๊ด€์ ์—์„œ OAuth2AuthorizedClientRepository ๋˜๋Š” OAuth2AuthorizedClientService๋Š” ํด๋ผ์ด์–ธํŠธ์™€ ์—ฐ๊ฒฐ๋œ OAuth2AccessToken์„ ์กฐํšŒํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜์—ฌ ๋ณดํ˜ธ๋œ ๋ฆฌ์†Œ์Šค ์š”์ฒญ์„ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ์€ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค:

@Controller
public class OAuth2ClientController {

    @Autowired
    private OAuth2AuthorizedClientService authorizedClientService;

    @GetMapping("/")
    public String index(Authentication authentication) {
        OAuth2AuthorizedClient authorizedClient =
            this.authorizedClientService.loadAuthorizedClient("okta", authentication.getName());

        OAuth2AccessToken accessToken = authorizedClient.getAccessToken();

        ...

        return "index";
    }
}

Spring Boot ์ž๋™ ์„ค์ •์€ OAuth2AuthorizedClientRepository ๋˜๋Š” OAuth2AuthorizedClientService๋ฅผ @Bean์œผ๋กœ ApplicationContext์— ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ํ•„์š”์— ๋”ฐ๋ผ ์ปค์Šคํ…€ OAuth2AuthorizedClientRepository ๋˜๋Š” OAuth2AuthorizedClientService๋ฅผ @Bean์œผ๋กœ ๋“ฑ๋กํ•˜์—ฌ ๊ธฐ๋ณธ ์„ค์ •์„ ๋ฎ์–ด์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

OAuth2AuthorizedClientService์˜ ๊ธฐ๋ณธ ๊ตฌํ˜„์€ InMemoryOAuth2AuthorizedClientService์ด๋ฉฐ, ์ด๋Š” OAuth2AuthorizedClient ๊ฐ์ฒด๋ฅผ ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.

๋Œ€์•ˆ์œผ๋กœ, JdbcOAuth2AuthorizedClientService๋ฅผ ์„ค์ •ํ•˜์—ฌ OAuth2AuthorizedClient ์ธ์Šคํ„ด์Šค๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

JdbcOAuth2AuthorizedClientService๋Š” OAuth 2.0 ํด๋ผ์ด์–ธํŠธ ์Šคํ‚ค๋งˆ์—์„œ ์„ค๋ช…๋œ ํ…Œ์ด๋ธ” ์ •์˜์— ์˜์กดํ•ฉ๋‹ˆ๋‹ค.

profile
๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์ž

0๊ฐœ์˜ ๋Œ“๊ธ€