Статьи

Добавление пользовательских утверждений в ответ SAML — (Как написать собственный обработчик утверждений для WSO2 Identity Server)

обзор

Последний выпуск WSO2 Identity Server (версия 5.0.0) оснащен «структурой аутентификации приложений», которая обеспечивает большую гибкость при аутентификации пользователей от различных поставщиков услуг, которые используют гетерогенные протоколы. Он имеет несколько точек расширения, которые можно использовать для удовлетворения нескольких индивидуальных требований, обычно встречающихся в корпоративных системах. В этом посте я расскажу о том, как использовать одну из таких точек расширения.

Функциональность будет расширена

Когда SAML Single Sign On используется в корпоративных системах, проверяющая сторона узнает, аутентифицирован ли пользователь или нет, с помощью ответа SAML. На этом этапе проверяющая сторона не знает о других атрибутах аутентифицированного пользователя, которые могут ему понадобиться для бизнес-целей и целей авторизации. Чтобы предоставить эти сведения об атрибутах для проверяющей стороны, спецификация SAML также позволяет отправлять атрибуты в ответе SAML. WSO2 Identity Server поддерживает это «из коробки» через графический интерфейс, предоставленный администраторам. Вы можете обратиться к [1] ​​за подробной информацией об этой функции и деталях конфигурации.

Гибкость, обеспечиваемая этим конкретным расширением, оказывается полезной, когда у нас есть требование добавить дополнительные атрибуты в ответ SAML, кроме атрибутов, доступных в подчеркнутом хранилище пользователей. Мы можем искать внешние источники данных, чтобы предоставить все атрибуты, запрашиваемые доверяющими сторонами.

В примере, который я здесь опишу, мы рассмотрим сценарий, в котором система должна предоставить некоторые локальные атрибуты пользователя, которые хранятся в хранилище пользователя, с некоторыми дополнительными атрибутами, которые я ожидаю получить из внешнего источника данных.
Следующий ответ SAML — это то, что нам нужно отправить проверяющей стороне от WSO2 IS.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<saml2p:Response Destination="https://localhost:9444/acs" ID="faibaccbcepemkackalbbjkihlegenhhigcdjbjk"
                 InResponseTo="kbedjkocfjdaaadgmjeipbegnclbelfffbpbophe" IssueInstant="2014-07-17T13:15:05.032Z"
                 Version="2.0" xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol"
                 xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <saml2:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity"
                  xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">localhost
    </saml2:Issuer>
    <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
        ..........
    </ds:Signature>
    <saml2p:Status>
        <saml2p:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
    </saml2p:Status>
    <saml2:Assertion ID="phmbbieedpcfdhcignelnepkemobepgaaipbjjdk" IssueInstant="2014-07-17T13:15:05.032Z" Version="2.0"
                     xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xs="http://www.w3.org/2001/XMLSchema">
        <saml2:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">localhost</saml2:Issuer>
        <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
            .........
        </ds:Signature>
        <saml2:Subject>
          <saml2:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">Administrator</saml2:NameID>
            <saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
                <saml2:SubjectConfirmationData InResponseTo="kbedjkocfjdaaadgmjeipbegnclbelfffbpbophe"
                                               NotOnOrAfter="2014-07-17T13:20:05.032Z"
                                               Recipient="https://localhost:9444/acs"/>
            </saml2:SubjectConfirmation>
        </saml2:Subject>
        <saml2:Conditions NotBefore="2014-07-17T13:15:05.032Z" NotOnOrAfter="2014-07-17T13:20:05.032Z">
            <saml2:AudienceRestriction>
                <saml2:Audience>carbonServer2</saml2:Audience>
            </saml2:AudienceRestriction>
        </saml2:Conditions>
        <saml2:AuthnStatement AuthnInstant="2014-07-17T13:15:05.033Z">
            <saml2:AuthnContext>
                <saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml2:AuthnContextClassRef>
            </saml2:AuthnContext>
        </saml2:AuthnStatement>
        <saml2:AttributeStatement>
            <saml2:Attribute Name="http://wso2.org/claims/role"
                             NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
                <saml2:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">
                    Internal/carbonServer2,Internal/everyone
                </saml2:AttributeValue>
            </saml2:Attribute>
            <saml2:AttributeStatement>
                <saml2:Attribute Name="http://pushpalanka.org/claims/keplerNumber"
                                 NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
                    <saml2:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">
                        E90836W19881010
                    </saml2:AttributeValue>
                </saml2:Attribute>
                <saml2:Attribute Name="http://pushpalanka.org/claims/status"
                                 NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
                    <saml2:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">
                        active
                    </saml2:AttributeValue>
                </saml2:Attribute>
            </saml2:AttributeStatement>
        </saml2:AttributeStatement>
    </saml2:Assertion>
