Conversation
- AuthType에 KEYCLOAK(3) 추가 - UserDbExtraEntity에 keycloak_user_id, keystone_username, keystone_password 컬럼 추가 - UserDetailJpaRepository.findByKeycloakUserId() 추가 - UserIdentityJpaRepository.findByUserEmail() 추가 - UserRepositoryPort/Adapter: findUserDetailByKeycloakUserId, findUserIdentityByEmail 추가 - KeystonePasswordEncryptor: AES-256-CBC 기반 Keystone 패스워드 암호화 모듈 추가
- KeycloakOidcExternalPort/Adapter/Module: code→token 교환, token revoke 구현
- KeycloakIdTokenParser: ID Token 클레임 추출 (email, department, studentId 등)
- KeycloakUserModule: 3-way 분기 처리
Branch 1) keycloakUserId 연결된 기존 사용자 → 일반 로그인
Branch 2) 이메일 일치 기존 사용자 → Account Linking (Keystone 패스워드 재설정)
Branch 3) 신규 사용자 → Keystone 생성 + user_detail/user_auth_detail INSERT
- KeycloakAuthModule.processCallback(): 전체 플로우 오케스트레이션
- KeycloakAuthController: /login, /callback, /logout 엔드포인트 추가
- SessionConstants: 세션 키/필드명 상수 정의 (global/security/session으로 배치)
- SessionPrincipal: SecurityContext principal 클래스 (sessionId, keycloakUserId, keystoneUserId)
- SessionAuthenticationFilter: acc-session-id 쿠키 → SecurityContext 인증 처리
- Redis 세션 조회 후 SessionPrincipal 설정
- 슬라이딩 세션 (요청마다 TTL 30분 연장)
- SecurityConfig: SessionAuthenticationFilter를 JwtAuthenticationFilter 뒤에 등록
- RedisSessionRepositoryAdapter: SessionConstants import global로 변경
- SessionModule: 세션 기반 토큰 접근 모듈
- getKeystoneUnscopedToken(): 만료 시 DB credentials로 자동 재발급
- getKeycloakAccessToken(): 만료 시 refresh_token으로 자동 갱신
- getKeystoneScopedToken(): 프로젝트 스코프 토큰 발급
- AuthModule: getUnscopedTokenByUserId, issueProjectScopeToken, getProjectPermission @deprecated 처리
- docker-compose.yml: Redis 서비스 추가
- JwtAuthenticationFilter 삭제 → SessionAuthenticationFilter로 대체 - JwtInfo 삭제 → SessionPrincipal로 대체 - SessionConstants 삭제 - SecurityConfig에서 JwtAuthenticationFilter 제거
- userId → sessionId 변경 - authModule → sessionModule 전환 - JwtInfo → SessionPrincipal 교체
- userId → sessionId 변경 - authModule → sessionModule 전환 - JwtInfo → SessionPrincipal 교체
- userId → sessionId 변경 - authModule → sessionModule 전환 - JwtInfo → SessionPrincipal 교체
- userId → sessionId 변경 - sessionModule 기반 Keystone 처리
- userId → sessionId 변경 - SessionModule + AuthModule 병행 구조
- requesterId → sessionId 변경 - SessionModule 기반 사용자 식별 전환 - JwtInfo → SessionPrincipal 교체
- userId → sessionId 변경 - authModule → sessionModule 전환 - JwtInfo → SessionPrincipal 교체
- Keycloak accessToken 만료 시 refreshToken으로 자동 갱신 (HTTP 1회, ~수명마다) - refreshToken까지 만료 시 Redis 세션 삭제 → 401 (재로그인 유도) - Keycloak 서버 설정 기반 세션 수명이 실제로 적용되도록 수정 - 대부분 요청: 로컬 timestamp 체크만 수행 (HTTP 없음)
- SuperAdminProperties에 keycloakUserId/keystoneUsername/keystonePassword 필드 추가 - SuperAdminInitializer: 기동 시 user_detail + user_auth_detail 함께 생성 - keycloakUserId 미설정 계정 재기동 시 자동 업데이트
- KeycloakUserModule: isLinkedAdmin() 추가, Branch 3 departDto null 방어 - KeycloakAuthModule: isLinkedAdmin() 확인 후 관리자 학적검증 skip
- KeycloakIdTokenClaims: studentId 제거, ajouStudentId(ajou_student_id) 단일화 - KeycloakIdTokenParser: ajou_student_id 클레임 추출 추가, email optional 처리 - AjouUnivModule: univ_depart_info 미매핑 시 ajouMajor를 department fallback으로 사용
src/main/java/com/acc/local/service/ports/SnapshotPolicyServicePort.java
Fixed
Show fixed
Hide fixed
src/main/java/com/acc/local/service/ports/SnapshotPolicyServicePort.java
Fixed
Show fixed
Hide fixed
src/main/java/com/acc/local/service/ports/SnapshotPolicyServicePort.java
Fixed
Show fixed
Hide fixed
src/main/java/com/acc/local/service/ports/SnapshotPolicyServicePort.java
Fixed
Show fixed
Hide fixed
src/main/java/com/acc/local/service/ports/SnapshotPolicyServicePort.java
Fixed
Show fixed
Hide fixed
src/main/java/com/acc/local/service/ports/SnapshotPolicyServicePort.java
Fixed
Show fixed
Hide fixed
src/main/java/com/acc/local/service/ports/SnapshotPolicyServicePort.java
Fixed
Show fixed
Hide fixed
src/main/java/com/acc/local/external/dto/keycloak/KeycloakRevokeRequest.java
Fixed
Show fixed
Hide fixed
| JwtInfo jwtInfo = (JwtInfo) authentication.getPrincipal(); | ||
| String id = interfaceServicePort.createInterface(jwtInfo.getUserId(), projectId, request); | ||
| SessionPrincipal principal = (SessionPrincipal) authentication.getPrincipal(); | ||
| String id = interfaceServicePort.createInterface(principal.getSessionId(), projectId, request); |
There was a problem hiding this comment.
해당 ID는 기존 도메인에서 개발된 지역변수이므로 변경하지 않음
|
코파일럿 리뷰 반영 먼저 진행해주세용!! |
callme-waffle
left a comment
There was a problem hiding this comment.
진행주신 리팩토링에 대한 플로우검토 및 로직검토를 완료했습니다.
코드 내 삼항연산자 사용과 단일메서드 내 다중책임 등이 확인되어 추후 리팩토링은 필요할 것 같으나, 전반적인 플로우 및 구현에서는 큰 문제는 없는 것 같습니다.
다만 몇가지 확인이 필요한 사항이 있어 확인 후 코멘트 추가 요청드립니다. 확인 후 코멘트 부탁드립니다!
| `user_detail.keycloak_user_id = claims.subject()`인 레코드가 있으면 저장된 keystoneUsername과 AES-256 복호화된 keystonePassword를 그대로 반환합니다. 외부 API 호출 없이 DB 조회와 복호화만으로 처리됩니다. | ||
|
|
||
| **Branch 2. 이메일 일치 기존 사용자 (Account Linking)** | ||
| keycloak_user_id는 없지만 `user_auth_detail.user_email = claims.email()`인 레코드가 있을 때 진입합니다. 기존에 ADMIN 또는 GOOGLE 방식으로 가입한 사용자가 Keycloak으로 최초 로그인하는 경우입니다. 기존 Keystone 패스워드를 알 수 없으므로 시스템 어드민 토큰으로 패스워드를 재설정하고, `user_detail`에 keycloak_user_id, keystoneUsername, keystonePassword(암호화)를 업데이트합니다. 이 시점 이후 Keystone 직접 패스워드 로그인은 불가합니다. |
There was a problem hiding this comment.
해당 플로우는 기존 테스트하던 ACC프로젝트 내에서의 하위호환성을 위함으로 이해했는데, 혹시 맞을까요?
ADMIN계정 등록의 경우, 관리자의 개인계정과는 무관하게 별도 계정을 생성하여 사용하는 것으로 이해했어 해당 플로우의 필요성이 운영환경에서도 존재하는지 궁금하여 문의드립니다.
There was a problem hiding this comment.
운영환경에서 필요성이 존재하지 않습니다.
개발환경에서 테스트 시 , 계정관련해서 필요성이 있다고 생각했는데, 개발환경의 계정이 필요없고, 다시 keycloak 기반으로 생성할 예정이라면 제거해도 되는 로직입니다.
There was a problem hiding this comment.
즉 , Keycloak과 연동을 하게된다면 ACC자체에서 계정을 생성해도, keycloak 계정에 대한 유효성체크부분을 해결해야하기에 위 flow를 만들어둔 것입니다.
이제 ACC자체의 로그인 Flow를 사용하지 않고 keycloak의 sso를 통해서 로그인을 진행하기 때문입니다.
<ACC의 ADMIN 계정 생성 방법>
- keycloak에서 ACC Admin 용 계정 생성 또는 Admin으로 등록할 기존 아올다 회원의 keycloak 계정 준비
- 해당 계정을 ACC에서 Admin으로 할당 -> (ACC DB에 직접 접근해서 Admin으로 변경하던, 아니면 Keycloak의 어떤 권한 정보를 매핑헤서 ACC DB내에 계정의 권한을 Admin으로 설정하는 로직을 추가하던 2번 과정은 필요합니다.)
그래서 만약, Keycloak에서 ACC Admin 용 계정 생성을 생성했을 시, 학적 정보를 검증하는 로직을 제거해야 정상적인 접근이 가능할 것 같아 위와같은 flow를 추가하게 된 것입니다.
src/main/java/com/acc/local/controller/KeycloakAuthController.java
Outdated
Show resolved
Hide resolved
| GITLAB(1, "GitLab 인증"), | ||
| ADMIN(2, "관리자 직접 생성"); | ||
| ADMIN(2, "관리자 직접 생성"), | ||
| KEYCLOAK(3, "Keycloak OIDC 인증"); |
There was a problem hiding this comment.
GOOGLE과 GITLAB 인증의 구현이 Keycloak 경유를 기반으로 구현되는거라 별도의 KEYCLOAK 기반 인증임을 구분할 필요는 없다고 생각했는데, 혹시 위 2개의 연동 flow는 다르게 구현되는건가요?
There was a problem hiding this comment.
이것또한 개발환경 데이터를 고려해서 저렇게 구분을 했던 것입니다.
acc는 이제 keycloak에 대한 계정만을 바라보기에 AuthType이라는 속성자체를 저장할 필요가 없습니다.
keycloak의 토큰 유효성을 통해서 회원을 생성하지, 계정 생성 인증 방식에 대해서는 ACC가 이제 관여하지 않기 때문입니다.
src/main/java/com/acc/local/service/modules/auth/AjouUnivModule.java
Outdated
Show resolved
Hide resolved
| /** | ||
| * Keycloak OIDC 콜백 처리: code → 세션 생성 → sessionId 반환. | ||
| */ | ||
| public String processCallback(String code) { |
There was a problem hiding this comment.
후순위라 이번 리뷰에서는 반영되지 않아도 될 것 같습니다만, 추후헤는 메서드 내 담당기능이 많아 목적 별 메서드 분할하여 정리하면 조금 더 가시성이 높아질 것 같습니다 :)
| JwtInfo jwtInfo = (JwtInfo) authentication.getPrincipal(); | ||
| String id = networkServicePort.createNetwork(request, jwtInfo.getUserId(), projectId); | ||
| SessionPrincipal principal = (SessionPrincipal) authentication.getPrincipal(); | ||
| String id = networkServicePort.createNetwork(request, principal.getSessionId(), projectId); |
| JwtInfo jwtInfo = (JwtInfo) authentication.getPrincipal(); | ||
| String id = securityRuleServicePort.createSecurityRule(projectId, jwtInfo.getUserId(), request); | ||
| SessionPrincipal principal = (SessionPrincipal) authentication.getPrincipal(); | ||
| String id = securityRuleServicePort.createSecurityRule(projectId, principal.getSessionId(), request); |
There was a problem hiding this comment.
이번 이슈 사항이 아님.
기존 도메인에서 개발된 내용이므로 적용하지 않는다.
| JwtInfo jwtInfo = (JwtInfo) authentication.getPrincipal(); | ||
| String id = routerServicePort.createRouter(request, jwtInfo.getUserId(), projectId); | ||
| SessionPrincipal principal = (SessionPrincipal) authentication.getPrincipal(); | ||
| String id = routerServicePort.createRouter(request, principal.getSessionId(), projectId); |
There was a problem hiding this comment.
마찬가지로 이번 이슈 사항이 아님.
기존 도메인에서 개발된 내용이므로 적용하지 않는다.
| JwtInfo jwtInfo = (JwtInfo) authentication.getPrincipal(); | ||
| String id = securityGroupServicePort.createSecurityGroup(request, projectId, jwtInfo.getUserId()); | ||
| SessionPrincipal principal = (SessionPrincipal) authentication.getPrincipal(); | ||
| String id = securityGroupServicePort.createSecurityGroup(request, projectId, principal.getSessionId()); |
There was a problem hiding this comment.
이번 이슈 사항이 아님.
기존 도메인에서 개발된 내용이므로 적용하지 않는다.
📌 변경 요약 (Summary)
acc-session-id세션 쿠키만으로 모든 API를 호출할 수 있게 됩니다.String token파라미터를SessionModule로부터 전달 받게 됩니다.🔗 관련 이슈 (Related Issue)
Closes #18
🛠 작업 내용 / 작업 순서 (Implementation Details)
예시:
UserEntity에keycloak_user_id컬럼 추가 및 Keystone 비밀번호 AES-256 암호화 적용KeycloakAuthController,KeycloakAuthServicePort)SessionAuthenticationFilter,SessionModule)SessionAuthenticationFilter에서 토큰 유효성 검사 후 자동 갱신JwtAuthenticationFilter·JwtInfo제거 및SecurityConfig정리✨ 주요 변경 사항 (Key Changes)
인증 방식 전환
JwtAuthenticationFilter,JwtInfo,/auth/login/refresh엔드포인트GET /api/v1/auth/keycloak/login), 로그아웃 (DELETE /api/v1/auth/keycloak/logout)acc-session-id(HttpOnly / Secure / SameSite=None) 발급Redis 세션 저장소
{ keystoneUnscopedToken, keystoneScopedTokens, keycloakAccessToken, keycloakRefreshToken }구조로 Redis 저장전 도메인 서비스 시그니처 변경
someMethod(String token, ...)→ 변경:someMethod(String sessionId, ...)SessionModule.getKeystoneUserId(sessionId)/getKeystoneScopedToken(sessionId, projectId)등으로 토큰 조회 일원화보안 강화
KEYSTONE_PASSWORD_ENCRYPTION_KEY)UserEntity에keycloak_user_id추가 — Keycloak ↔ Keystone 계정 연결환경변수 변경
KEYCLOAK_ISSUER_URI,KEYCLOAK_CLIENT_ID,KEYCLOAK_CLIENT_SECRET,KEYCLOAK_REDIRECT_URI,KEYCLOAK_FRONTEND_REDIRECT_URL,OAUTH_COOKIE_DOMAIN,KEYSTONE_PASSWORD_ENCRYPTION_KEY|
| 제거 가능 |
JWT_SECRET,JWT_EXPIRATION_MS,JWT_REFRESH_EXPIRATION_MS,JWT_OAUTH_VERIFICATION_EXPIRATION_MS|🧪 테스트 결과 (Test Results)
테스트 환경
테스트 방법
테스트 결과