Apache Zeppelin 보안: Shiro 커스텀 비밀번호 암호화(SSO) 연동기
과거 당사의 빅데이터 모니터링 플랫폼에서는 데이터 사이언티스트들이 웹 브라우저 코딩을 할 수 있도록 Apache Zeppelin(제플린) 노트북을 포털 화면 내에 iFrame으로 임베드해서 제공했습니다.
가장 큰 문제는 보안 결함이었습니다. Zeppelin의 기본 shiro.ini 설정은 평문 텍스트 파일(Text file)에 어드민 아이디와 비밀번호를 하드코딩해서 읽어가는 초보적인 구조를 갖추고 있었습니다. 하지만 기업용 데이터 솔루션인 플랫폼는, 내장된 PostgreSQL 데이터베이스의 계정 관리 메타 테이블(FL_SYS_USER)에 저장된 AES 기반 단방향 해시 암호문과 동기화하는 SSO(Single Sign-On) 기반의 깐깐한 인증(Authentication) 처리가 요구되었습니다.
이를 해결하기 위해, 플랫폼의 보안 모듈 암호화 키를 Zeppelin의 Apache Shiro 프레임워크 런타임에 주입하여 커스텀 Password Matcher를 작성했습니다.
1. 커스텀 CredentialsMatcher 자바 모듈 구현
Zeppelin 코어 소스를 가져와, 클라이언트가 로그인 창에서 제출한 패스워드를 플랫폼의 AES/SHA 규격으로 동일하게 해싱(Hashing)하여 DB 값과 일치하는지 비교하는 인증 통과 레이어를 삽입(Override) 했습니다.
// 패키지 경로에서 플라밍고 프로젝트의 시그니처를 확인 수 있음
package com.exem.flamingo.zeppelin.security;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.credential.CredentialsMatcher;
public class FlamingoPasswordMatcher implements CredentialsMatcher {
// ini를 통해 외부에서 주입(DI) 받을 대칭키 문자열 슬롯
private String secretKey1 = "";
private String secretKey2 = "";
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
// 1. 사용자가 Zeppelin UI에서 로그인 폼에 친 오리지널 텍스트
String inputPassword = String.copyValueOf((char[]) token.getCredentials());
// 2. DB에서 끌어온 (비교 대상인) 암호화된 타겟 패스워드
String targetPassword = String.copyValueOf((char[]) info.getCredentials());
// 3. 플라밍고 전용 자체 AES/비밀키 헬퍼 로직으로 인풋을 동일하게 해싱 처리
String encPassword = encrypt(secretKey1, secretKey2, inputPassword);
// 4. 최종 문자열 이퀄 검사
return targetPassword.equals(encPassword);
}
// ... encrypt() helper method private logic (AES-256)
}컴파일된 flamingo-zeppelin-security.jar 덩어리를 만들어 Zeppelin의 lib 폴더에 던져 넣었습니다.
2. 권한 제어 코어 설정 (shiro.ini)
이제 Zeppelin의 인증 엔진이 텍스트 파일이 아닌 JDBC Realm (외부 데이터베이스 조회 체계)을 바라보도록 설정 파일을 교체하고, 우리가 만든 FlamingoPasswordMatcher를 물려줍니다.
[main]
flamingoPasswordMatcher=com.exem.flamingo.zeppelin.security.FlamingoPasswordMatcher
# 2. 객체의 프로퍼티 필드에 사용할 솔트(Salt) 비밀키 파라미터 강제 수사
flamingoPasswordMatcher.secretKey1=Bar12345Bar12345
flamingoPasswordMatcher.secretKey2=ThisIsASecretKet
# 3. JDBC(RDB) 기반 Shiro 인증(Realm) 환경 명시
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
# 4. 제출받은 USER_ID 변수(?)를 바탕으로 비밀번호 열을 Select 해오는 무식하지만 확실한 Query
jdbcRealm.authenticationQuery = SELECT USER_PW AS password FROM FL_SYS_USER WHERE USER_ID = ?
# 5. 방금 가져온 암호화 텍스트의 참/거짓 매칭기 엔진을 우리가 만든 플라밍고 객체로 런타임 스위칭
jdbcRealm.credentialsMatcher=$flamingoPasswordMatcher
# 6. 타겟이 되는 플라밍고의 마스터 PostgreSQL 연결 정보 정의
ds = org.postgresql.ds.PGPoolingDataSource
ds.serverName=fem.exem.oss
ds.databaseName=flamingo
ds.user=postgres
ds.password=supersecret
jdbcRealm.dataSource=$ds
# 마지막으로 변경된 전체 보안 덩어리를 Zeppelin 코어에 Apply
securityManager.realms=$jdbcRealm이 고된 작업이 끝난 뒤, 사용자들은 플랫폼 웹 포털에서 하나의 동일한 아이디와 비밀번호 규격으로 별개로 떠 있는 스파크(Spark) 분석용 Zeppelin 화면을 매끄럽게 입장(SSO)할 수 있게 되었습니다.