</saml2p:Response>

В этом ответе у нас есть один локальный атрибут, а именно роль и два дополнительных атрибута: http://pushpalanka.org/claims/keplerNumber и http://pushpalanka.org/claims/status, которые были получены из какого-либо другого метода, который мы можем определить в нашем расширении.

Как?

  1. Реализуйте настраиваемую логику для получения внешних утверждений. Есть только два факта, которые мы должны отметить в этой работе.
    • Пользовательская реализация должна реализовывать интерфейс org.wso2.carbon.identity.application.authentication.framework.handler.claims.ClaimHandler или расширять реализацию интерфейса по умолчанию org.wso2.carbon.identity.application.authentication. framework.handler.claims.impl.DefaultClaimHandler.
    • Карта, возвращаемая методом «public Map <String, String> handleClaimMappings», должна содержать все атрибуты, которые мы хотим добавить в ответ SAML.

    Ниже приведен пример кода, который я написал, придерживаясь вышесказанного. Внешние утверждения могут быть запрошены из базы данных, прочитаны из файла или с использованием любого другого механизма, как требуется.

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    public class CustomClaimHandler implements ClaimHandler {
     
        private static Log log = LogFactory.getLog(CustomClaimHandler.class);
        private static volatile CustomClaimHandler instance;
        private String connectionURL = null;
        private String userName = null;
        private String password = null;
        private String jdbcDriver = null;
        private String sql = null;
     
     
        public static CustomClaimHandler getInstance() {
            if (instance == null) {
                synchronized (CustomClaimHandler.class) {
                    if (instance == null) {
                        instance = new CustomClaimHandler();
                    }
                }
            }
            return instance;
        }
     
        public Map<String, String> handleClaimMappings(StepConfig stepConfig,
                                                       AuthenticationContext context, Map<String, String> remoteAttributes,
                                                       boolean isFederatedClaims) throws FrameworkException {
     
            String authenticatedUser = null;
     
            if (stepConfig != null) {
                //calling from StepBasedSequenceHandler
                authenticatedUser = stepConfig.getAuthenticatedUser();
            } else {
                //calling from RequestPathBasedSequenceHandler
                authenticatedUser = context.getSequenceConfig().getAuthenticatedUser();
            }
     
            Map<String, String> claims = handleLocalClaims(authenticatedUser, context);
            claims.putAll(handleExternalClaims(authenticatedUser));
     
            return claims;
        }
     
     
        /**
         * @param context
         * @return
         * @throws FrameworkException
         */
        protected Map<String, String> handleLocalClaims(String authenticatedUser,
                                                        AuthenticationContext context) throws FrameworkException {
        ....
        }
     
        private Map<String, String> getFilteredAttributes(Map<String, String> allAttributes,
                                                          Map<String, String> requestedClaimMappings, boolean isStandardDialect) {
        ....
        }
     
        protected String getDialectUri(String clientType, boolean claimMappingDefined) {
        ....
        }
     
        /**
         * Added method to retrieve claims from external sources. This results will be merged to the local claims when
         * returning final claim list, to be added to the SAML response, that is sent back to the SP.
         *
         * @param authenticatedUser : The user for whom we require claim values
         * @return
         */
        private Map<String, String> handleExternalClaims(String authenticatedUser) throws FrameworkException {
            Map<String, String> externalClaims = new HashMap<String, String>();
            externalClaims.put("http://pushpalanka.org/claims/keplerNumber","E90836W19881010");
            externalClaims.put("http://pushpalanka.org/claims/status","active");
            return externalClaims;
        }
    }
  2. Удалите скомпилированный OSGI-пакет в IS_HOME / repository / components / dropins. (Мы разработали его как пакет OSGI, так как нам нужно получать локальные заявки, используя RealmService. Вы можете найти полный пакет и исходный код здесь )
  3. Укажите WSO2 Identity Server, чтобы использовать новую пользовательскую реализацию, которую мы имеем.

В IS_HOME / repository / conf / security / applicationauthentication.xml настройте имя нового обработчика. (в элементе «ApplicationAuthentication.Extensions.ClaimHandler».)

1
   <ClaimHandler>com.wso2.sample.claim.handler.CustomClaimHandler</ClaimHandler>

Теперь, если посмотреть на сгенерированный ответ SAML, мы увидим добавленные внешние атрибуты.

Ура!

[1] — https://docs.wso2.com/display/IS500/Adding+a+Service+Provider.