-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[FEAT/#20] Public Key 조회 API 구현 #22
base: dev
Are you sure you want to change the base?
Conversation
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
작업하시느라 고생하셨습니다!
그리고 코멘트로 질문주신 부분에 대해서는 보안을 생각했을 때 2번 방식이 낫다고 생각하는 편이며 파싱 과정의 어려움은 저희 플랫폼팀에서 각 팀에게 공유문서를 만들어 전달드리면 해결할 수 있는 문제인 것 같다고 생각합니다!
src/main/java/sopt/makers/authentication/support/config/LocalRSAKeyManager.java
Outdated
Show resolved
Hide resolved
src/main/java/sopt/makers/authentication/support/config/LocalRSAKeyManager.java
Outdated
Show resolved
Hide resolved
src/main/java/sopt/makers/authentication/support/security/filter/JwtAuthenticationFilter.java
Show resolved
Hide resolved
src/main/java/sopt/makers/authentication/usecase/auth/service/JwksRetrieveService.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
고생 많으셨습니다!!
- KeyManager의 추상화
우선 로컬에서 *.pem
파일을 관리하는 방식을 지양하는 것은 동의하구
추후 Vault 등을 활용해서 jwks 자원 관리하는 방향도 좋을 것 같네요
(나중에 배포 스크립트를 다시 수정해야겠네 ㅠㅜ)
몇 가지 질문이 있어 남겨봅니다!
- "로컬 파일로 관리 -> prod 단계에서 격리 하는 방법"은 완벽히 이해하지 못했는데 prod에서 pem 키를 resources 패키지에 두지 않고 외부에 저장한다는 의미일까요?!
- prod 단계에서 추가 구현체를 작성한다면 Local 구현체에 대한 소스코드도 동시에 존재하는 상황이 되는거겠죠? (단순 확인차 질문)
- 환경별로 추가 구현체 구현하는 방식은 많이 경직될 수 있으니
JwtRSAKeyConfiguration
내부에 JwtProperty 객체를 private final로 주입 받아서 profile 별 RSAKeyManager bean 생성 메서드를 선언한 다음 각 bean 객체 생성자 파라미터로 각 환경에서의 pem key location을 주입하는 방식으로 변경하는 것은 어떨까요? - 이렇게하면 유사 객체 중복 구현은 피할 수 있을 것 같아요!!
- 환경별로 추가 구현체 구현하는 방식은 많이 경직될 수 있으니
@Configuration
@RequiredArgsConstructor
public class JwtConfiguration {
private final RSAKeyManager keymanager;
@Bean
public JwtEncoder jwtEncoder() {
RSAPublicKey publicKey = keyManager.getPublicKey();
RSAPrivateKey privateKey = keyManager.getPrivateKey();
JWK jwk = new RSAKey.Builder(publicKey).privateKey(privateKey).build();
JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
return new NimbusJwtEncoder(jwks);
}
@Bean
public JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withPublicKey(keyManager.getPublicKey()).build();
}
}
@Configuration
@RequiredArgsConstructor
public class RSAKeyManagerConfiguration {
private final JwtProperty jwtProperty;
private final ResourceLoader resourceLoader;
@Bean
@Profile("dev")
public RSAKeyManager keyManagerForDev() {
...
return new RSAKeyManagerImpl(...);
}
@Bean
@Profile("prod")
public RSAKeyManager keyManagerForProd() {
....
return new RSAKeyManagerImpl(...);
}
}
// RSAKeyManager
// Local이든 Dev이든 Prod이든 생성자 주입을 통해서 자동으로 맞춤형 객체가 생성되기 때문에 중복 코드가 존재하는 객체 생성을 방지할 . 수있음
@Component
@RequiredArgsConstructor
public class RSAKeyManagerImpl implements RSAKeyManager {
private final Resource publicKeyResource;
private final Resource privateKeyResource;
...
}
위 예시 중 제가 잘못 이해한 부분이 있다면 말씀해주세요!!
여하튼 프로필별로 분리하고자 추상화하고자 노력하신 것은 너무 좋은 것 같다고 생각합니다!!
- Jwks 반환
- PublicKey 전문을 내려주기 (------PublicKey--- 부터 시작하겠죠..? )
- Jwks를 내려주기
저 또한 성은이와 동일하게 2번 방식이 낫다고 생각합니다.
본래 의도가 JWKS 제공이었고 각 서비스에 맞는 파싱 및 복호화는 각 서비스 인증 과정에서의 역할이라고 생각하기 때문에 공유문서를 철저하게 준비해서 전달하면 충분히 해소 가능한 불편함이라고 생각해요!!
src/main/java/sopt/makers/authentication/support/constant/JwtConstant.java
Outdated
Show resolved
Hide resolved
src/main/java/sopt/makers/authentication/support/config/LocalRSAKeyManager.java
Outdated
Show resolved
Hide resolved
src/main/java/sopt/makers/authentication/support/config/LocalRSAKeyManager.java
Outdated
Show resolved
Hide resolved
현재 방식은 Local classpath:에 존재하는 *.pem파일을 활용해 반환하고 있습니다. 하지만 로컬 작업 상황에서는 해당 기능이 필요하지 않을 것 같다고 생각했고, |
Related Issue 🚀
Work Description ✏️
기존
AuthController
와 Jwks를 조회하는 서비스는 엄밀히 성격이 다르다고 생각합니다.(다른 헤더가 필요하고, 인증이 필요하지 않습니다.) 따라서 컨트롤러도 분리해서 구현하게 되었구요.
URI Mapping은
**/.well-known/jwks.json**
으로 설정했는데요, 이는RFC 8414
의 Oauth2/OICD 에서 정의한 JWKS 엔드포인트 표준 규약을 따라서 구현했습니다.논의할 점은 아래에 적어놓겠습니다.
PR Point 📸
구현 로직 자체는 어렵지 않습니다. 변화한 점은 KeyManager의 추상화와, PublicKey가 아닌, Jwks를 반환한다는 점 입니다.
1. KeyManager의 추상화
기존 RSAConfig에서는 로컬 파일에 존재하는 *.pem 파일을 읽어서 RSAPublic,RSAPrivateKey로 만드는 구조를 가지고 있습니다.
구현하며 계속 이런 생각이 들었는데요, '로컬에 키 파일을 갖고 있는게 맞을까?'
저는 아니라고 생각합니다. AWS Key Manager, Hasicorp Vault등 다양한 외부 벤더들이 있지만, 현재 작업 생산성을 위해서는
로컬 파일로 관리 -> prod 단계에서 격리 하는 방법이 최선이라고 생각했습니다.
그럼에 있어서 추후 Profile 별로 KeyManager를 분리하기 위해
다음과 같이 추상화 하였습니다. 현재는
LocalRSAKeyManager
가 구현하고 있습니다.이 부분은 prod 단계에서 추가 구현체를 작성하면 될 것 같습니다.
2. Jwks 반환
두 가지 방법 중에서, 아직도 고민중입니다.
원래는 2번을 내려주는 방식이 무조건 맞다고 생각했습니다.
Jwks 조회 시 다음과 같은 값들이 전달됩니다.
이 값들은 기존에 갖고있는 public.pem 의 값과 조금 다른데요, URL-safe를 위해서 Base64로 인코딩 된 상태입니다.
따라서 해당 값을 조회 -> base64 디코딩 -> RsaKey 생성 -> JwsVerifier / CustomVerifier 검증 의 절차를 가지게 됩니다.
하지만
1
번 방식은 그러한 점에서 자유롭습니다. 하지만 전문을 공유하기 때문에, 보안에 강점을 가진다고는 말을 못하겠네요..홀로 진행하는 프로젝트였다면 당연히 2번을 선택했겠지만, 다른 팀에도 전역적으로 적용해야 하는 파싱 로직이 추가되다 보니 망설여지는 것도 사실이네요. 의견이 궁금합니다 !