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), 즉 실제 인증에 맞게 데이터를 재구성한다.
'Framework > Spring' 카테고리의 다른 글
[SAML 2.0] Security Assertion Markup Language (0) | 2024.12.18 |
---|---|
[OAuth2] 기본적인 로그인 연동은 이미 스프링에서 구현해놓았다? (0) | 2024.12.08 |
[OAuth2] accessToken에 이어서 왜 refreshToken 까지 필요할까? (1) | 2024.11.30 |
[Spring Security] HttpBasic 방식을 비활성화 하는 이유 (0) | 2024.11.27 |
[Spring Security] HttpSecurity 객체와 필터 체이닝 (1) | 2024.11.26 |