ADFS에서 OIDC와 SAML구성하기

0. ADFS 구성하기

다음 링크를 참고하여 ADFS를 구성해둡니다.
-> Windows Server 2019 ADFS구축하기

1. OIDC 구성하기

참고 : Microsoft/AD FS 2016 이상에서 OpenID 커넥트 사용하여 웹 애플리케이션 빌드

Server Manager에서 Tools > AD FS Management로 이동합니다.

Application Groups > Add Application Group > Server application 선택

다음 페이지로 넘어가면 Client ID와 Redirect할 URI를 적는 칸이 나오게 됩니다.
Client ID는 복사해두고, URI는 추가해주도록 합시다.

generate client secret을 선택하고, 생성해줍니다.
Client Secret이 없다면 Authorization code 방식을 사용할 수 없게 되니, 생성해주도록 합니다.

오버뷰 페이지

이렇게 하고나면 OIDC설정을 마치게 됩니다.

https://{DOMAIN}/adfs/.well-known/openid-configuration

위 링크로 접속하게 되면 oidc의 구성정보를 확인할 수 있습니다.

[OIDC] Access Token 얻기

인증을 시도했을 때 OIDC에서는 access_token, id_token, refresh_token 그리고 각 토큰의 만료시간을 client에게 보내주게 됩니다.

token을 얻기 위한 endpoint는 /.well-known/openid-configuration페이지의 정보를 통해서 확인할 수 있습니다.

ADFS의 token endpoint는 다음과 같습니다.

https://{DOMAIN}/adfs/oauth2/token

Curl커맨드를 통해 토큰을 얻어보겠습니다.

$ curl -d 'client_id=1b7d1b49-8c67-abcd-abcd-94ee42f4ae78' -d 'username=seung@ce-window.xxx.com' -d 'password=***' -d 'grant_type=password' 'https://{DOMAIN}/adfs/oauth2/token' | python -m json.tool

{
    "access_token": "eyJ0eXAiOi...H2qA",
    "expires_in": 3600,
    "id_token": "eyJ0eXA...hk0DA",
    "refresh_token": "00-YU5JXcI5...6HeNZv",
    "refresh_token_expires_in": 28799,
    "resource": "1b7d1b49-8c67-4427-ae7d-94ee42f4ae78",
    "token_type": "bearer"
}

유저의 정보는 access_tokenid_token에 들어가있고, 토큰들을 decoding해보면 아래와 같은 정보를 얻을 수 있습니다.

access_token:

{
  "aud": "microsoft:identityserver:1b7d1b49-8c67-4427-ae7d-94ee42f4ae78",
  "iss": "http://{DOMAIN}/adfs/services/trust",
  "iat": 1671347857,
  "nbf": 1671347857,
  "exp": 1671351457,
  "apptype": "Public",
  "appid": "1b7d1b49-8c67-4427-ae7d-94ee42f4ae78",
  "authmethod": "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
  "auth_time": "2022-12-18T07:17:37.496Z",
  "ver": "1.0"
}

id_token:

{
  "aud": "1b7d1b49-8c67-4427-ae7d-94ee42f4ae78",
  "iss": "https://{DOMAIN}/adfs",
  "iat": 1671347857,
  "nbf": 1671347857,
  "exp": 1671351457,
  "auth_time": 1671347857,
  "sub": "cciv92mvgkfSLMlgq9QmTKlceQacwTIQozVZv/w2Qg0=",
  "upn": "seung@ce-window.xxx.com",
  "unique_name": "ADFS\\seung",
  "sid": "S-1-5-21-2542615494-1454194932-797505765-1133",
  "apptype": "Public",
  "appid": "1b7d1b49-8c67-4427-ae7d-94ee42f4ae78",
  "authmethod": "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
  "ver": "1.0"
}

Default claim에는 upn(User Principal Name), unique_name같이 기본적인 정보들만 담겨있습니다.

예를 들어, 유저가 속한 그룹에 대한 정보를 넘기려면 어떻게 설정해야할까요?

[OIDC] Custom Claim 추가하기

