지금까지는 Keycloak 인증 서버를
성공적으로 구축하였고
Postman 애플리케이션을 활용하여
액세스 토큰을 얻을 수 있는
클라이언트 정보도 생성하였습니다
이번 강의에서는 리소스 서버를 구축해 보겠습니다
지금도 EazyBank 백엔드 애플리케이션이 있지만
이는 리소스 서버로 직접 기능하기는 어렵습니다
현재 Spring Boot 애플리케이션 내부에서는
데이터베이스와 같은 저장 시스템의 도움으로
인증이 이루어지고 있기 때문입니다
이런 인증 방식을 바꾸기 위해서는
SpringBoot 백엔드 애플리케이션의 내부에서
일부 설정을 바꾸고
dependencies도 변경하여
리소스 서버로 기능할 수 있도록 바꾸어야 합니다
그럼 변경 작업을 시작하겠습니다
제 작업 공간 내에서 지금 제가 할 일은
이 springsecsection_10 project를 선택하고
같은 폴더 내에 복제본을 만들 것입니다
그리고 이 복제본은
springsecsection_13으로 이름을 변경합니다
지금 강의는 섹션 13에 있기 때문이죠
섹션 11에서 저희는
OAUTH2의 이론적인 부분을 모두 다루었습니다
섹션 12에서는 이 프로젝트를 만들었습니다
springsecOAUTH2GitHub라고 되어 있습니다
그래서 이번 프로젝트는
springsecsection_13이라고 이름 붙였습니다
폴더를 생성하고 나면
pom.xml을 열도록 합니다
pom.xml 안에 있는
Artifact ID를 springsecsection_13으로 고칩니다
파일명도 똑같이 수정합니다
그 다음 pom.xml은 닫습니다
다음으로 target 폴더에 가서
내용을 모두 삭제합니다
그러고 나면 Maven 프로젝트를 intelliJ IDEA로 불러옵니다
IDE를 열고 나서 File>New>
Module from existing sources를 클릭하고
springsecsection_13 내부에 있는
pom.xml을 찾습니다
pom.xml을 선택합니다
이걸로 InteliJ IDE에
새로운 Maven 프로젝트를 불러왔습니다
이 Maven 프로젝트에서
가장 먼저 변경하고 싶은 점은
pom.xml에 새로운 dependency를 추가하는 것입니다
spring-boot-starter-security dependency 바로 다음에
새로운 dependency를 붙여 넣습니다
dependency의 이름은
spring-boot-starter-oauth2-resource-server입니다
이 dependency는 SpringBoot 웹 애플리케이션을
리소스 서버로 만들어 줄 것입니다
이걸로 제 웹 퍼블리케이션은
리소스 서버처럼 행동할 것이며
Spring Security 프레임워크를 이용하여
제가 설정한 보안 기준에 따라
접속하려는 애플리케이션에게
항상 액세스 토큰을 요구할 겁니다
이 dependency가 추가되었으니
Maven 변경 사항을 반영시켜
제 로컬 Maven 저장소에
이 dependency를 로드하겠습니다
pom.xml 내용을 모두 변경했다면
이제 저희는
ProjectSecurityConfig 클래스를 열어야 합니다
지금은 이 클래스 안을 보시면
상당히 많은 필터가 있어
웹 애플리케이션 내부에
JWT 토큰을 생성하고 인증하는 역할을 합니다
하지만 저희는 OAuth2 프레임워크와
Keycloak 인증 서버를 활용하기로 했기 때문에
JWT 토큰을 저희가 생성할 필요가 없습니다
그렇기 때문에 지금 여기 있는 필터들은
CsrfCookieFilter를 제외하고 모두 삭제합니다
이 코드를 모두 선택하고
삭제를 누릅니다
그 다음에는
저희가 가지고 있는 필터 패키지로 갑니다
필터 패키지 내부에서 할 일은
쿠키 필터를 제외하고
모든 필터를 삭제하는 겁니다
모두 삭제해 줍니다
이 작업을 마쳤으면
다음으로 청소할 것은
EazyBankUsernamePasswordAuthenticationProvider입니다
이걸 삭제해야 하는 이유는
이 클래스로는 데이터베이스의 도움을 받아
엔드 유저를 인증하게 되는데
앞으로 저희는 인증을 할 때
데이터베이스의 도움을 받지 않을 것이기 때문입니다
그 대신 Keycloak 인증 서버를 활용합니다
그러므로 이 인증 provider도
삭제합니다
다음으로는
ProjectSecurityConfig class로 가 보면
여기에는 BCryptPasswordEncoder bean이 있습니다
bean도 삭제합니다
삭제하는 이유도 마찬가지입니다
우리는 이제 비밀번호를 보관하거나
인증하는 일을 하지 않기 때문입니다
그 역할은
Keycloak 인증 서버가 맡습니다
그러므로 이 bean은 삭제합니다
다음으로 SecurityConstants 같은
상수들이 있습니다
JWT token 생성과 인증 과정에서
저희가 사용했던 상수 값들입니다
그 filter들을 삭제했으므로
SecurityConstants도 삭제합니다
파일을 모두 정리했으니
이제 config 패키지 내부에 새로운 클래스를 생성합니다
New>Java Class를 선택합니다
클래스 이름은 KeycloakRoleConverter로 할 겁니다
제가 이 KeycloakRoleConverter 내부에
작성할 로직은 Keycloak 인증 서버가
액세스 토큰을 반환하도록 만들 겁니다
액세스 토큰에 들어 있는 정보는
클라이언트 애플리케이션의 역할이나
엔드 유저의 역할입니다
토큰으로부터 그 역할 정보
또는 인증 정보를 추출하여
Spring Security 프레임워크가 알아들을 수 있는 형식으로
변환할 수 있도록 해야 합니다
그래서 KeycloakRoleConverter가 필요합니다
KeycloakRoleConverter class 내부에는
Converter라는 이름의 인터페이스를 넣어 줘야 합니다
여기서 보이듯이
이 Converter는 JWT 토큰을
Keycloak 서버로부터 받아서
역할 정보를 추출한 다음
GrantedAuthority 목록으로 응답할 겁니다
SpringSecurity 프레임워크는
역할 정보나 인증 정보를
GrantedAuthority 객체 형식으로만 보내야
알아들을 수 있기 때문입니다
그래서 형식을 변환하는 역할을 Converter가 해 줄 겁니다
이 인터페이스를 상속시킬 때마다
메소드를 오버라이드해야 합니다
그 메소드를 붙여 넣어 봅시다
메소드를 붙여 넣습니다
여기 보이듯이 메소드의 이름은 convert이며
이 친구가 JWT 토큰을 받아 줄 것입니다
그리고 이 JWT 토큰으로부터
먼저 이름이 realm_access인
map 객체를 추출하려고 합니다
JWT에 대해 getClaims 함수를 호출하여
이름이 realm_access인 map 객체를
가져오고 싶다고 말합니다
그리고 getClaims로 추출한 객체는
이 realmAccess map에 할당합니다
만약 realmAccess map이 비었다면
비어 있는 ArrayList를 생성하게 됩니다
비어 있는 경우가 아니라면 그 다음에는
realmAccess map에 존재하는 모든 역할 정보를 가져오고 싶습니다
그러므로 이름이 roles인 객체를 반환하라고 명령합니다
이러면 JWT 토큰 내부에 존재하는
모든 역할 정보와 인증 정보를 반환합니다
그 정보를 얻고 나면 stream 메소드를 호출합니다
stream 메소드 다음에는 map 메소드을 호출합니다
map 메소드를 이용하면 Lambda 식을 작성해서
이 steam list에 존재하는 역할 하나하나에
저의 로직을 실행시킬 수 있습니다
이 map 안에 보면 Lambda 식이 있습니다
우선 제가 아까 받았던
역할 이름을 전부 가져다
ROLE_ 이라는 prefix를 이름 앞에 붙입니다
SpringSecurity 프레임워크는
역할 정보를 이용할 때마다
이 prefix를 요구할 것이기 때문입니다
이 로직을 실행했다면
map 메소드를 한 번 더 호출합니다
왜냐면 변경된 역할 이름을 가져다가
SimpleGrantedAuthority 객체로
변환하고 싶기 때문입니다
SimpleGrantedAuthority를 생성한 후
SimpleGrantedAuthorities 객체들을 모두 모아
리스트를 만듭니다
그리고는 이 method로 다시 돌아옵니다
이 프로그램이 아주 복잡해 보일 겁니다
이 코드를 이해하고 싶으시다면
Lambda 식에 대해 알고 계셔야 합니다
Lambda의 식과 stream들이 낯설다면
저의 강좌들 중에
Java 8의 기능 특히 Lambda에 대해
상세하게 설명한 강좌를 들으시면 좋을 것 같습니다
이제 역할 converter 클래스가 완성되었습니다
이제 저희가 할 일은
KeycloakRoleConverter의 환경설정을
ProjectSecurityConfig class 내부에서 해야 합니다
defaultSecurityFilterChain method 내부도 마찬가지입니다
CSRF와 관련된 부분을 변경하고
코드 두 줄을 붙여넣습니다
제가 지금 하고 있는 건
jwtAuthenticationConverter 객체를 만드는 일입니다
이 객체는
SpringSecurity 프레임워크에 이미 존재합니다
jwtAuthenticationConverter의 이 객체를 이용해
setJwtGrantedAuthoritiesConverter method를 호출하고
이 method에게
KeycloakRoleConverter 객체를 넘깁니다
무슨 일을 하고 있는 건지 잘 이해되실 것 같습니다
이 method를 이용하여 저희는
이 converter에 커스텀 로직을 반환하여
Keycloak 서버로부터 받은
JWT 액세스 토큰을 읽도록 한다는 겁니다
그리고 그 token을
GrantedAuthorities로 변환합니다
이 두 줄을 정의하고 나면
아래로 스크롤해서
현재 코드는 formLoginr과 httpBasic을 지원하고 있다는 걸
확인합니다
OAuth2 프레임워크를 활용할 거기 때문에
이 둘을 더 이상 지원할 필요가 없습니다
대신 oauth2ResourceServer method를 호출해야 합니다
앞으로는 SpringBoot 웹 애플리케이션이
리소스 서버로 기능할 것이기 때문이죠
그리고 이 리소스 서버는 JWT token을 활용하여
인증을 할 것입니다
또 이 JWT 메소드를 활용하여
jwtAuthenticationConverter라는 이름의
메소드를 호출합니다
그리고 이 jwtAuthenticationConverter에게
위에서 생성한 객체를 넘겨줘야 합니다
그러므로 jwtAuthenticationConverter method에
그 객체가 입력되도록 코딩합니다
이렇게 하면 저희는 SpringSecurity 프레임워크에게
SpringBoot 웹 애플리케이션이
OAuth2ResourceServer로 기능하며
JWT 액세스 토큰을 활용할 것이라고 알려주는 것입니다
또한 jwtAuthenticationConverter의 도움으로
저희는 액세스 토큰에 존재하는 역할 정보를
GrantedAuthorities로 변환할 수 있습니다
저희가 어떤 수정사항을 가한 건지
잘 이해하셨길 바랍니다
이걸로 저희는
ProjectSecurityConfig의 변경사항은 완료했습니다
변경사항을 저장하겠습니다
변경사항을 가한 후
AccountsController와 같은
Controller class로 가 보면
지금은 여기 보이듯이
controller 클래스들 내부의 메소드들은
ID 입력 파라메터를 받아들이고 있습니다
이러면 제 서드 파티 애플리케이션들은
제가 가진 계정 정보나 대출 정보를 알고 싶다면
ID 값을 넘겨야 한다는 건데
이건 말이 안 됩니다
왜나면 서드 파티 애플리케이션들은
SpringBoot 웹 애플리케이션 내부에서
관리되고 있는 ID 정보를 알 수가 없기 때문입니다.
그렇기 때문에 이 입력은
ID로부터 이메일로 바꿔줘야 합니다
이렇게 하면 서드 파티나 Postman에서는
엔드 유저의 이메일을 보내고
이메일을 이용하여 저희 쪽에서
계정 정보와 대출 정보를 반환하여
이들에게 줄 수 있게 됩니다
그렇기 때문에 저는 우선 AccountController 내부에 있는
int id를 삭제하고
입력을 String email로 바꿀 겁니다
그러면 이제 서드 파티 애플리케이션으로부터는
이메일을 입력으로 받게 됩니다
다음으로 저희는 사용자의 ID를 알아내서
계정 저장소로 보내야 합니다
그렇게 하기 위해
customerRepository bean을
이 controller에 추가하겠습니다
지금 저는 customerRepository bean을
이 controller 클래스에 Autowire하고 있습니다
다음으로 이 customerRepository를 이용하여
먼저 데이터베이스로부터
이메일을 이용해 클라이언트의 정보를 알아내야 합니다
아까처럼 controller 내부의
이 코드를 삭제하겠습니다
이 코드를 제거하고 나서는
새로운 코드를 붙여넣습니다
보이시듯이 먼저
저는 customerRepository 내부에서
findByEmail로부터 도움을 받아
클라이언트 정보를 찾으려고 하고 있습니다
이 메소드는 이메일을 입력으로 받습니다
어떤 이유로 주어진 이메일에 대응하는
클라이언트 정보가 없다면
null 객체가 반환됩니다
그러므로 메소드의 끝에
null 객체를 추가합니다
다른 controller에서도
비슷한 문제가 발생할 겁니다
다른 controller들처럼
여기서도 ID가 입력 파라메터로 되어 있습니다
강의에서는 생략할 것이지만
나머지 controller의 입력도 모두 이메일로 변경할 겁니다
이제 필요한 controller에는
예를 들어 BalanceController라던지
수정을 가했습니다
이제 입력을 이메일로 받을 겁니다
CardsController와
LoansController도 마찬가지입니다
이런 변경을 왜 해야 하는지는 이해하고 계실 것 같습니다
혹시 이해 안 되는 부분이 있으시다면
섹션 13의 Github 저장소의 코드와
비교해 보시면 될 것 같습니다
마지막으로 변경할 사항은
application.properties 내부에
리소스 서버와 Keycloak 인증 서버를
연결해 주는 겁니다
리소스 서버가
Keycloak 인증 서버에 든 정보를 알 수 있는 건
제가 지금 생성하려고 하는
프로퍼티 덕분입니다
여기서 볼 수 있듯이
이것이 바로 프로퍼티이며 이름은
spring.security.oauth2.resourceserver.jwt.jwk-set-url입니다
이 프로퍼티가 있어야 저희는
Keycloak 인증 서버의 위치를 URL로
리소스 서버에게 알려주어 인증서를 다운받게 할 수 있습니다
여기 나온 포트 넘버는 잘못되었죠
8180번으로 변경해야 합니다
이제 시동 중에 제 리소스 서버가
백그라운드에서 할 작업은
인증 서버에 연결하여
인증서를 다운받는 것입니다
인증 서버는 사설 인증서
또는 사설 키를 가지고 있어 그걸 이용해
액세스 토큰 ID 토큰에 모두 서명을 할 겁니다
그리고 공용 인증서를 이용하여 리소스 서버는
토큰을 다른 사람이 건드리진 않았는지
토큰이 유효한지 판단할 수 있습니다
이 방법을 이용하면
리소스 서버가 액세스 토큰을 인증할 때마다
인증 서버에 일일이 연결할 필요가 없어집니다
이걸로 리소스 서버는 성공적으로 구축했습니다
이번 강의는 많이 길었죠?
그래도 웹 애플리케이션에
저희가 무슨 변경을 가한 것인지 이해되셨길 바랍니다.
질문이 있으시다면
물론 Github 저장소로 찾아오시면 됩니다
들어주셔서 감사하며 다음 강의에서 뵙겠습니다
수고하셨습니다