SAML
Security Assertion Markup Language
XML 기반 인증/인가 프로토콜. 엔터프라이즈 SSO 표준.
Security Assertion Markup Language
XML 기반 인증/인가 프로토콜. 엔터프라이즈 SSO 표준.
SAML(Security Assertion Markup Language)은 2005년 OASIS에서 표준화된 XML 기반 인증/인가 프로토콜입니다. 주로 엔터프라이즈 환경에서 SSO(Single Sign-On)를 구현하는 데 사용됩니다. 한 번 로그인하면 여러 서비스(Salesforce, Workday, Slack 등)에 자동으로 인증됩니다.
SAML의 핵심 구성요소는 Identity Provider(IdP), Service Provider(SP), Assertion입니다. IdP는 사용자 인증을 담당하는 중앙 인증 서버(Okta, Azure AD, OneLogin 등)입니다. SP는 인증을 요청하는 서비스입니다. Assertion은 IdP가 발행하는 XML 문서로, 사용자 정보와 인증 상태를 담습니다.
SAML 흐름은 SP-Initiated와 IdP-Initiated 두 가지가 있습니다. SP-Initiated는 사용자가 SP에 접근 → SP가 SAML Request를 IdP로 리다이렉트 → IdP에서 로그인 → IdP가 SAML Response를 SP로 전송 → SP가 Assertion 검증 후 세션 생성하는 흐름입니다.
SAML은 브라우저 기반 웹 애플리케이션에 최적화되어 있고, XML 서명과 암호화로 보안이 강력합니다. 하지만 XML 파싱 오버헤드, 모바일 지원 한계, 복잡한 설정 등의 단점이 있어, 새 프로젝트에서는 OAuth 2.0/OIDC를 많이 선택합니다. 그러나 기존 엔터프라이즈 시스템 통합에는 여전히 SAML이 필수입니다.
// Node.js SAML Service Provider (passport-saml 사용)
import passport from 'passport';
import { Strategy as SamlStrategy } from '@node-saml/passport-saml';
// SAML Strategy 설정
passport.use(new SamlStrategy(
{
// Service Provider 설정
path: '/auth/saml/callback', // ACS (Assertion Consumer Service) URL
entryPoint: 'https://idp.example.com/sso/saml', // IdP SSO URL
issuer: 'https://myapp.example.com', // SP Entity ID
// IdP 인증서 (SAML Response 서명 검증용)
cert: fs.readFileSync('./idp-certificate.pem', 'utf-8'),
// 옵션
wantAssertionsSigned: true, // Assertion 서명 필수
signatureAlgorithm: 'sha256',
identifierFormat: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
},
// 검증 콜백 - Assertion에서 사용자 정보 추출
async (profile, done) => {
try {
// profile에서 사용자 정보 추출
const user = await findOrCreateUser({
email: profile.nameID, // SAML NameID
firstName: profile['firstName'], // Custom Attribute
lastName: profile['lastName'],
groups: profile['groups'] || [], // 그룹/역할 정보
});
return done(null, user);
} catch (error) {
return done(error);
}
}
));
// 라우트 설정
const app = express();
// SAML 로그인 시작 (SP-Initiated)
app.get('/auth/saml/login',
passport.authenticate('saml', {
failureRedirect: '/login',
failureFlash: true
})
);
// SAML Callback (ACS - Assertion Consumer Service)
app.post('/auth/saml/callback',
passport.authenticate('saml', {
failureRedirect: '/login',
failureFlash: true
}),
(req, res) => {
// 인증 성공 후 리다이렉트
res.redirect(req.session.returnTo || '/dashboard');
}
);
// SAML 메타데이터 제공 (IdP 등록 시 필요)
app.get('/auth/saml/metadata', (req, res) => {
const metadata = samlStrategy.generateServiceProviderMetadata(
fs.readFileSync('./sp-certificate.pem', 'utf-8'), // SP 공개 인증서
fs.readFileSync('./sp-private-key.pem', 'utf-8') // SP 비밀키
);
res.type('application/xml');
res.send(metadata);
});
// SAML 로그아웃 (Single Logout)
app.get('/auth/saml/logout', (req, res) => {
samlStrategy.logout(req, (err, requestUrl) => {
if (err) return res.status(500).send(err);
req.logout();
res.redirect(requestUrl); // IdP 로그아웃 URL로 리다이렉트
});
});
엔터프라이즈 연동에서:
"대기업 고객이 Okta SSO 연동을 요청했어요. 우리 앱에서 SAML SP로 동작해야 합니다. 메타데이터 XML 제공하고 ACS URL 설정하면 돼요. Attribute Mapping도 확인하세요."
기술 면접에서:
"SAML과 OAuth의 차이는?" - "SAML은 인증(Authentication) 중심이고 XML 기반, 주로 엔터프라이즈 SSO용입니다. OAuth는 인가(Authorization) 프레임워크로 JSON 기반, API 접근 제어에 적합해요. OIDC가 OAuth 위에 인증 레이어를 추가한 거예요."
트러블슈팅에서:
"SAML Response 검증이 계속 실패해요." - "시간 동기화 문제일 수 있어요. NotBefore/NotOnOrAfter 조건이 있어서 서버 시간이 몇 분만 틀려도 실패합니다. NTP 설정 확인하고, 클럭 스큐 허용 범위를 늘려보세요."