기본적으로 ADFS에서 주는 정보 외에, 추가적으로 정보를 넘기고 싶다면 claim정보를 수정해줘야 합니다.

ADFS Management에서 위에서 만들었던 OIDC의 설정을 들어갑니다.

Add application 을 선택해 Web API를 새로 만들어줍니다.
Web API에서는 claim과 access policy등을 설정할 수 있습니다.

-> Web API 선택

Issuance Transform Rules탭으로 이동합니다.
여기에서 claim 규칙을 정의할 수 있습니다.

그룹의 정보는 AD(Active Directory)에 담겨있으므로 LDAP에서 정보를 가져오기를 선택합니다.

규칙의 이름을 정의하고, 정보를 가져올 소스를 선택한 뒤에 어떤 속성을 claim으로 보낼건지 정의합니다.

규칙을 정의한 뒤에, 다시 토큰정보를 받아보면 아래와 같이 그룹에 대한 정보가 role에 매핑되어 담겨있는 것을 확인할 수 있습니다.

{
  "aud": "1b7d1b49-8c67-4427-ae7d-94ee42f4ae78",
  "iss": "https://adfs.ce-window.xxx.com/adfs",
  "iat": 1671358274,
  "nbf": 1671358274,
  "exp": 1671361874,
  "auth_time": 1671358274,
  "sub": "cciv92mvgkfSLMlgq9QmTKlceQacwTIQozVZv/w2Qg0=",
  "upn": "seung@ce-window.xxx.com",
  "unique_name": "ADFS\\seung",
  "sid": "S-1-5-21-2542615494-1454194932-797505765-1133",
  "role": [
    "Domain Users",
    "testtest"
  ],
  "apptype": "Public",
  "appid": "1b7d1b49-8c67-4427-ae7d-94ee42f4ae78",
  "authmethod": "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
  "ver": "1.0"
}

이름을 다르게 해서 claim 규칙을 정하고 싶다면, Transform an Incoming Claim옵션을 활용하면 됩니다.

예를들어 Department정보를 claim에 담고싶다면 먼저 LDAP에서 정보를 가져오고,

새로운 규칙은 Transform an Incoming Claim템플릿을 사용하여 Group타입으로 들어오는 claim은 department라는 이름의 타입으로 나가게 설정해줍니다.

그러면 아래와 같이 department라는 이름으로 부서정보가 출력되게 됩니다.

{
  "aud": "1b7d1b49-8c67-4427-ae7d-94ee42f4ae78",
  "iss": "https://adfs.ce-window.xxx.com/adfs",
  "iat": 1671358478,
  "nbf": 1671358478,
  "exp": 1671362078,
  "auth_time": 1671358477,
  "sub": "cciv92mvgkfSLMlgq9QmTKlceQacwTIQozVZv/w2Qg0=",
  "upn": "seung@ce-window.xxx.com",
  "unique_name": "ADFS\\seung",
  "sid": "S-1-5-21-2542615494-1454194932-797505765-1133",
  "role": [
    "Domain Users",
    "testtest"
  ],
  "group": "department-ce",
  "department": "department-ce",
  "apptype": "Public",
  "appid": "1b7d1b49-8c67-4427-ae7d-94ee42f4ae78",
  "authmethod": "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
  "ver": "1.0"
}

2. SAML 2.0 구성하기

참고 : Microsoft/Configure a SAML 2.0 provider for portals with AD FS

Server Manager에서 Tools > AD FS Management로 이동합니다.

Relying Party Trusts에서 Add Relying Party Trust 선택

Claims aware 선택

Enter data about the relying party manually 선택

이름 지어주기

SAML의 assertion을 암호화하려면 아래와 같이 certificate를 설정해주어야 합니다. (만약 SAML client가 암호화된 Assertion을 지원하지 않는다면 설정하지 말 것)

Enable support for the SAML 2.0 WebSSO protocol박스에 체크해주고, SAML로그인 성공시 redirect될 url을 기재해줍니다.
ex) https://xxx.xxx/dex

