3 ๋ถ„ ์†Œ์š”

application.yml / OAuth2ClientProperties

ํด๋ผ์ด์–ธํŠธ ๊ถŒํ•œ ๋ถ€์—ฌ ์š”์ฒญ ์‹œ์ž‘

  1. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ธ๊ฐ€์„œ๋ฒ„๋กœ ๊ถŒํ•œ ๋ถ€์—ฌ ์š”์ฒญ์„ ํ•˜๊ฑฐ๋‚˜ ํ† ํฐ ์š”์ฒญ์„ ํ•  ๊ฒฝ์šฐ ํด๋ผ์ด์–ธํŠธ ์ •๋ณด ๋ฐ ์—”๋“œํฌ์ธํŠธ ์ •๋ณด๋ฅผ ์ฐธ์กฐํ•ด์„œ ์ „๋‹ฌํ•œ๋‹ค
  2. application.yml ํ™˜๊ฒฝ์„ค์ • ํŒŒ์ผ์— ํด๋ผ์ด์–ธํŠธ ์„ค์ •๊ณผ ์ธ๊ฐ€์„œ๋ฒ„ ์—”๋“œํฌ์ธํŠธ ์„ค์ •์„ ํ•œ๋‹ค
  3. ์ดˆ๊ธฐํ™”๊ฐ€ ์ง„ํ–‰๋˜๋ฉด application.yml ์— ์žˆ๋Š” ํด๋ผ์ด์–ธํŠธ ๋ฐ ์—”๋“œํฌ์ธํŠธ ์ •๋ณด๊ฐ€ OAuth2ClientProperties ์˜ ๊ฐ ์†์„ฑ์— ๋ฐ”์ธ๋”ฉ ๋œ๋‹ค
  4. OAuth2ClientProperties ์— ๋ฐ”์ธ๋”ฉ ๋˜์–ด ์žˆ๋Š” ์†์„ฑ์˜ ๊ฐ’์€ ์ธ๊ฐ€์„œ๋ฒ„๋กœ ๊ถŒํ•œ๋ถ€์—ฌ ์š”์ฒญ์„ ํ•˜๊ธฐ ์œ„ํ•œ ClientRegistration ํด๋ž˜์Šค์˜ ํ•„๋“œ์— ์ €์žฅ๋œ๋‹ค
  5. OAuth2Client ๋Š” ClientRegistration ๋ฅผ ์ฐธ์กฐํ•ด์„œ ๊ถŒํ•œ๋ถ€์—ฌ ์š”์ฒญ์„ ์œ„ํ•œ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๊ตฌ์„ฑํ•˜๊ณ  ์ธ๊ฐ€์„œ๋ฒ„์™€ ํ†ต์‹ ํ•œ๋‹ค

image

application.yml

