OAuth2 에 대하여.

간단하게 말하면, 인증과 인가를 처리하기 위한 표준 규칙을 일컫는다.

 

다음 포스팅에서 더 자세하게 알 수 있다.

https://jinn-o.tistory.com/309

 

[OAuth2] accessToken에 이어서 왜 refreshToken 까지 필요할까?

OAuth2.0 이 뭐에요 ?Open Authorization의 약자로, 웹 애플리케이션이나 모바일 앱 등에서 인증(Authentication)과 인가(Authorization)를 처리하기 위한 표준 프로토콜이다. 표준이기 때문에, 사용자가 직접 자

jinn-o.tistory.com

 

 

스프링이 OAuth2 라는 RFC6749 규약을 지키는 방법

1. application.yml 을 작성한다.

spring:
  security:
    oauth2:
      client:
        registration: # Client 설정
          keycloak:
            authorizationGrantType: authorization_code      # OAuth 2.0 권한 부여 타입
            clientId: oauth2-client-app                      # 서비스 공급자에 등록된 클라이언트 아이디
            clientName: oauth2-client-app                    # 클라이언트 이름
            clientSecret: ********************************   # 서비스 공급자에 등록된 클라이언트 비밀번호
            redirectUrl: http://localhost:8080/login/oauth2/code/keycloak  # 인가서버에서 권한 코드 부여 후 클라이언트로 리다이렉트 하는 위치
            clientAuthenticationMethod: client_secret_post    # 클라이언트 자격증명 전송방식(basic, post, none)
            scope: openid, email, profile
        provider: # 공급자 설정
          keycloak:
            authorizationUri: http://localhost:8081/realms/oauth2/protocol/openid-connect/auth # Authorization code 부여 엔드 포인트
            issuerUri: http://localhost:8081/realms/oauth2 # 서비스 공급자 위치 (issuer)
            jwkSetUri: http://localhost:8081/realms/oauth2/protocol/openid-connect/certs # JwkSetUri 엔드 포인트 (JWT 를 암호화하기 위한 검증)
            tokenUri: http://localhost:8081/realms/oauth2/protocol/openid-connect/token # AccessToken 등 토큰 요청할때 엔드 포인트
            userInfoUri: http://localhost:8081/realms/oauth2/protocol/openid-connect/userinfo # 사용자 정보 가져오기
            userNameAttribute: preferred_username # 사용자명을 추출하는 클레임명

 

 

이제 이 application.yml 에 정의한 속성들을 다음 OAuth2ClientProperties 객체로 매핑시켜야 한다.

@ConfigurationProperties(
    prefix = "spring.security.oauth2.client"
)
public class OAuth2ClientProperties implements InitializingBean {
    private final Map<String, Provider> provider = new HashMap();
    private final Map<String, Registration> registration = new HashMap();

    ...

    public static class Registration {
        private String provider;
        private String clientId;
        private String clientSecret;
        private String clientAuthenticationMethod;
        private String authorizationGrantType;
        private String redirectUri;
        private Set<String> scope;
        private String clientName;

        ...
        
        (getters and setters)
    }

    public static class Provider {
        private String authorizationUri;
        private String tokenUri;
        private String userInfoUri;
        private String userInfoAuthenticationMethod;
        private String userNameAttribute;
        private String jwkSetUri;
        private String issuerUri;

        ...
        
        (getters and setters)
    }
}

 

 

객체를 매핑시키는 방법, OAuth2ClientPropertiesRegistrationAdapter

public final class OAuth2ClientPropertiesRegistrationAdapter {

    public static Map<String, ClientRegistration> getClientRegistrations(OAuth2ClientProperties properties) {
        Map<String, ClientRegistration> registrations = new HashMap<>();
        properties.getRegistration().forEach((registrationId, registration) -> {
            OAuth2ClientProperties.Provider provider = properties.getProvider().get(registrationId);
            registrations.put(registrationId, createClientRegistration(registrationId, registration, provider));
        });
        return registrations;
    }

    private static ClientRegistration createClientRegistration(String registrationId,
                                                               OAuth2ClientProperties.Registration registration,
                                                               OAuth2ClientProperties.Provider provider) {
        return ClientRegistration.withRegistrationId(registrationId)
                .clientId(registration.getClientId())
                .clientSecret(registration.getClientSecret())
                .authorizationUri(provider.getAuthorizationUri())
                .tokenUri(provider.getTokenUri())
                .userInfoUri(provider.getUserInfoUri())
                .redirectUri(registration.getRedirectUri())
                .scope(registration.getScope())
                .build();
    }
}

 

 

코드를 보면 Properties 객체를 ClientRegistration 객체로 변환하고 있는 것을 확인할 수 있다.

 

최종 목적은 ClientRegistration 객체를 반환하는 것.

public final class ClientRegistration {

    private final String registrationId;
    private final String clientId;
    private final String clientSecret;
    private final String authorizationUri;
    private final String tokenUri;
    private final String redirectUri;
    private final Set<String> scopes;

    // Builder 패턴을 통해 객체 생성
    public static Builder withRegistrationId(String registrationId) {
        return new Builder(registrationId);
    }

    public static class Builder {
        private String registrationId;
        private String clientId;
        private String clientSecret;
        private String authorizationUri;
        private String tokenUri;
        private String redirectUri;
        private Set<String> scopes;

        public Builder clientId(String clientId) {
            this.clientId = clientId;
            return this;
        }

        public ClientRegistration build() {
            return new ClientRegistration(this);
        }
    }
}

 

 

OAuth2ClientProperties 객체와 ClientRegistration 객체가 나뉘어 있는 이유.

  • 구성과 실행의 역할 분리하기 위해서
    • OAuth2ClientProperties: 구성 데이터를 관리하고
    • ClientRegistration: 실행 시 최적화된 정보 제공한다.
  • 불변 객체가 필요했기 때문에
    • 런타임 중 데이터 변경 없이 안정적인 동작을 보장해야 했기에, ClientRegistration 객체로 굳혀놓는 것이다.
  • Spring Boot와 Spring Security의 역할 분리:
    • Spring Boot: 설정 관리(OAuth2ClientProperties), 즉 application.yml 에서 원시 데이터 매칭한다.
    • Spring Security: 런타임에서 클라이언트 인증 처리(ClientRegistration), 즉 실제 인증에 맞게 데이터를 재구성한다.

+ Recent posts