세가지 인증/인가 표준에 대해 공부하면서 나름대로 이해한 내용을 정리해보았다.
퍼즐 조각 맞추듯 이해한 내용이기 때문에 정확하지 않을 수 있다.
SSO(Single Sign On)
- 한번의 로그인으로 여러 어플리케이션 이용한다. (로그인 피로도 감소)
- 인증을 못하면 전체 어플리케이션을 이용할 수 없다.
- 비인가 유저가 일단 권한을 얻으면 모든 어플리케이션을 이용할 수 있게 된다.
한번만 로그인하면 된다?
정확히는 브라우저당 로그인을 한번만 하면 된다.
예를 들어 a.com, b.com이 같은 idp(myidp.com)를 바라보고 SSO가 되어있다고 할때,
- 인증안한 상태로 a.com 접속
- myidp.com으로 리다이렉트
- myidp.com 세션 쿠키가 없으므로 사용자에게 로그인 요청
- 인증 성공하면 myidp.com에 세션쿠키를 생성
- a.com의 콜백 url로 리다이렉트, a.com에 세션쿠키 생성
- 최초 요청 페이지로 다시 리다이렉트
이 상태에서 b.com 접속한다고 해보자. 다시 로그인해야할까? 아니다. 왜 그런걸까?
- b.com에는 세션 쿠키가 없는 상태로 접속
- myidp.com으로 리다이렉트
- myidp.com 세션쿠키가 있으므로 (위의 3에서) 사용자 로그인 필요없이 인증성공 내려줌
- b.com의 콜백 url로 리다이렉트, b.com에 세션쿠키 생성
- 최초 요청 페이지로 다시 리다이렉트
때문에 브라우저에 myidp.com의 세션쿠키만 남아있다면 다른 사이트에서 별도의 인증이 필요 없는 것이다.
SAML2.0 / OAUTH2.0 / OIDC(OpenIDConnect)
OAuth는 인증보단 인가 목적의 표준이므로 SAML과 OIDC를 비교해보자.
SSO과 같이 통합인증(federated authentication)을 위해 SAML이 있었다.
그런데 단점이 있다. SAML은 클라이언트가 웹사이트인 경우에는 적합하지만 모바일 어플리케이션 등에는 적합하지 않다. (그 이유는 아래에서)
이런 단점들을 OAuth 프로토콜을 이용해 보완하게 OIDC이다.
SAML(Security Assertion Markup Language)과 OIDC
SAML은 SP가 웹 사이트여야만 한다. OIDP는 웹이나 모바일앱 등이 가능하다.
SAML은 백채널을 (거의) 안쓴다. 브라우저를 통해서만 SP와 IDP가 인증정보를 주고 받는다.
서버간 통신없이 클라이언트를 통해서 인증 데이터를 주고 받기 때문에, POST 바인딩을 통해 인증정보 + 리다이렉션을 처리한다. ( POST Binding) 헤더를 사용하는게 아니라 HTML내에 스크립트를 심어서 리다이렉트를 시키기 때문에 클라이언트가 웹사이트여야 한다. 모바일웹에서 SAML 방식을 사용한다고 하면 별도로 브라우저를 띄워서 구현해야 할 텐데 그럼 사용자 경험이 떨어질 것이다.
IDP의 응답으로 내려온 HTML에서 스크립트로 리다이렉트 처리하는 예시이다.
form으로 POST 요청을 만들어 IDP에서 얻은 인증 정보를 SP에게 전달한다.
스크립트가 실행되기 위해선 브라우저라는 환경이 필요하다.
<form method=’post’ action=’https://localhost:9443/samlsso'>
<input type=’hidden’ name=’SAMLRequest’ value=’PD94bWwgdmVyc2lvbj0iMS4wln…….’>
<input type=’hidden’ name=’RelayState’ value=”trtreey34234fdfes’’>
<button type=’submit’>POST</button>
</form>
때문에 이런 단점을 보완한 서버간 통신을하는 OAuth 프로토콜 기반 OIDC가 나오게 된 듯하다.
SAML의 XML Assertion이 OIDC의 ID token
에 대응되는 개념이다.
아래가 사용자 정보를 담은 SAML의 XML Assertion이다.
```
<samlp:ArtifactResponse
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
ID="identifier_5"
InResponseTo="identifier_4"
Version="2.0"
IssueInstant="2004-12-05T09:22:05Z">
<!-- an ArtifactResponse message SHOULD be signed -->
<ds:Signature
xmlns:ds="http://www.w3.org/2000/09/xmldsig#">...</ds:Signature>
<samlp:Status>
<samlp:StatusCode
Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
</samlp:Status>
<samlp:Response
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="identifier_6"
InResponseTo="identifier_3"
Version="2.0"
IssueInstant="2004-12-05T09:22:05Z"
Destination="https://sp.example.com/SAML2/SSO/Artifact">
<saml:Issuer>https://idp.example.org/SAML2</saml:Issuer>
<ds:Signature
xmlns:ds="http://www.w3.org/2000/09/xmldsig#">...</ds:Signature>
<samlp:Status>
<samlp:StatusCode
Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
</samlp:Status>
<saml:Assertion
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="identifier_7"
Version="2.0"
IssueInstant="2004-12-05T09:22:05Z">
<saml:Issuer>https://idp.example.org/SAML2</saml:Issuer>
<!-- a Subject element is required -->
<saml:Subject>
<saml:NameID
Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">
user@mail.example.org
</saml:NameID>
<saml:SubjectConfirmation
Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData
InResponseTo="identifier_3"
Recipient="https://sp.example.com/SAML2/SSO/Artifact"
NotOnOrAfter="2004-12-05T09:27:05Z"/>
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Conditions
NotBefore="2004-12-05T09:17:05Z"
NotOnOrAfter="2004-12-05T09:27:05Z">
<saml:AudienceRestriction>
<saml:Audience>https://sp.example.com/SAML2</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:AuthnStatement
AuthnInstant="2004-12-05T09:22:00Z"
SessionIndex="identifier_7">
<saml:AuthnContext>
<saml:AuthnContextClassRef>
urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
</saml:Assertion>
</samlp:Response>
</samlp:ArtifactResponse>
```
다음은 OIDC의 id_token이다.
{
"iss": "https://accounts.google.com",
"azp": "1234987819200.apps.googleusercontent.com",
"aud": "1234987819200.apps.googleusercontent.com",
"sub": "10769150350006150715113082367",
"at_hash": "HK6E_P6Dh8Y93mRNtsDB1Q",
"hd": "example.com",
"email": "jsmith@example.com",
"email_verified": "true",
"iat": 1353601026,
"exp": 1353604926,
"nonce": "0394852-3190485-2490358"
}
OIDC와 OAuth2.0의 차이
OIDC는 OAuth2.0 기반으로 돌아가기 때문에 비슷하나 목적이 다르다.
OAuth2.0은 3rd Party의 API 사용인가가 목적이고, OIDC는 통합인증(federated authentication), SSO가 목적이다.
(OAuth를 인증 용도로도 사용할 수는 있다)OAuth를 인증 용도로 사용할 수 있는데 왜 OIDC가 필요한걸까?
- OAuth는 다양한 상황의 요구사항을 충족할 수 있또록 제네릭하게 구현되었다. 제네릭하다는 것은 그 만큼 클라이언트에서 할일이 많아진다. 반면 OIDC는 인증 초점이다. 상대적으로 클라이언트가 간편하게 사용할 수 있다. 또한 OAuth에서 제공하지 않는 부가적인 스펙(OP Discovery 등)도 있다. 때문에 인증만이 목적이라면 OIDC를 사용하는게 좋을 것 같다.
OIDC에는
id_token
개념이 추가된다. 사용자에 대한 부가적인 정가 id_token에 담긴다. 사용자 정보를 얻기 위해 별도 API콜을 하지 않아도 된다. (요청량이 많은 서비스에선 유의미한 효과일 것이다)Oauth 인가 플로우
+--------+ +---------------+ | |--(A)- Authorization Request ->| Resource | | | | Owner | | |<-(B)-- Authorization Grant ---| | | | +---------------+ | | | | +---------------+ | |--(C)-- Authorization Grant -->| Authorization | | Client | | Server | | |<-(D)----- Access Token -------| | | | +---------------+ | | | | +---------------+ | |--(E)----- Access Token ------>| Resource | | | | Server | | |<-(F)--- Protected Resource ---| | +--------+ +---------------+
OIDC 인증 플로우
OIDC가 OAuth2.0 기반이므로 플로우는 유사하다.
앞서 말했듯이 OIDC는 access_token외에 id token이라는 것을 추가로 받게 된다.
- `ID token`
JWT 형태로, 누가 인증을 했는지, 그 사람에 대한 정보, 발행자(issuer)가 누구인지등의 정보가 담겨있다.
```
{
"iss": "https://accounts.google.com",
"azp": "1234987819200.apps.googleusercontent.com",
"aud": "1234987819200.apps.googleusercontent.com",
"sub": "10769150350006150715113082367",
"at_hash": "HK6E_P6Dh8Y93mRNtsDB1Q",
"hd": "example.com",
"email": "jsmith@example.com",
"email_verified": "true",
"iat": 1353601026,
"exp": 1353604926,
"nonce": "0394852-3190485-2490358"
}
```
때문에 추가적인 API 콜없이 토큰을 열어서 유저정보를 획득할 수 있다.
정리
OIDC는 통합 인증을 위한 표준으로 SAML을
- XML Assertion을
ID token
(JWT)으로 - 통신 플로우를 OAuth2.0 방식으로
재작성했다고 볼 수 있다.
'개발 > 기타' 카테고리의 다른 글
[SpringSecurity] Https same origin 에 대한 CORS 설정 (0) | 2020.06.09 |
---|---|
URI와 URL의 개념, 차이 (2) | 2020.05.23 |
[PHP] Call to undefined function dl() (0) | 2020.02.10 |
카프카(Kafka) (0) | 2020.01.30 |
Cross-origin 파일 이름 변경해서 다운로드 하기 (0) | 2020.01.09 |