spring:
  security:
    oauth2:
      client:
        registration: ## ํด๋ผ์ด์–ธํŠธ ์„ค์ •
          keycloak:
            authorization-grant-type: authorization_code # OAuth 2.0 ๊ถŒํ•œ ๋ถ€์—ฌ ํƒ€์ž…
            client-id: oauth2-client-app # ์„œ๋น„์Šค ๊ณต๊ธ‰์ž์— ๋“ฑ๋ก๋œ ํด๋ผ์ด์–ธํŠธ ์•„์ด๋””
            client-name: oauth2-client-app # ํด๋ผ์ด์–ธํŠธ ์ด๋ฆ„
            client-secret: tynI8eYUw4H1fJYxwLQ36XhFC1Ge1w1x # ์„œ๋น„์Šค ๊ณต๊ธ‰์ž์— ๋“ฑ๋ก๋œ ํด๋ผ์ด์–ธํŠธ ๋น„๋นŒ๋ฒˆํ˜ธ
            redirect-uri: http://localhost:8081/login/oauth2/code/keycloak # ์ธ๊ฐ€์„œ๋ฒ„์—์„œ ๊ถŒํ•œ ์ฝ”๋“œ ๋ถ€์—ฌ ํ›„ ํด๋ผ์ด์–ธํŠธ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธํ•˜๋Š” ์œ„์น˜
            clientAuthenticationMethod: client_secret_post # ํด๋ผ์ด์–ธํŠธ ์ž๊ฒฉ์ฆ๋ช… ์ „์†ก๋ฐฉ์‹
            scope: openid,email # ๋ฆฌ์†Œ์Šค์— ์ ‘๊ทผ ์ œํ•œ ๋ฒ”์œ„
        provider: ## ๊ณต๊ธ‰์ž ์„ค์ •
          keycloak:
            authorization-uri: http://localhost:8080/realms/oauth2/protocol/openid-connect/auth # OAuth 2.0 ๊ถŒํ•œ ์ฝ”๋“œ ๋ถ€์—ฌ ์—”๋“œ ํฌ์ธํŠธ
            issuer-uri: http://localhost:8080/realms/oauth2 # ์„œ๋น„์Šค ๊ณต๊ธ‰์ž ์œ„์น˜
            jwk-set-uri: http://localhost:8080/realms/oauth2/protocol/openid-connect/certs # OAuth 2.0 JwkSetUri ์—”๋“œ ํฌ์ธํŠธ
            token-uri: http://localhost:8080/realms/oauth2/protocol/openid-connect/token # OAuth 2.0 ํ† ํฐ ์—”๋“œ ํฌ์ธํŠธ
            user-info-uri: http://localhost:8080/realms/oauth2/protocol/openid-connect/userinfo # OAuth 2.0 UserInfo ์—”๋“œ ํฌ์ธํŠธ
            user-name-attribute: preferred_username # OAuth 2.0 ์‚ฌ์šฉ์ž๋ช…์„ ์ถ”์ถœํ•˜๋Š” ํด๋ ˆ์ž„๋ช…

OAuth2ClientProperties (prefix = โ€œspring.security.oauth2.clientโ€œ)

image

  • Registration ์€ ์ธ๊ฐ€ ์„œ๋ฒ„์— ๋“ฑ๋ก๋œ ํด๋ผ์ด์–ธํŠธ ๋ฐ ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ ์ •๋ณด๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค
  • Provider ๋Š” ๊ณต๊ธ‰์ž์—์„œ ์ œ๊ณตํ•˜๋Š” ์—”๋“œํฌ์ธํŠธ ๋“ฑ์˜ ์ •๋ณด๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค
  • ํด๋ผ์ด์–ธํŠธ ๋ฐ ๊ณต๊ธ‰์ž์˜ ์ •๋ณด๋ฅผ registration / provider ๋งต์— ์ €์žฅํ•˜๊ณ  ์ธ๊ฐ€์„œ๋ฒ„์™€์˜ ํ†ต์‹  ์‹œ ๊ฐ ํ•ญ๋ชฉ์„ ์ฐธ์กฐํ•˜์—ฌ ์‚ฌ์šฉํ•œ๋‹ค

ClientRegistration