그 다음 스텝에서는 신뢰할 수 있는 url을 Trust Identifiers에 등록해줍니다.
ex) https://xxx.xxx/dex/callback

SAML에 접근할 수 있는 access control policy를 설정해줍니다.

설정이 끝나게 되면 Claim 규칙을 직접 정의해서 SAML assertion에 어떤 정보를 담을 건지 설정할 수 있게 창이 뜨게 됩니다.

좌측 하단의 ADD rule 버튼을 눌러 필요한 규칙들을 추가해주도록 합니다.

여기서는 이메일과, 그룹정보를 담아보도록 하겠습니다.


NameIDPolicy아래에서 추가로 후술

이렇게 규칙들까지 세팅하고나면 완성입니다!

제대로 세팅되었는지 확인하려면 ADFS Manangement화면의 Service>Endpoints 탭으로 넘어가서 SAML의 endpoint를 확인하고

https://{DOMAIN}/adfs/ls/idpinitiatedsignon

으로 접속하면 아래와 같이 로그인창이 뜨게 됩니다.

[SAML] metadata xml얻기

SAML의 xml은 아래 url에서 얻을 수 있습니다.

https://{DOMAIN}/FederationMetadata/2007-06/FederationMetadata.xml

[SAML] Assertion 얻기

로그인창에서 로그인을 진행할 때, 브라우저의 개발자모드를 켜서 Network탭을 보면 callback이라는 항목이 생기는데

그 안에 saml assertion data가 들어있습니다.

이걸 base64 decoding을 시켜보면 사람이 읽을 수 있는 assertion이 나오게 됩니다.

<samlp:Response ID="_71038709-8f5f-46e0-9ede-f67843231cce" Version="2.0" ...>
...
<NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">seung@ce-window.xxx</NameID>
...
<AudienceRestriction><Audience>https://xxx:5556/dex/callback</Audience></AudienceRestriction>
</Conditions>
<AttributeStatement>
    <Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress">
        <AttributeValue>seung@ce-window.xxx</AttributeValue>
    </Attribute>
    <Attribute Name="http://schemas.microsoft.com/ws/2008/06/identity/claims/role">
        <AttributeValue>Domain Users</AttributeValue>
        <AttributeValue>testtest</AttributeValue>
    </Attribute>
</AttributeStatement>
...
</samlp:Response>

[SAML] Troubleshooting

1. The requested relying party trust ‘’ is unspecified or unsupported. 혹은 response did not contain an assertion

이 문제는 client와 SAML server쪽 모두를 살펴봐야 합니다.

  1. Client쪽의 CA validation을 살펴보기 (Insecure로 설정해보고 다시 시도, certification이 잘못되지 않았는지 살펴보기)
  2. SAML server의 encrypted assertion 기능 끄기(설정의 encryption 탭에서 certificate 삭제)

    이렇게 하면 Assertion이 암호화되지 않고 plain text로 넘어옴

2. The SAML request contained a NameIDPolicy that was not satisfied by the issued token

client쪽에서 요청한 NameID의 포맷이 SAML에서 제공하는 포맷과 일치하지 않을 때 발생

위의 에러같은 경우, 요청은 email address로 요청했으나 기본적으로 ADFS의 SAML에서는 urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified형식으로 보내게됩니다.

그래서 persistent형이든 email형이든 NameIDPolicy에 대한 것들을 지정해주어야 합니다.

email식으로 nameID를 정의하고 싶다면 위에서 진행했던것과 같이 Email을 LDAP에서 받아오고 그걸 NameID로 내보내도록 설정해주어야 합니다.

3. status code of the Response was not Success, was “Requester”

ADFS의 assertion의 attribute와 client에서 받아주는 attribute의 이름이 다를경우 발생

  • ADFS의 SAML assertion이 암호화되어있는지, 암호화되어있다면 client측에서 복호화를 제대로 수행하는지 확인 필요
  • ADFS의 이벤트로그와 실제 받는 assertion의 attribute의 정확한 이름을 표기(assertion을 decoding해서 살펴보면 좋음)

태그:

카테고리:

업데이트:

댓글남기기