1. 개요
Cloud Foundry 사용자 계정 및 인증(CF UAA)은 ID 관리 및 권한 부여 서비스입니다. 보다 정확하게는 인증을 허용하고 클라이언트 애플리케이션에 토큰을 발행하는 OAuth 2.0 제공자입니다.
이 사용방법(예제)에서는 CF UAA 서버 설정의 기본 사항을 다룹니다. 그런 다음 리소스 서버 애플리케이션을 보호하기 위해 이를 사용하는 방법을 살펴보겠습니다.
그러나 그 전에 OAuth 2.0 인증 프레임워크 에서 UAA의 역할을 명확히 합시다 .
2. Cloud Foundry UAA 및 OAuth 2.0
UAA가 OAuth 2.0 사양과 어떤 관련이 있는지 이해하는 것으로 시작하겠습니다.
OAuth 2.0 사양은 리소스 소유자, 리소스 서버, 클라이언트 및 권한 부여 서버와 같이 서로 연결할 수 있는 네 가지 참가자를 정의합니다.
UAA는 OAuth 2.0 Provider로서 인증 서버 역할을 합니다 . 즉, 주요 목표는 클라이언트 응용 프로그램에 대한 액세스 토큰을 발급하고 리소스 서버 에 대한 이러한 토큰의 유효성을 검사하는 것 입니다 .
이러한 참가자의 상호 작용을 허용하려면 먼저 UAA 서버를 설정한 다음 두 개의 애플리케이션을 더 구현해야 합니다. 하나는 클라이언트로 다른 하나는 리소스 서버로.
클라이언트와 함께 authorization_code 권한 부여 흐름을 사용합니다 . 그리고 리소스 서버와 함께 Bearer 토큰 인증을 사용합니다. 보다 안전하고 효율적인 핸드셰이크를 위해 서명된 JWT를 액세스 토큰 으로 사용합니다 .
3. UAA 서버 설정
먼저 UAA를 설치하고 일부 데모 데이터로 채웁니다.
일단 설치되면 webappclient라는 클라이언트 애플리케이션을 등록합니다 . 그런 다음 resource.read 및 resource.write라는 두 가지 역할을 가진 appuser 라는 사용자를 만듭니다 .
3.1. 설치
UAA는 호환되는 모든 서블릿 컨테이너에서 실행할 수 있는 Java 웹 애플리케이션입니다. 이 사용방법(예제)에서는 Tomcat을 사용 합니다 .
계속해서 UAA war를 다운로드 하고 Tomcat 배포에 저장해 보겠습니다 .
wget -O $CATALINA_HOME/webapps/uaa.war \
https://search.maven.org/remotecontent?filepath=org/cloudfoundry/identity/cloudfoundry-identity-uaa/4.27.0/cloudfoundry-identity-uaa-4.27.0.war
하지만 시작하기 전에 데이터 소스와 JWS 키 쌍을 구성해야 합니다.
3.2. 필수 구성
기본적으로 UAA는 해당 클래스 경로의 uaa.yml 에서 구성을 읽습니다 . 그러나 방금 war 파일을 다운로드했으므로 UAA에 파일 시스템의 사용자 지정 위치를 알려주는 것이 좋습니다.
UAA_CONFIG_PATH 속성을 설정하여 이를 수행할 수 있습니다 .
export UAA_CONFIG_PATH=~/.uaa
또는 CLOUD_FOUNDRY_CONFIG_PATH를 설정할 수 있습니다. 또는 UAA_CONFIG_URL을 사용하여 원격 위치를 지정할 수 있습니다.
그런 다음 UAA의 필수 구성을 구성 경로에 복사할 수 있습니다.
wget -qO- https://raw.githubusercontent.com/cloudfoundry/uaa/4.27.0/uaa/src/main/resources/required_configuration.yml \
> $UAA_CONFIG_PATH/uaa.yml
잠시 후 교체할 것이기 때문에 마지막 세 줄을 삭제하고 있습니다.
3.3. 데이터 소스 구성
이제 UAA가 클라이언트에 대한 정보를 저장할 데이터 소스를 구성해 보겠습니다.
이 사용방법(예제)의 목적을 위해 HSQLDB를 사용합니다.
export SPRING_PROFILES="default,hsqldb"
물론 이것은 Spring Boot 애플리케이션이므로 uaa.yml 에서 이것을 spring.profiles 속성 으로 지정할 수도 있습니다 .
3.4. JWS 키 쌍 구성
우리는 JWT를 사용하고 있기 때문에 UAA는 UAA가 발행하는 각 JWT에 서명하기 위해 개인 키가 있어야 합니다.
OpenSSL은 다음과 같이 간단하게 만듭니다.
openssl genrsa -out signingkey.pem 2048
openssl rsa -in signingkey.pem -pubout -out verificationkey.pem
인증 서버는 개인 키로 JWT에 서명 하고 클라이언트 및 리소스 서버는 공개 키로 해당 서명을 확인합니다 .
JWT_TOKEN_SIGNING_KEY 및 JWT_TOKEN_VERIFICATION_KEY 로 내보냅니다 .
export JWT_TOKEN_SIGNING_KEY=$(cat signingkey.pem)
export JWT_TOKEN_VERIFICATION_KEY=$(cat verificationkey.pem)
다시 말하지만 jwt.token.signing-key 및 jwt.token.verification-key 속성을 통해 uaa.yml 에서 이를 지정할 수 있습니다 .
3.5. UAA 시작하기
마지막으로 시작하겠습니다.
$CATALINA_HOME/bin/catalina.sh run
이 시점에서 http://localhost:8080/uaa 에서 사용할 수 있는 작동 중인 UAA 서버가 있어야 합니다 .
http://localhost:8080/uaa/info 로 이동하면 기본 시작 정보가 표시됩니다.
3.6. UAA 명령줄 클라이언트 설치
CF UAA 명령줄 클라이언트는 UAA 관리를 위한 주요 도구이지만 이를 사용하려면 먼저 Ruby를 설치 해야 합니다 .
sudo apt install rubygems
gem install cf-uaac
그런 다음 실행 중인 UAA 인스턴스를 가리키도록 uaac를 구성할 수 있습니다.
uaac target http://localhost:8080/uaa
명령줄 클라이언트를 사용하지 않으려면 물론 UAA의 HTTP 클라이언트를 사용할 수 있습니다.
3.7. UAAC를 사용하여 클라이언트 및 사용자 채우기
이제 uaac를 설치 했으므로 UAA를 일부 데모 데이터로 채웁니다. 최소한 client , user , resource.read 및 resource.write 그룹이 필요합니다.
따라서 관리를 수행하려면 자체 인증이 필요합니다. 다른 클라이언트, 사용자 및 그룹을 만들 수 있는 권한이 있는 UAA와 함께 제공되는 기본 관리자를 선택합니다 .
uaac token client get admin -s adminsecret
(물론 배송 전에 oauth-clients.xml 파일을 통해 이 계정을 변경해야 합니다 !)
기본적 으로 이 명령 을 다음 과 같이 읽을 수 있습니다 .
모두 잘 진행되면 성공 메시지가 표시됩니다.
Successfully fetched token via client credentials grant.
토큰은 uaac 의 상태 에 저장됩니다 .
이제 admin 으로 작동하여 client add를 사용하여 webappclient 라는 클라이언트를 등록할 수 있습니다 .
uaac client add webappclient -s webappclientsecret \
--name WebAppClient \
--scope resource.read,resource.write,openid,profile,email,address,phone \
--authorized_grant_types authorization_code,refresh_token,client_credentials,password \
--authorities uaa.resource \
--redirect_uri http://localhost:8081/login/oauth2/code/uaa
또한 user add를 사용하여 appuser 라는 사용자를 등록할 수 있습니다.
uaac user add appuser -p appusersecret --emails appuser@acme.com
다음으로 group add를 사용하여 두 개의 그룹( resource.read 및 resource.write ) 을 추가합니다 .
uaac group add resource.read
uaac group add resource.write
마지막으로 구성원 추가를 사용하여 이러한 그룹을 appuser 에 할당합니다 .
uaac member add resource.read appuser
uaac member add resource.write appuser
휴! 따라서 지금까지 수행한 작업은 다음과 같습니다.
- UAA 설치 및 구성
- 설치된 uaac
- 데모 클라이언트, 사용자 및 그룹 추가
따라서 이러한 정보를 염두에 두고 다음 단계로 넘어갑시다.
4. OAuth 2.0 클라이언트
이 섹션에서는 Spring Boot를 사용하여 OAuth 2.0 클라이언트 애플리케이션을 만듭니다 .
4.1. 애플리케이션 설정
Spring Initializr 에 액세스하고 Spring Boot 웹 애플리케이션을 생성하는 것으로 시작하겠습니다 . 웹 및 OAuth2 클라이언트 구성 요소 만 선택합니다 .
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
이 예에서는 Spring Boot 버전 2.1.3을 사용했습니다 .
다음으로 클라이언트인 webapp client 를 등록해야 합니다 .
간단히 말해서 앱에 client-id, client-secret 및 UAA의 issuer-uri 를 제공해야 합니다 . 또한 이 클라이언트가 사용자가 부여하기를 원하는 OAuth 2.0 범위를 지정합니다.
#registration
spring.security.oauth2.client.registration.uaa.client-id=webappclient
spring.security.oauth2.client.registration.uaa.client-secret=webappclientsecret
spring.security.oauth2.client.registration.uaa.scope=resource.read,resource.write,openid,profile
#provider
spring.security.oauth2.client.provider.uaa.issuer-uri=http://localhost:8080/uaa/oauth/token
이러한 속성에 대한 자세한 내용은 등록 및 Provider 빈 에 대한 Java 문서를 볼 수 있습니다 .
UAA에 이미 포트 8080을 사용하고 있으므로 8081에서 실행해 보겠습니다.
server.port=8081
4.2. 로그인
이제 /login 경로 에 액세스하면 등록된 모든 클라이언트 List이 있어야 합니다. 우리의 경우에는 등록된 클라이언트가 하나만 있습니다.
링크를 클릭하면 UAA 로그인 페이지로 리디렉션됩니다.
여기에서 appuser/appusersecret 로 로그인하겠습니다 .
양식을 제출하면 사용자가 클라이언트에 대한 액세스를 승인하거나 거부할 수 있는 승인 양식으로 리디렉션됩니다.
그런 다음 사용자는 원하는 권한을 부여할 수 있습니다. 여기서는 resource:write를 제외한 모든 항목을 선택하겠습니다 .
사용자가 확인하는 것은 결과 액세스 토큰의 범위가 됩니다.
이를 증명하기 위해 인덱스 경로 http://localhost:8081 에 표시된 토큰을 복사 하고 JWT 디버거를 사용하여 디코딩 할 수 있습니다 . 승인 페이지에서 확인한 범위가 표시되어야 합니다.
{
"jti": "f228d8d7486942089ff7b892c796d3ac",
"sub": "0e6101d8-d14b-49c5-8c33-fc12d8d1cc7d",
"scope": [
"resource.read",
"openid",
"profile"
],
"client_id": "webappclient"
// more claims
}
클라이언트 애플리케이션이 이 토큰을 수신하면 사용자를 인증할 수 있으며 사용자는 앱에 액세스할 수 있습니다.
이제 데이터를 표시하지 않는 앱은 그다지 유용하지 않으므로 다음 단계는 사용자 데이터가 있는 리소스 서버를 세우고 클라이언트를 연결하는 것입니다.
완료된 리소스 서버에는 두 개의 보호된 API가 있습니다. 하나는 resource.read 범위가 필요하고 다른 하나는 resource.write가 필요합니다.
우리가 볼 수 있는 것은 우리가 부여한 범위를 사용하는 클라이언트가 읽기 API를 호출할 수 있지만 쓸 수는 없다는 것입니다 .
5. 리소스 서버
리소스 서버는 사용자의 보호된 리소스를 호스팅합니다.
Authorization 헤더를 통해 그리고 Authorization Server와 협의하여 클라이언트를 인증합니다 . 여기서는 UAA입니다.
5.1. 애플리케이션 설정
리소스 서버를 생성하기 위해 Spring Initializr를 다시 사용하여 Spring Boot 웹 애플리케이션을 생성합니다. 이번에는 웹 및 OAuth2 리소스 서버 구성 요소를 선택합니다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
클라이언트 애플리케이션과 마찬가지로 Spring Boot 버전 2.1.3을 사용하고 있습니다.
다음 단계는 application.properties 파일에서 실행 중인 CF UAA의 위치를 나타내는 것입니다.
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8080/uaa/oauth/token
물론 여기서도 새 포트를 선택합시다. 8082는 잘 작동합니다.
server.port=8082
그리고 그게 다야! 작동하는 리소스 서버가 있어야 하며 기본적으로 모든 요청에는 Authorization 헤더에 유효한 액세스 토큰이 필요합니다.
5.2. 리소스 서버 API 보호
다음으로 보호할 가치가 있는 몇 가지 Endpoints을 추가해 보겠습니다.
두 개의 엔드포인트가 있는 RestController를 추가합니다 . 하나는 resource.read 범위 를 가진 사용자에게 권한이 부여되고 다른 하나는 resource.write 범위를 가진 사용자에게 권한이 부여됩니다.
@GetMapping("/read")
public String read(Principal principal) {
return "Hello write: " + principal.getName();
}
@GetMapping("/write")
public String write(Principal principal) {
return "Hello write: " + principal.getName();
}
다음으로 기본 Spring Boot 구성을 재정의하여 두 리소스를 보호합니다.
@EnableWebSecurity
public class OAuth2ResourceServerSecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/read/**")
.hasAuthority("SCOPE_resource.read")
.antMatchers("/write/**")
.hasAuthority("SCOPE_resource.write")
.anyRequest()
.authenticated()
.and()
.oauth2ResourceServer()
.jwt();
return http.build();
}
}
액세스 토큰에 제공된 범위는 Spring Security GrantedAuthority 로 변환될 때 SCOPE_ 접두사가 붙습니다 .
5.3. 클라이언트에서 보호된 리소스 요청
클라이언트 애플리케이션에서 RestTemplate을 사용하여 두 개의 보호된 리소스를 호출합니다. 요청하기 전에 컨텍스트에서 액세스 토큰을 검색하고 Authorization 헤더 에 추가합니다 .
private String callResourceServer(OAuth2AuthenticationToken authenticationToken, String url) {
OAuth2AuthorizedClient oAuth2AuthorizedClient = this.authorizedClientService.
loadAuthorizedClient(authenticationToken.getAuthorizedClientRegistrationId(),
authenticationToken.getName());
OAuth2AccessToken oAuth2AccessToken = oAuth2AuthorizedClient.getAccessToken();
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Bearer " + oAuth2AccessToken.getTokenValue());
// call resource endpoint
return response;
}
그러나 RestTemplate 대신 WebClient를 사용 하면 이 상용구를 제거할 수 있습니다 .
그런 다음 리소스 서버 엔드포인트에 대한 두 가지 호출을 추가합니다.
@GetMapping("/read")
public String read(OAuth2AuthenticationToken authenticationToken) {
String url = remoteResourceServer + "/read";
return callResourceServer(authenticationToken, url);
}
@GetMapping("/write")
public String write(OAuth2AuthenticationToken authenticationToken) {
String url = remoteResourceServer + "/write";
return callResourceServer(authenticationToken, url);
}
예상대로 / read API 호출은 성공하지만 / write API 호출은 성공하지 않습니다. HTTP 상태 403은 사용자가 인증되지 않았음을 알려줍니다.
6. 결론
이 기사에서는 OAuth 2.0 권한 부여 서버인 UAA의 기본 기반인 OAuth 2.0에 대한 간략한 개요부터 시작했습니다. 그런 다음 클라이언트에 대한 액세스 토큰을 발급하고 리소스 서버 애플리케이션을 보호하도록 구성했습니다.
예제의 전체 소스 코드는 Github에서 사용할 수 있습니다 .