๊ฐœ๋…

  • OAuth 2.0 ๋˜๋Š” OpenID Connect 1.0 Provider ์—์„œ ํด๋ผ์ด์–ธํŠธ์˜ ๋“ฑ๋ก ์ •๋ณด๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค
  • ClientRegistration ์€ OpenID Connect Provider์˜ ์„ค์ • ์—”๋“œํฌ์ธํŠธ๋‚˜ ์ธ๊ฐ€ ์„œ๋ฒ„์˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์—”๋“œํฌ์ธํŠธ๋ฅผ ์ฐพ์•„ ์ดˆ๊ธฐํ™”ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ClientRegistrations์˜ ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์•„๋ž˜ ์˜ˆ์ œ์ฒ˜๋Ÿผ ํŽธ๋ฆฌํ•˜๊ฒŒ ClientRegistration ์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค
    • ClientRegistration clientRegistration = ClientRegistrations.fromIssuerLocation(โ€œhttps://idp.example.com/issuerโ€).build();
    • ์œ„ ์ฝ”๋“œ๋Š” 200 ์‘๋‹ต์„ ๋ฐ›์„ ๋•Œ๊นŒ์ง€ https://idp.example.com/issuer/.well-known/openid-configuration, https://idp.example.com/.well-known/oauth-authorization-server ์— ์ฐจ๋ก€๋Œ€๋กœ ์งˆ์˜ํ•ด๋ณธ๋‹ค.

image

image

  • registrationId : ClientRegistration์„ ์‹๋ณ„ํ•  ์ˆ˜ ์žˆ๋Š” ์œ ๋‹ˆํฌํ•œ ID.
  • clientId : ํด๋ผ์ด์–ธํŠธ ์‹๋ณ„์ž.
  • clientSecret : ํด๋ผ์ด์–ธํŠธ secret.
  • clientAuthenticationMethod : provider์—์„œ ํด๋ผ์ด์–ธํŠธ๋ฅผ ์ธ์ฆํ•  ๋•Œ ์‚ฌ์šฉํ•  ๋ฉ”์†Œ๋“œ๋กœ์„œ basic, post, none (public ํด๋ผ์ด์–ธํŠธ) ์„ ์ง€์›ํ•œ๋‹ค.
  • authorizationGrantType : OAuth 2.0 ์ธ๊ฐ€ ํ”„๋ ˆ์ž„์›Œํฌ๋Š” ๋„ค ๊ฐ€์ง€ ๊ถŒํ•œ ๋ถ€์—ฌ ํƒ€์ž…์„ ์ •์˜ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ ์ง€์›ํ•˜๋Š” ๊ฐ’์€ authorization_code, implicit, client_credentials, password๋‹ค.
  • redirectUriTemplate : ํด๋ผ์ด์–ธํŠธ์— ๋“ฑ๋กํ•œ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ URL๋กœ, ์‚ฌ์šฉ์ž์˜ ์ธ์ฆ์œผ๋กœ ํด๋ผ์ด์–ธํŠธ์— ์ ‘๊ทผ ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•˜๊ณ  ๋‚˜๋ฉด, ์ธ๊ฐ€ ์„œ๋ฒ„๊ฐ€ ์ด URL๋กœ ์ตœ์ข… ์‚ฌ์šฉ์ž์˜ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ์‹œํ‚จ๋‹ค.
  • scopes : ์ธ๊ฐ€ ์š”์ฒญ ํ”Œ๋กœ์šฐ์—์„œ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์š”์ฒญํ•œ openid, ์ด๋ฉ”์ผ, ํ”„๋กœํ•„ ๋“ฑ์˜ scope.
  • clientName : ํด๋ผ์ด์–ธํŠธ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ์ด๋ฆ„์œผ๋กœ ์ž๋™ ์ƒ์„ฑ๋˜๋Š” ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€์—์„œ ๋…ธ์ถœํ•˜๋Š” ๋“ฑ์— ์‚ฌ์šฉํ•œ๋‹ค.
  • authorizationUri : ์ธ๊ฐ€ ์„œ๋ฒ„์˜ ์ธ๊ฐ€ ์—”๋“œํฌ์ธํŠธ URI.
  • tokenUri : ์ธ๊ฐ€ ์„œ๋ฒ„์˜ ํ† ํฐ ์—”๋“œํฌ์ธํŠธ URI.
  • jwkSetUri : ์ธ๊ฐ€ ์„œ๋ฒ„์—์„œ JSON ์›น ํ‚ค (JWK) ์…‹์„ ๊ฐ€์ ธ์˜ฌ ๋•Œ ์‚ฌ์šฉํ•  URI. ์ด ํ‚ค ์…‹์—” ID ํ† ํฐ์˜ JSON Web Signature (JWS) ๋ฅผ ๊ฒ€์ฆํ•  ๋•Œ ์‚ฌ์šฉํ•  ์•”ํ˜ธํ‚ค๊ฐ€ ์žˆ์œผ๋ฉฐ, UserInfo ์‘๋‹ต์„ ๊ฒ€์ฆํ•  ๋•Œ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
  • configurationMetadata : OpenID Provider ์„ค์ • ์ •๋ณด๋กœ์„œ application.properties ์—spring.security.oauth2.client.provider.[providerId].issuerUri๋ฅผ ์„ค์ •ํ–ˆ์„ ๋•Œ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
  • (userInfoEndpoint)uri : ์ธ์ฆ๋œ ์ตœ์ข… ์‚ฌ์šฉ์ž์˜ ํด๋ ˆ์ž„/์†์„ฑ์— ์ ‘๊ทผํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” UserInfo ์—”๋“œํฌ์ธํŠธ URI.
  • (userInfoEndpoint)authenticationMethod : UserInfo ์—”๋“œํฌ์ธํŠธ๋กœ ์•ก์„ธ์Šค ํ† ํฐ์„ ์ „์†กํ• ๋•Œ ์‚ฌ์šฉํ•  ์ธ์ฆ ๋ฉ”์†Œ๋“œ. header, form, query ๋ฅผ ์ง€์›ํ•œ๋‹ค.
  • userNameAttributeName : UserInfo ์‘๋‹ต์— ์žˆ๋Š” ์†์„ฑ ์ด๋ฆ„์œผ๋กœ, ์ตœ์ข… ์‚ฌ์šฉ์ž์˜ ์ด๋ฆ„์ด๋‚˜ ์‹๋ณ„์ž์— ์ ‘๊ทผํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค

image

CommonOAuth2Provider

image

  • OAuth 2.0 ๊ณต๊ธ‰์ž ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๋Š” ํด๋ž˜์Šค๋กœ์„œ ๊ธ€๋กœ๋ฒŒ ์„œ๋น„์Šค ์ œ๊ณต์ž ์ผ๋ถ€๋Š” ๊ธฐ๋ณธ์œผ๋กœ ์ œ๊ณต๋˜์–ด์ง„๋‹ค
  • Client ID ์™€ Client Secret ๋Š” ๋ณ„๋„๋กœ application.properties ์— ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค.
  • Naver ๋‚˜ Kakao ์™€ ๊ฐ™์€ ๊ตญ๋‚ด ๊ณต๊ธ‰์ž ์ •๋ณด๋Š” ์œ„์˜ ๋ชจ๋“  ํ•ญ๋ชฉ์„ ์ˆ˜๋™์œผ๋กœ ์ž‘์„ฑํ•ด์„œ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค
  • ํด๋ผ์ด์–ธํŠธ ๊ธฐ์ค€์ธ Registration ํ•ญ๋ชฉ๊ณผ ์„œ๋น„์Šค ์ œ๊ณต์ž ๊ธฐ์ค€์ธ Provider ํ•ญ๋ชฉ์œผ๋กœ ๊ตฌ๋ถ„ํ•˜์—ฌ ์„ค์ •ํ•œ๋‹ค
  • application.properties ๊ฐ€ ์•„๋‹Œ Java Config ๋ฐฉ์‹์œผ๋กœ ClientRegistration ๋“ฑ๋ก์„ ์„ค์ • ํ•  ์ˆ˜ ์žˆ๋‹ค
  • ClientRegistration ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ๋นŒ๋” ํด๋ž˜์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค

ClientRegistrationRepository

๊ฐœ๋…

  • ClientRegistrationRepository ๋Š” OAuth 2.0 & OpenID Connect 1.0 ์˜ ClientRegistration ์ €์žฅ์†Œ ์—ญํ• ์„ ํ•œ๋‹ค.
  • ํด๋ผ์ด์–ธํŠธ ๋“ฑ๋ก ์ •๋ณด๋Š” ๊ถ๊ทน์ ์œผ๋กœ ์ธ๊ฐ€ ์„œ๋ฒ„๊ฐ€ ์ €์žฅํ•˜๊ณ  ๊ด€๋ฆฌํ•˜๋Š”๋ฐ ์ด ๋ ˆํฌ์ง€ํ† ๋ฆฌ๋Š” ์ธ๊ฐ€ ์„œ๋ฒ„์— ์ผ์ฐจ์ ์œผ๋กœ ์ €์žฅ๋œ ํด๋ผ์ด์–ธํŠธ ๋“ฑ๋ก ์ •๋ณด์˜ ์ผ๋ถ€๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค.
  • ์Šคํ”„๋ง ๋ถ€ํŠธ 2.X ์ž๋™ ์„ค์ •์€ spring.security.oauth2.client.registration.[registrationId] ํ•˜์œ„ ํ”„๋กœํผํ‹ฐ๋ฅผ ClientRegistration ์ธ์Šคํ„ด์Šค์— ๋ฐ”์ธ๋”ฉํ•˜๋ฉฐ, ๊ฐ ClientRegistration๊ฐ์ฒด๋ฅผ ClientRegistrationRepository ์•ˆ์— ๊ตฌ์„ฑํ•œ๋‹ค.
  • ClientRegistrationRepository ์˜ ๋””ํดํŠธ ๊ตฌํ˜„์ฒด๋Š” InMemoryClientRegistrationRepository ๋‹ค.
  • ์ž๋™ ์„ค์ •์„ ์‚ฌ์šฉํ•˜๋ฉด ClientRegistrationRepository ๋„ ApplicationContext ๋‚ด @Bean ์œผ๋กœ ๋“ฑ๋กํ•˜๋ฏ€๋กœ ํ•„์š”ํ•˜๋‹ค๋ฉด ์›ํ•˜๋Š” ๊ณณ์— ์˜์กด์„ฑ์„ ์ฃผ์ž…ํ•  ์ˆ˜ ์žˆ๋‹ค.

    • ์˜์กด์„ฑ ์ฃผ์ž… ์˜ˆ์‹œ

      @RestController
      public class IndexPageController {
      
          @Autowired
          private ClientRegistrationRepository clientRegistrationRepository;
      
          @GetMapping("/")
          public String index() {
              ClientRegistration clientRegistration =
                  this.clientRegistrationRepository.findByRegistrationId("keycloak");
              ...
      
              return "index";
          }
      
      }
      

ClientRegistration / ClientRegistrationRepository ๋นˆ ๋“ฑ๋กํ•˜๊ธฐ

@Bean
public ClientRegistrationRepository clientRegistrationRepository() {
	return new InMemoryClientRegistrationRepository(this. keycloakClientRegistration());
}

private ClientRegistration keycloakClientRegistration() {
	return ClientRegistration.withRegistrationId("keycloak")
		.clientId("keycloak-client-id")
		.clientSecret(" keycloak-client-secret")
		.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
		.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
		.redirectUriTemplate("{baseUrl}/login/oauth2/code/{registrationId}")
		.scope("openid", "profile", "email", "address", "phone")
		.authorizationUri("http://localhost:8080/realms/oauth2")
		.tokenUri("http://localhost:8080/realms/oauth2/token")
		.userInfoUri("http://localhost:8080/realms/oauth2/userinfo")
		.userNameAttributeName(IdTokenClaimNames.SUB)
		.jwkSetUri("http://localhost:8080/realms/oauth2/certs")
		.clientName(โ€œKeycloak")
		.build();
}

AuthenticationEntryPoint

image

์ž๋™์„ค์ •์— ์˜ํ•œ ์ดˆ๊ธฐํ™” ๊ณผ์ •

OAuth2ImportSelector

image

OAuth2ClientAutoConfiguration

image

์นดํ…Œ๊ณ ๋ฆฌ:

์—…๋ฐ์ดํŠธ:

๋Œ“๊ธ€๋‚จ๊ธฐ๊ธฐ