Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Делимся навыком по интеграции SSO средствами SAML 2.0

Anna | 4.06.2014 | нет комментариев
1. Предыстория

Не смотря на то, что функция централизованного входа (Single Sign On, SSO) существует, обсуждается и используется теснее давным-давно, на практике ее внедрение нередко сопровождается преодолением самых разных задач. Целью данной статьи будет показать, как реализовать примитивный личный Service Provider 1(SP) для SAML 2.0 identity provider (idP) и с его поддержкой осуществить интеграции SSO в Java Web приложение.

Одним из наших последних планов была подготовка и кластеризация портального решения для огромного института. В рамках плана мы столкнулись с задачей реализации (а также кластеризации) функции цельной аутентификации для следующих систем:

  1. Liferay version 6.1.20-ee-ga2.
  2. Примитивное java web-приложение.
  3. Google apps.

Со стороны клиента были выдвинуты основные требования построения SSO:

  1. Для построения SSO должен применяться протокол SAML 2.0.
  2. Требуется интеграция с Jasig CAS для поддержания работы теснее существующих систем.
  3. LDAP применяется для проверки аутентификации пользователей.

В качестве idP решили применять Shibboleth (http://shibboleth.net/about/index.html) как open source-систему, реализующую в полном объеме протоколы SAML 1.0 && SAML 2.0.

Трудные моменты, с которыми мы столкнулись при решении данной задачи:

  1. Неимение экспертизы по работе с протоколом SAML 2.0 и продуктом Shibboleth.
  2. Сырая и еще не довольно отлично структурированная документация по Shibboleth от изготовителя.
  3. Неимение добротных примеров по реализации Service Provider’а для интеграции SSO в свое Java Web-приложение.

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

2. Для кого предуготовлена статья?

Данная статья ориентирована на следующую аудиторию:

  1. Разработчики, интегрирующие функцию SSO в своих планах средствами SAML 2.0.
  2. Java-Разработчики, которым необходим утилитарный пример интеграции в свое приложение функции SSO средствами SAML 2.0.
  3. Java-Разработчики, которые хотят опробовать в качестве SSO Identity Provider’а (idP) компонент Shibboleth.

Для понимания статьи рекомендуется иметь минимальные познания по протоколу SAML 2.0.

3. Основные компоненты работы SSO

На схеме ниже изображена всеобщая схема функционирования нашего централизованного входа.

Основные компоненты и моменты, подмеченные на диаграмме:

  1. В системе SSO участвует 2 приложения:
    a. Java Web App — обыкновенное Java Web-приложение
    b. Google Apps — приложение из облачных служб Google. Мы будем применять его лишь для проверки работы SSO.
  2. SP Filter — реализация Service Provider, функцией которого будет взаимодействие с Shibboleth idP средствами отправки и разбора сообщений SAML 2.0
  3. Shibboleth idP — приложение для осуществления аутентификации и авторизации средствами SAML 1.0 и SAML 2.0.
  4. Tomcat AS — Java Application Server.
  5. Взаимодействие между SP filter и Shibboleth idP происходят по защищенному протоколу HTTPS.

Примечание: На диаграмме Shibboleth idP и Java Web-приложение физически разнесены на различные серверы Tomcat. Впрочем вы можете развернуть окружение на одном сетевом узле, применяя каждого лишь один инстанс Tomcat.

4. Настраиваем окружение для Shibboleth idP
Установка и конфигурация shibboleth idP:

1. Скачиваем последнюю версию idP здесь shibboleth.net/downloads/identity-provider/latest/2 и разархивируем в произвольное место $shDistr.
2. Проверяем, что переменная JAVA_HOME установлена правильно3.
Запускаем $shDistr/install.sh (будем считать, что применяется UNIX-сходственная операционная систем).4

Инсталлятор запросит следующую информацию, которую следует удерживать в уме:

  • путь установки (скажем: /opt/shib)
  • наименование idP сервера (скажем: idp.local.ru).

    Добавьте idP сервер в список алиасов для локалхоста в файле /etc/hosts:
    127.0.0.1 localhost idp.local.ru

  • пароль для java key store, тот, что генерируется в процессе установки (скажем: 12345).

Дальше проверяем, что процесс установки удачно завершен.

Введем обозначения:

  • $shHome — директория, куда был установлен Shibboleth;
  • $shHost — наименование idP сервера;
  • $shPassword — пароль для java key store (JKS).

3. Определяем, какие признаки и из каких источников будут извлекаться idP. В нашем случае мы будем передавать login пользователя. Добавляем изложение признака в файл $shHome/conf/attribute-resolver.xmlпозже элемента <resolver:AttributeDefinition id=«transientId» xsi:type=«ad:TransientId»>.

<resolver:AttributeDefinition xsi:type="PrincipalName"     
             xmlns="urn:mace:shibboleth:2.0:resolver:ad"  id="userLogin" >
      <resolver:AttributeEncoder xsi:type="SAML1String" 
             xmlns="urn:mace:shibboleth:2.0:attribute:encoder" name="userLogin" />
      <resolver:AttributeEncoder xsi:type="SAML2String" 
             xmlns="urn:mace:shibboleth:2.0:attribute:encoder" name="userLogin" />
</resolver:AttributeDefinition>

Примечание: в этом же файле дозволено настроить приобретение признаков из разных источников данных как скажем LDAP либо DBMS через JDBC. Подробнее здесьhttps://wiki.shibboleth.net/confluence/display/SHIB2/IdPAddAttribute

4. Для того Дабы idP отдавал данный признак SAML SP фильтру описываем его в файле $shHome/conf/attribute-filter.xml.

<afp:AttributeFilterPolicy id="releaseUserLoginToAnyone">
    <afp:PolicyRequirementRule xsi:type="basic:ANY"/>
    <afp:AttributeRule attributeID="userLogin">
        <afp:PermitValueRule xsi:type="basic:ANY"/>
    </afp:AttributeRule>
</afp:AttributeFilterPolicy>

Примечание: Тут дозволено задать больше трудное и правильное правило. Скажем, дозволено указать, Дабы данный признак передавался только определенному SAML SP.

5. Наш Shibboleth idP должен знать о тех узлах, с которыми он может взаимодействовать – так называемые relying party (https://wiki.shibboleth.net/confluence/display/SHIB2/IdPUnderstandingRP). Эта информация хранится в файле $shHome/conf/relying-party.xml.
Открываем файл и добавляем в него дальнейший элемент:

<rp:RelyingParty id="sp.local.ru" provider="https://idp.local.ru/idp/shibboleth" 
                                        defaultSigningCredentialRef="IdPCredential">
   <rp:ProfileConfiguration xsi:type="saml:SAML2SSOProfile"
       signResponses="never" signAssertions="never"
       encryptNameIds="never" encryptAssertions="never" />
</rp:RelyingParty>

Тут мы указываем, что для SP с id = «sp.local.ru» будет применяться idP с id=”https://idp.local.ru/idp/shibboleth“.

Добавьте SP в список алиасов для локалхоста в файле /etc/hosts:
127.0.0.1 localhost sp.local.ru

Также даем указание shibboleth idP не подписывать SAML 2.0 результаты и комплект assertions (заявлений). До нынешнего времени наш shibboleth idP не имел представления, что из себя представляет компонент с id=«sp.local.ru». Время поправить данный момент. Идем на дальнейший шаг.

6. Добавляем изложение нашего SAML 2.0 SP фильтра. Для этого в файле $shHome/conf/relying-party.xmlопределяем метаинформацию для нашего SP, рядом с элементом <metadata:MetadataProvider id=«IdPMD» xsi:type=«metadata:FilesystemMetadataProvider»… >

<metadata:MetadataProvider id="spMD" xsi:type="metadata:FilesystemMetadataProvider"
                            metadataFile="/opt/shib/metadata/saml-sp-metadata.xml"/>

Мы дали указанию shibboleth idP искать определение SP в файле /opt/shib/metadata/saml-sp-metadata.xml. Создаем данный файл со дальнейшим содержимым:

<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="sp.local.ru">
  <md:SPSSODescriptor AuthnRequestsSigned="false" ID="sp.local.ru" 
     protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
    <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
       Location="https://sp.local.ru:8443/sso/acs" index="1" isDefault="true"/>
  </md:SPSSODescriptor>
</md:EntityDescriptor>

Тут необходимо понимать следующее:

  • наш SAML 2.0 SP имеет идентификатор «sp.local.ru»
  • адрес, по которому shibboleth idP будет возвращать SAML 2.0 сообщенияLocation=”https://sp.local.ru:8443/sso/acs указан в элементе md:AssertionConsumerService.
  • И, наконец, параметр Binding=«urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST» показывает, что результат SP будет отправляться от shibboleth idP через редирект броузера.

7. Осталось предпочесть метод, с которым shibboleth idP будет проводить реальную аутентификацию пользователей. В окружении production здесь могут быть самые различные конфигурации, включая аутентификацию через LDAP, DBMS и даже CAS. Тут, как говорится, на вкус и цвет. Мы будем применять теснее включенный Remote User Authentication механизм (https://wiki.shibboleth.net/confluence/display/SHIB2/IdPAuthRemoteUser). При приобретении запроса на аутентификацию shibboleth idP будет глядеть в контексте переменную REMOTE_USER. Если такая переменная есть, то shibboleth idP будет считать, что пользователь теснее прошел аутентификацию через внешнюю систему (скажем, через Web Apache сервер). чтобы не усложнять данную статью мы решили пойти на хитрость и устанавливать переменную REMOTE_USER неестественно для всякого запроса.
Это будет сделано в дальнейшем разделе при настройке Tomcat AS (пункт 7).

Настройка Shibboleth закончена, поздравляем :)

Установка и конфигурация Tomcat для shibboleth idP:
  1. Скачиваем томкат 6 http://tomcat.apache.org/download-60.cgi, разархивируем в произвольную папку$tomcatHome (скажем: в opt/shib-tomcat).

    Значимо подметить, что в данный момент Tomcat 7.* не может применяться в случае, когда общение между SP и idP происходит напрямую по протоколу SOAP. И правда в примерах этой статьи мы будем применять прямые редиректы броузера для осуществления данных коммуникаций, мы все же рекомендуем применять Tomcat версии 6.

  2. Копируем папку $shDistr/endorsed в папку $tomcatHome.
  3. Изменяем файл $tomcatHome/bin/setenv.sh, ставим настройки для динамической и перманентной памяти JVM:
    JAVA_OPTS=”$JAVA_OPTS -Xmx512m -XX:MaxPermSize=128m”
  4. Скачиваем библиотеку (https://build.shibboleth.net/nexus/content/repositories/releases/edu/internet2/middleware/security/tomcat6/tomcat6-dta-ssl/1.0.0/tomcat6-dta-ssl-1.0.0.jar) для поддержки протокола SOAP в процессе общения между SP и idP в папку $tomcatHome/lib.
    Открываем $tomcatHome/conf/server.xml и настраиваем доступ к томкату через HTTPS.
    Для этого определяем дальнейший элемент Connector:

    <Connector port="8443"
           protocol="org.apache.coyote.http11.Http11Protocol"
           SSLImplementation="edu.internet2.middleware.security.tomcat6.DelegateToApplicationJSSEmplementation"
           scheme="https"
           SSLEnabled="true"
           clientAuth="want"
               keystoreFile="$shHome/credentials/idp.jks"
               keystorePass="$shPassword" />
    

    Не позабудьте заменить переменные $shHome и $shPassword реальными значениями.

  5. Деплоим приложение shibboleth idP в Tomcat. Для этого создаем файл
    $tomcatHome/conf/Catalina/localhost/idp.xml с содержимым:

    <Context docBase="$shHome/war/idp.war" privileged="true"   
    antiResourceLocking="false" antiJARLocking="false" unpackWAR="false"  
    swallowOutput="true" />
    

    Не позабудьте заменить переменные $shHome реальным значением

  6. Скомпилировать5 дальнейший класс в произвольную библиотеку tomcat-valve.jar:
      public class RemoteUserValve extends ValveBase{
        public RemoteUserValve() {
            }
    
            @Override
            public void invoke(final Request request, final Response response)
             throws IOException, ServletException {
               final String username = "idpuser";
            final String credentials = "idppass";
            final List<String> roles = new ArrayList<String>();
            final Principal principal = new GenericPrincipal(null, username, credentials, 
                                                                  roles);
    
            request.setUserPrincipal(principal);
            getNext().invoke(request, response);
          }
    
        }
    

    Библиотеку положить в папку ${tomcatHome}/lib. И в файл server.xml добавить строчку
    <Valve сlassName=«ru.eastbanctech.java.web.RemoteUserValve» /> внутри элемента
    <Host name=«localhost» appBase=«webapps» ..>. Позже старта сервера при обращении к любому приложению Tomcat сервера механически будет проставляться параметр REMOTE_USER со значением idpuser в контекст реквеста.

5. Реализация SP Filter для протокола SAML 2.0

Для реализации данного решения сделаем SAML 2.0 Service Provider фильтр, задачами которого будет:

  1. Фильтр пропускает запросы на публичные источники, для которых не необходима аутентификация.
  2. Фильтр хранит в себе информацию по аутентифицированному пользователю, Дабы сократить число обращений к Shibboleth idP.
  3. Фильтр создает SAML 2.0 запрос на аутентификацию в виде SAML 2.0 сообщения (AuthN) и средствами редиректа браузера перенаправляет пользователя к Shibboleth idP.
  4. Фильтр обрабатывает результат от Shibboleth idP, и если процесс аутентификация пользователя прошла удачно, система показывает изначально запрашиваемый источник.
  5. Фильтр удаляет локальную сессию при логауте пользователя из Java Web-приложения.
  6. При этом сессия на shibboleth idP продолжает оставаться энергичной.

С технической точки зрения фильтр будет представлять собой реализацию стандартного интерфейса javax.filter.Filter. Область действия фильтра будет задаваться в определенном web-приложении.

Сейчас, когда функциональность фильтра внятна, приступим к реализации:
1. Создаем скелет maven плана
Дозволено сделать через плагин mvn:archetype:
mvn archetype:generate -DgroupId=ru.eastbanctech.java.web -DartifactId=saml-sp-filter -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
Параметры groupId и artefactId можете указать на свой вкус и цвет.
Конструкция нашего плана в Intellij Idea будет выглядеть так:

2. Файл сборки pom.xml:

Код

<source lang="xml">
<project xmlns="http://maven.apache.org/POM/4.0.0" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"           
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>ru.eastbanctech.web</groupId>
    <artifactId>saml-sp-filter</artifactId>
    <name>${project.artifactId}</name>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <properties>
                <jdk.version>1.6</jdk.version>
        <encoding>UTF-8</encoding>
        <project.build.sourceEncoding>${encoding}</project.build.sourceEncoding>
        <project.reporting.outputEncoding>${encoding}</project.reporting.outputEncoding>
    </properties>
    <build>
      <pluginManagement>
        <plugins>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>2.5.1</version>
            <configuration>
              <encoding>${encoding}</encoding>
              <sourсe>${jdk.version}</sourсe>
              <target>${jdk.version}</target>
            </configuration>
          </plugin>
        </plugins>
      </pluginManagement>
    </build>
    <dependency>
      <groupId>org.opensaml</groupId>
      <artifactId>opensaml</artifactId>
      <version>2.5.1-1</version>
    </dependency>

    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.5</version>
      <scope>provided</scope>
    </dependency>
        <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>log4j-over-slf4j</artifactId>
          <version>1.7.1</version>
        </dependency>

        <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-api</artifactId>
          <version>1.7.1</version>
        </dependency>
    </dependencies>
</project>

3. Сердцем нашего фильтра будет класс SAMLSPFilter:

public class SAMLSPFilter implements Filter {
   public static final String SAML_AUTHN_RESPONSE_PARAMETER_NAME = "SAMLResponse";        
   private static Logger log = LoggerFactory.getLogger(SAMLSPFilter.class);
   private FilterConfig filterConfig;
   private SAMLResponseVerifier checkSAMLResponse;
   private SAMLRequestSender samlRequestSender; 

   @Override
   public void init(javax.servlet.FilterConfig config) throws ServletException {
     OpenSamlBootstrap.init();
     filterConfig = new FilterConfig(config);
     checkSAMLResponse = new SAMLResponseVerifier();
     samlRequestSender = new SAMLRequestSender();     
   }

   @Override
   public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,             
                          FilterChain chain) throws IOException, ServletException {
     HttpServletRequest request = (HttpServletRequest) servletRequest;
     HttpServletResponse response = (HttpServletResponse) servletResponse;
  /*
  ШАГ 1: Игнорируем запросы, предуготовленные не для фильтра
  ШАГ 2: Если пришел результат от Shibboleth idP, обрабатываем его
  ШАГ 3: Если получен запрос на logout, удаляем локальную сессию
  ШАГ 4: Если пользователь теснее аутентифицирован, даем доступ к источнику
  ШАГ 5: Создаем SAML запрос на аутентификацию и отправляем пользователя к
         Shibboleth idP
  */ 
}
}

В класс FilterConfig определим основные переменные фильтра (область действия фильтра, наименование idP, путь до метадаты idP, наименование SP и т.д.). Значения этих параметров будут задаваться в конфигурационном файле web.xml Java Web-приложения. Объекты checkSAMLResponse и samlRequestSender необходимы для проверки валидности SAML 2.0 сообщений и отправки запроса аутентификации. К ним возвратимся чуть позднее.

Код

public class FilterConfig {
  /**
  * The parameters below should be defined in web.xml file of Java Web Application
  */
  public static final String EXCLUDED_URL_PATTERN_PARAMETER = "excludedUrlPattern";
  public static final String SP_ACS_URL_PARAMETER           = "acsUrl";
  public static final String SP_ID_PARAMETER                = "spProviderId";     
  public static final String SP_LOGOUT_URL_PARAMETER        = "logoutUrl";
  public static final String IDP_SSO_URL_PARAMETER          = "idProviderSSOUrl";

  private String excludedUrlPattern;
  private String acsUrl;
  private String spProviderId;
  private String logoutUrl;
  private String idpSSOUrl;

  public FilterConfig(javax.servlet.FilterConfig config) {
    excludedUrlPattern = config.getInitParameter(EXCLUDED_URL_PATTERN_PARAMETER);
    acsUrl = config.getInitParameter(SP_ACS_URL_PARAMETER);
    spProviderId = config.getInitParameter(SP_ID_PARAMETER);
    idpSSOUrl = config.getInitParameter(IDP_SSO_URL_PARAMETER);
    logoutUrl = config.getInitParameter(SP_LOGOUT_URL_PARAMETER);
  } 
  // getters and should be defined below 
}
Класс OpenSamlBootstrap инициализирует библиотеки для работы с SAML 2.0 сообщениями: 
public class OpenSamlBootstrap extends DefaultBootstrap {
  private static Logger log = LoggerFactory.getLogger(OpenSamlBootstrap.class);
  private static boolean initialized;
  private static String[] xmlToolingConfigs = {
    "/default-config.xml",
    "/encryption-validation-config.xml",
    "/saml2-assertion-config.xml",
    "/saml2-assertion-delegation-restriction-config.xml",
    "/saml2-core-validation-config.xml",
    "/saml2-metadata-config.xml",
    "/saml2-metadata-idp-discovery-config.xml",
    "/saml2-metadata-query-config.xml",
    "/saml2-metadata-validation-config.xml",
    "/saml2-protocol-config.xml",
    "/saml2-protocol-thirdparty-config.xml",
    "/schema-config.xml",
    "/signature-config.xml",
    "/signature-validation-config.xml"
  };

  public static synchronized void init() {
    if (!initialized) {
      try {
        initializeXMLTooling(xmlToolingConfigs);
      } catch (ConfigurationException e) {
        log.error("Unable to initialize opensaml DefaultBootstrap", e);
      }
      initializeGlobalSecurityConfiguration();
      initialized = true;
    }
  }
}

Комплект XML файлов содержит инструкции, как разбирать элементы SAML 2.0 сообщений и содержится в библиотеке opensaml-*.jar, которая подключится при сборке плана через maven.

ШАГ 1: Игнорируем запросы, не предуготовленные для фильтра 
Параметр excludedUrlPattern, тот, что заклюи SAML сообщений.

public static String SAMLObjectToString(XMLObject samlObject) {
  try {
    Marshaller marshaller =  
        org.opensaml.Configuration.getMarshallerFactory().getMarshaller(samlObject);
    org.w3c.dom.Element authDOM = marshaller.marshall(samlObject);
    StringWriter rspWrt = new StringWriter();
    XMLHelper.writeNode(authDOM, rspWrt);
    return rspWrt.toString();
  } catch (Exception e) {
    e.printStackTrace();   
  }
  return null;
}

Сотворим класс SAMLResponseVerifier, в тот, что разместим функциональность для проверки SAML 2.0 сообщений, полученных от shibboleth idP. В основном способе verify(..) реализуем следующие проверки:

  • Данному SAML 2.0 результату от idP предшествовал SAML 2.0 запрос, отправленный нашим фильтром.
  • Сообщение содержится позитивный итог аутентификации пользователя через shibboleth idP.
  • Основные заявления в результате SAML 2.0 исполнены (срок давности сообщения не истек, данное сообщение предуготовлено для нашего SP и т.д.).
Код


public class SAMLResponseVerifier {
  private static Logger log = LoggerFactory.getLogger(SAMLResponseVerifier.class);
  private SAMLRequestStore samlRequestStore = SAMLRequestStore.getInstance();

  public void verify(SAMLMessageContext<Response, SAMLObject, NameID> samlMessageContext) 
    throws SAMLException {
    Response samlResponse = samlMessageContext.getInboundSAMLMessage();
    log.debug("SAML Response message : {}", SAMLUtils.SAMLObjectToString(samlResponse));
    verifyInResponseTo(samlResponse);
    Status status = samlResponse.getStatus();
    StatusCode statusCode = status.getStatusCode();
    String statusCodeURI = statusCode.getValue();
    if (!statusCodeURI.equals(StatusCode.SUCCESS_URI)) {
      log.warn("Incorrect SAML message code : {} ", 
                   statusCode.getStatusCode().getValue());
      throw new SAMLException("Incorrect SAML message code : "   statusCode.getValue());
    }
    if (samlResponse.getAssertions().size() == 0) {
      log.error("Response does not contain any acceptable assertions");
      throw new SAMLException("Response does not contain any acceptable assertions");
    }

    Assertion assertion = samlResponse.getAssertions().get(0);
    NameID nameId = assertion.getSubject().getNameID();
    if (nameId == null) {
      log.error("Name ID not present in subject");
      throw new SAMLException("Name ID not present in subject");
    }
    log.debug("SAML authenticated user "   nameId.getValue());
    verifyConditions(assertion.getConditions(), samlMessageContext);  
}

private void verifyInResponseTo(Response samlResponse) {  
  String key = samlResponse.getInResponseTo();
  if (!samlRequestStore.exists(key)) { {
    log.error("Response does not match an authentication request");
    throw new RuntimeException("Response does not match an authentication request");
  }
  samlRequestStore.removeRequest(samlResponse.getInResponseTo());
}

private void verifyConditions(Conditions conditions, SAMLMessageContext samlMessageContext) throws SAMLException{
    verifyExpirationConditions(conditions);
    verifyAudienceRestrictions(conditions.getAudienceRestrictions(), samlMessageContext);
}
private void verifyExpirationConditions(Conditions conditions) throws SAMLException {
  log.debug("Verifying conditions");
  DateTime currentTime = new DateTime(DateTimeZone.UTC);
  log.debug("Current time in UTC : "   currentTime);
  DateTime notBefore = conditions.getNotBefore();
  log.debug("Not before condition : "   notBefore);
  if ((notBefore != null) && currentTime.isBefore(notBefore))
    throw new SAMLException("Assertion is not conformed with notBefore condition");

  DateTime notOnOrAfter = conditions.getNotOnOrAfter();
  log.debug("Not on or after condition : "   notOnOrAfter);
  if ((notOnOrAfter != null) && currentTime.isAfter(notOnOrAfter))
    throw new SAMLException("Assertion is not conformed with notOnOrAfter condition");
}

 private void verifyAudienceRestrictions(
 List<AudienceRestriction> audienceRestrictions,
            SAMLMessageContext<?, ?, ?> samlMessageContext)
            throws SAMLException{
// TODO: Audience restrictions should be defined below<sup>7</sup> 
}
}

В способе verifyInResponseTo делается проверка о том, что SAML 2.0-результату предшествовал запрос от нашего фильтра. Для реализации применяется объект класса SAMLRequestStore, в котором хранятся отправленные SAML 2.0 запросы к shibboleth idP.

Код

final public class SAMLRequestStore {
  private Set<String> samlRequestStorage = new HashSet<String>();  
  private IdentifierGenerator identifierGenerator = new RandomIdentifierGenerator();
  private static SAMLRequestStore instance = new SAMLRequestStore();

  private SAMLRequestStore() {
  }

  public static SAMLRequestStore getInstance() {
    return instance;
  }

  public synchronized void storeRequest(String key) {
    if (samlRequestStorage.contains(key))
     throw new RuntimeException("SAML request storage has already contains key "   key);

    samlRequestStorage.add(key);
  }
  public synchronized String storeRequest(){
     String key = null;
     while (true) {
       key = identifierGenerator.generateIdentifier(20);
       if (!samlRequestStorage.contains(key)){
         storeRequest(key);
         break;
       }
     }
    return key;
  }
  public synchronized boolean exists(String key) {
    return samlRequestStorage.contains(key);
  }

  public synchronized void removeRequest(String key){
    samlRequestStorage.remove(key);
  }
}

Для создании локальной сессии будем применять свой класс SAMLSessionManager. Его задачей будет создавать/уничтожать локальные сессии, которая представляет из себя объект дальнейшего класса SAMLSessionInfo.

public class SAMLSessionInfo {
  private String nameId;
  private Map<String, String> attributes;
  private Date validTo;
  public SAMLSessionInfo(String nameId, Map<String, String> attributes, Date validTo) {
    this.nameId = nameId;
    this.attributes = attributes;
    this.validTo = validTo;
  }
   // getters should be defined below

}

Собственно сам класс SAMLSessionManager, тот, что создает и уничтожает локальные SAML сессии в Session контексте cервлета, применяя SAMLContext.

Код

<source lang="java">
public class SAMLSessionManager {
  public static String SAML_SESSION_INFO = "SAML_SESSION_INFO";
  private static SAMLSessionManager instance = new SAMLSessionManager();

  private SAMLSessionManager() {
  }

  public static SAMLSessionManager getInstance() {
    return instance;
  }

  public void createSAMLSession(HttpSession session, SAMLMessageContext<Response, 
         SAMLObject, NameID> samlMessageContext) {
    List<Assertion> assertions =
                       samlMessageContext.getInboundSAMLMessage().getAssertions();
    NameID nameId = (assertions.size() != 0 && assertions.get(0).getSubject() != null) ? 
                     assertions.get(0).getSubject().getNameID() : null;
    String nameValue = nameId == null ? null : nameId.getValue();
    SAMLSessionInfo samlSessionInfo = new SAMLSessionInfo(nameValue,
                                      getAttributesMap(getSAMLAttributes(assertions)),
                                      getSAMLSessionValidTo(assertions));
    session.setAttribute(SAML_SESSION_INFO, samlSessionInfo);
  }

  public boolean isSAMLSessionValid(HttpSession session) {
        SAMLSessionInfo samlSessionInfo = (SAMLSessionInfo)
                              session.getAttribute(SAML_SESSION_INFO);
        if (samlSessionInfo == null)
            return false;
        return samlSessionInfo.getValidTo() == null || new 
                 Date().before(samlSessionInfo.getValidTo());
  }

  public void destroySAMLSession(HttpSession session) {          
    session.removeAttribute(SAML_SESSION_INFO);
  }

  public List<Attribute> getSAMLAttributes(List<Assertion> assertions) {
    List<Attribute> attributes = new ArrayList<Attribute>();
    if (assertions != null) {
      for (Assertion assertion : assertions) {
        for (AttributeStatement attributeStatement : 
                                 assertion.getAttributeStatements()) {
          for (Attribute attribute : attributeStatement.getAttributes()) {
             attributes.add(attribute);
          }
        }
     }
   }
   return attributes;
 }

 public Date getSAMLSessionValidTo(List<Assertion> assertions) {
   org.joda.time.DateTime sessionNotOnOrAfter = null;
   if (assertions != null) {
     for (Assertion assertion : assertions) {
       for (AuthnStatement statement : assertion.getAuthnStatements()) {
         sessionNotOnOrAfter = statement.getSessionNotOnOrAfter();
       }
     }
   }

   return sessionNotOnOrAfter != null ? 
           sessionNotOnOrAfter.toCalendar(Locale.getDefault()).getTime() : null;
 }
 public Map<String, String> getAttributesMap(List<Attribute> attributes) {
   Map<String, String> result = new HashMap<String, String>();
   for (Attribute attribute : attributes) {
     result.put(attribute.getName(), attribute.getDOM().getTextContent());
   }
   return result;
 }
      }

Шаг 3: Если получен запрос на logout, удаляем локальную сессию

if (getCorrectURL(request).equals(filterConfig.getLogoutUrl())) {
  log.debug("Logout action: destroying SAML session.");
  SAMLSessionManager.getInstance().destroySAMLSession(request.getSession());
  chain.doFilter(request, response);
  return;
}

Примечание: стоит подметить, что сессия остается энергичной на shibboleth idP и при дальнейшем запросе на аутентификацию shibboleth idP легко вернет нам энергичную сессию. Реализация же глобального logout требует дополнительных настроек и до версии 2.4.0 не поддерживалась shibboleth idP. Подробнее дозволено почитать здесь https://wiki.shibboleth.net/confluence/display/SHIB2/SLOIssues 

Шаг 4: Если пользователь теснее аутентифицирован, даем доступ к источнику

Если пользователь имеет энергичную SAML сессию в нашем фильтре, то даем пользователю данный источник.

if (SAMLSessionManager.getInstance().isSAMLSessionValid(request.getSession())) {
     log.debug("SAML session exists and valid: grant access to secure resource");
     chain.doFilter(request, response);
     return;
}

Шаг 5: Создаем SAML запрос на аутентификацию и отправляем пользователя к
Shibboleth idP

log.debug("Sending authentication request to idP");
try {
  samlRequestSender .sendSAMLAuthRequest(request, response,
    filterConfig.getSpProviderId(), filterConfig.getAcsUrl(), 
    filterConfig.getIdpSSOUrl());
} catch (Exception e) {
   throw new ServletException(e);
 }

Класс SAMLRequestSender создает, кодирует и отправляет запросы в виде SAML 2.0-сообщений.

Код

<source lang="java">
public class SAMLRequestSender {
  private static Logger log = LoggerFactory.getLogger(SAMLRequestSender.class);
  private SAMLAuthnRequestBuilder samlAuthnRequestBuilder = 
                                                   new SAMLAuthnRequestBuilder();
  private MessageEncoder messageEncoder = new MessageEncoder();

  public void sendSAMLAuthRequest(HttpServletRequest request, HttpServletResponse 
     servletResponse, String spId, String acsUrl, String idpSSOUrl) throws Exception {
    String redirectURL;
    String idpUrl = idpSSOUrl;
    AuthnRequest authnRequest = samlAuthnRequestBuilder.buildRequest(spId, acsUrl, 
                                   idpUrl);
    // store SAML 2.0 authentication request
    String key = SAMLRequestStore.getInstance().storeRequest();
    authnRequest.setID(key);
    log.debug("SAML Authentication message : {} ", 
                            SAMLUtils.SAMLObjectToString(authnRequest));
    redirectURL = messageEncoder.encode(authnRequest, idpUrl, request.getRequestURI());

    HttpServletResponseAdapter responseAdapter = 
                    new HttpServletResponseAdapter(servletResponse, request.isSecure());
    HTTPTransportUtils.addNoCacheHeaders(responseAdapter);
    HTTPTransportUtils.setUTF8Encoding(responseAdapter);
    responseAdapter.sendRedirect(redirectURL);

  }

  private static class SAMLAuthnRequestBuilder {

    public AuthnRequest buildRequest(String spProviderId, String acsUrl, String idpUrl){
      /* Building Issuer object */
      IssuerBuilder issuerBuilder = new IssuerBuilder();
      Issuer issuer =
                     issuerBuilder.buildObject("urn:oasis:names:tc:SAML:2.0:assertion",
                                                "Issuer", "saml2p");
      issuer.setValue(spProviderId);

      /* Creation of AuthRequestObject */
      DateTime issueInstant = new DateTime();
      AuthnRequestBuilder authRequestBuilder = new AuthnRequestBuilder();

      AuthnRequest authRequest =
                    authRequestBuilder.buildObject(SAMLConstants.SAML20P_NS,
                            "AuthnRequest", "saml2p");
      authRequest.setForceAuthn(false);
      authRequest.setIssueInstant(issueInstant);
      authRequest.setProtocolBinding(SAMLConstants.SAML2_POST_BINDING_URI);
      authRequest.setAssertionConsumerServiceURL(acsUrl);
      authRequest.setIssuer(issuer);
      authRequest.setNameIDPolicy(nameIdPolicy);
      authRequest.setVersion(SAMLVersion.VERSION_20);
      authRequest.setDestination(idpUrl);

      return authRequest;
    }
  }

  private static class MessageEncoder extends HTTPRedirectDeflateEncoder {
    public String encode(SAMLObject message, String endpointURL, String relayState) 
                                         throws MessageEncodingException {
      String encodedMessage = deflateAndBase64Encode(message);
      return buildRedirectURL(endpointURL, relayState, encodedMessage);
    }
    public String buildRedirectURL(String endpointURL, String relayState, String message)
                                                        throws MessageEncodingException {
      URLBuilder urlBuilder = new URLBuilder(endpointURL);
      List<Pair<String, String>> queryParams = urlBuilder.getQueryParams();
      queryParams.clear();
      queryParams.add(new Pair<String, String>("SAMLRequest", message));
      if (checkRelayState(relayState)) {
         queryParams.add(new Pair<String, String>("RelayState", relayState));
      }
      return urlBuilder.buildURL();
    }
  }

}

SAML 2.0-сообщение с инструкцией по аутентификации пользователя создается в способе buildRequest и представляет из себя XML объект:

<saml2p:AuthnRequest xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol"
     AssertionConsumerServiceURL="https://sp.local.ru:8443/sso/acs"
     Destination="https://idp.local.ru:8443/idp/profile/SAML2/Redirect/SSO" 
     ForceAuthn="false"
     ID="_0ddb303f9500839762eabd30e7b1e3c28b596c69" 
     IssueInstant="2013-09-12T09:46:41.882Z"
     ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0">
    <saml2p:Issuer  
       xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:assertion">sp.local.ru</saml2p:Issuer>
</saml2p:AuthnRequest>

Параметр AssertionConsumerServiceURL задает URL, по которому shibboleth idP будет возвращать результат, а параметр ProtocolBinding указывает каким образом возвращать результат нашему фильтру (POST способ протокола HTTP)
Параметр ID определяет идентификатор сообщения, тот, что мы сберегаем при отправке сообщения
String key = SAMLRequestStore.getInstance().storeRequest();
и проверяем при разборе сообщения в способе verifyInResponseTo класса SAMLResponseVerifier.

Элемент saml2p:Issuer определяет имя нашего SP. Применяя значение saml2p:Issuer shibboleth idP определяет от какого SP прислан запрос на аутентификацию, и как его необходимо обрабатывать (через метадату SP).

В результат на приведенное выше SAML 2.0 сообщение мы получим результат от idP в виде SAML 2.0 сообщения в XML формате:

Код

<source lang="xml">
<saml2p:Response xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" 
     Destination="https://sp.local.ru:8443/sso/acs" 
     ID="_9c5e6028df334510cce22409ddbca6ac"
     InResponseTo="_0ddb303f9500839762eabd30e7b1e3c28b596c69" 
     IssueInstant="2013-09-12T10:13:35.177Z" Version="2.0">
   <saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" 
             Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">       

https://idp.local.ru/idp/shibboleth

   </saml2:Issuer>
<saml2p:Status>
    <saml2p:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
</saml2p:Status>
<saml2:Assertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" 
    ID="_0a299e86f4b17b5e047735121a880ccb" IssueInstant="2013-09-12T10:13:35.177Z"  
    version="2.0">
    <saml2:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">

https://idp.local.ru/idp/shibboleth

    </saml2:Issuer>
    <saml2:Subject>
      <saml2:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" 
        NameQualifier="https://idp.local.ru/idp/shibboleth">
        _f1de09ee54294d4b5ddeb3aa5e6d2aab
      </saml2:NameID>
      <saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
        <saml2:SubjectConfirmationData Address="127.0.0.1"
          InResponseTo="_0ddb303f9500839762eabd30e7b1e3c28b596c69" 
          NotOnOrAfter="2013-09-12T10:18:35.177Z" 
          Recipient="https://sp.local.ru:8443/sso/acs"/>
      </saml2:SubjectConfirmation>
    </saml2:Subject>
    <saml2:Conditions 
         NotBefore="2013-09-12T10:13:35.177Z" 
         NotOnOrAfter="2013-09-12T10:18:35.177Z">
        <saml2:AudienceRestriction>
            <saml2:Audience>sp.local.ru</saml2:Audience>
        </saml2:AudienceRestriction>
    </saml2:Conditions>
    <saml2:AuthnStatement AuthnInstant="2013-09-12T10:13:35.137Z" 
              SessionIndex="_91826738984ca8bef18a8450135b1821">
        <saml2:SubjectLocality Address="127.0.0.1"/>
        <saml2:AuthnContext>
          <saml2:AuthnContextClassRef>
            urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
          </saml2:AuthnContextClassRef>
        </saml2:AuthnContext>
    </saml2:AuthnStatement>
 <saml2:AttributeStatement>
        <saml2:Attribute Name="userLogin" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
            <saml2:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">idpuser</saml2:AttributeValue>
        </saml2:Attribute>
    </saml2:AttributeStatement>
</saml2:Assertion>
</saml2p:Response>

Сообщение будет обработано в теснее реализованном способе SAMLResponseVerifier.verify(..)
Вот собственно и все, наш фильтр реализован!
Конструкция нашего плана выглядит так:

Собираем реализованный фильтр в jar библиотеку в локальный репозиторий.
Для этого исполняем команду в директории с pom.xml: mvn clean install

6. Создаем Java Web приложение с помощью SSO
Создаем Java Web приложение

Для наглядного примера мы сделаем примитивное Java Web-приложение с приватными и публичными источниками. Доступ до приватных источников требует аутентификацию пользователя через веб-приложение Shibboleth idP. Одним из приватных источников сделаем страницу, которая выводит информацию по нынешнему пользователю системы.
Конструкция нашего приложения выглядит дальнейшим образом:

pom.xml

Код

<project xmlns="http://maven.apache.org/POM/4.0.0" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
  http://maven.apache.org/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion>
<groupId> ru.eastbanctech.web</groupId>
<artifactId>SimpleSSOApplication</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>SimpleSSOApplication</name>
<url>http://maven.apache.org</url>

<!-- Задаем значения для нашего приложения  -->
<properties>
  <sp.id>sp.local.ru</sp.id>
  <acs.url>https://sp.local.ru:8443/sso/acs</acs.url>
  <idp.sso.url>https://idp.local.ru:8443/idp/profile/SAML2/Redirect/SSO</idp.sso.url>
  <logout.url>/logout</logout.url>
</properties>

<dependencies>
  <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>servlet-api</artifactId>
    <version>2.5</version>
   </dependency>
   <dependency>
     <groupId> ru.eastbanctech.web</groupId>
     <artifactId>saml-sp-filter</artifactId>
     <version>1.0-SNAPSHOT</version>
   </dependency><sup>8</sup> 
   <dependency>
     <groupId>org.slf4j</groupId>
     <artifactId>slf4j-api</artifactId>
     <version>1.7.1</version>
   </dependency>
   <dependency>
     <groupId>org.slf4j</groupId>
     <artifactId>slf4j-log4j12</artifactId>
     <version>1.7.1</version>
   </dependency>
 </dependencies>

 <build>
   <finalName>sso</finalName>
   <plugins>
     <plugin>
       <artifactId>maven-war-plugin</artifactId>
       <configuration>
         <webResources>
           <resource>
             <filtering>true</filtering>
             <directory>src/main/webapp/WEB-INF</directory>
             <targetPath>WEB-INF</targetPath>
             <includes>
               <include>**/*.xml</include>
             </includes>
           </resource>
         </webResources>
       </configuration>
     </plugin>
   </plugins>
 </build> 
 </project> 

Здесь необходимо обратить на секцию properties, где задаются основные параметры нашего фильтра
<sp.id>sp.local.ru</sp.id> — наименование SAML 2.0 фильтра SP
<acs.url>https://sp.local.ru:8443/sso/acs</acs.url> — URL фильтра, по которому он
будет обрабатывать SAML 2.0 сообщения от shibboleth idP
<idp.sso.url>https://idp.local.ru:8443/idp/profile/SAML2/Redirect/SSO</idp.sso.url> — URL, по
которому наш фильтр будет отправлять сообщения shibboleth idP
<logout.url>/logout</logout.url> — logout URL

web.xml

В web.xml файле определяем параметры нашего фильтра и область его действия. Сделаем источники в формате “.jpg” открытыми через параметр excludedUrlPattern.

Код

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
  <display-name>Simple SSO Java Web Application</display-name>7.
  <filter>
    <filter-name>SSOFilter</filter-name>
    <filter-class> ru.eastbanctech.java.web.filter.saml.SAMLSPFilter</filter-class>
    <init-param>
      <param-name>excludedUrlPattern</param-name>
      <param-value>.*.jpg</param-value>
   </init-param>
   <init-param>
     <param-name>idProviderSSOUrl</param-name>
     <param-value> ${idp.sso.url}</param-value>
   </init-param>
   <init-param>
     <param-name>spProviderId</param-name>
     <param-value>${sp.id}</param-value>
   </init-param>
   <init-param>
     <param-name>acsUrl</param-name>
     <param-value>${acs.url}</param-value>
   </init-param>
   <init-param>
     <param-name>logoutUrl</param-name>
     <param-value>${logout.url}</param-value>
   </init-param>
 </filter>
 <filter-mapping>
   <filter-name>SSOFilter</filter-name>
   <url-pattern>/pages/private/*</url-pattern>
 </filter-mapping>

 <filter-mapping>
   <filter-name>SSOFilter</filter-name>
   <url-pattern>${logout.url}</url-pattern>
 </filter-mapping>

 <filter-mapping>
   <filter-name>SSOFilter</filter-name>
   <url-pattern>/acs</url-pattern>
 </filter-mapping>
</web-app>

private/page.jsp

Страничка представляет собой легко итог id и атрибутов аутентифицированного пользователя.

Код

<%@ page import=" ru.eastbanctech.java.web.filter.saml.store.SAMLSessionManager" %>
<%@ page import=" ru.eastbanctech.java.web.filter.saml.store.SAMLSessionInfo" %>
<%@ page import="java.util.Map" %>
<html>
<body>
<h2>Private Resource</h2>
<%    
  SAMLSessionInfo info =
       (SAMLSessionInfo)request.getSession().getAttribute(SAMLSessionManager.SAML_SESSION_INFO);
  out.println("User id = "   info.getNameId()   "
");
  out.println("<TABLE> <TR> <TH> Attribute name </TH> <TH> Attribulte value </TH></TR>");

  for (Map.Entry entry : info.getAttributes().entrySet()) {
    out.println("<TR><TD>"   entry.getKey()   "</TD><TD>"   entry.getValue()   "</TD></TR>");
  }
  out.println("</TABLE>");
%>
<a href="<%=request.getContextPath()%>/logout">Logout</a>
</body>
</html>

Собираем приложение командой: mvn clean package.

Проверяем работу Java Web-приложения

Деплоим приложение в Tomcat AS и проверяем работу SSO:

  1. Описываем контекст приложения в файле ${tomcatHome}/conf/Catalina/localhost/sso.xml
    <Context docBase="$pathToWebApp" privileged="true" antiResourceLocking="false"
            antiJARLocking="false"    unpackWAR="false" swallowOutput="true" />
    

    либо легко копируем наше приложение sso.war в ${tomcatHome}/webapps

  2. Для того, Дабы приложения томката могли устанавливать соединение с shibboleth idP по протоколу HTTPS необходимо добавить сертификат shibboleth idP в java keystore.
    Для этого необходимо воспользоваться Java утилитой keytool:

    keytool -alias idp.local.ru -importcert -file ${shHome}/idp.crt -keystore ${keystorePath}

  3. Запускаем Tomcat AS
  4. Открываем браузер и стучимся в закрытый источник приложенияsp.local.ru:8443/sso/pages/private/page.jsp
  5. Проверяем, что открылась страница и система вывела id и имя пользователя
  6. Как упражнение проверьте, что фильтр пропускает запросы к картинкам в формате .jpg в папке /pages/private.
Интеграция с Google Apps.

А сейчас время проверить, что у нас подлинно работает SSO.
Для этого будем применять приложение из облачных служб Google Apps (http://www.google.com/enterprise/apps/business/).

  1. Зарегистрируйте себе доменное имя и супер-менеджера, применяя бесплатную пробную версию. Позже того как все закончено зайдите в систему admin.google.com/ под сделанным пользователем (применяя полное доменное имя).
  2. Пользуясь административной панелью сделать пользователя idpuser, дать ему право Super Administrator.
  3. Предпочесть внизу экрана «Добавить элементы управления» и в выпадающем списке нажать
    на пункт «Безопасность».
  4. Дальше предпочесть Расширенные настройки -> Установить цельный вход.
  5. Подметить пункт Позволить цельный вход и поставить параметры:
    URL входной страницы * https://idp.local.ru:8443/idp/profile/SAML2/Redirect/SSO
    URL страницы выхода * = gmail.com
    Изменить пароль URL * = gmail.com

    Нажать на кнопку Сберечь метаморфозы.

  6. Загрузить сертификат для работы с shibboleth idP по HTTPS
    Сертификат находится в $shHome/credentials/idp.crt

    Нажать на кнопку Сберечь метаморфозы.

  7. Пользуясь инструкцией https://shibboleth.usc.edu/docs/google-apps/ настроить shibboleth idP на работу с Google Apps.

    Примечание: указывайте имя схемы для добавляемых элементов, напротив получите ошибку при старте shibboleth idP. Скажем, взамен RelyingParty необходимо указывать rp:RelyingParty.

  8. Для logger’а с именем edu.internet2.middleware.shibboleth устанавливаем ярус DEBUG
        <!-- Logs IdP, but not OpenSAML, messages -->
        <logger name="edu.internet2.middleware.shibboleth" level="DEBUG"/>
    

    Перезапускаем shibboleth idP и идем на страницу https://admin.google.com в новой сессии броузера (допустимо понадобится удаление куков, в Google Chrome дозволено применять режим Инкогнито).
    Вводим idpuser@domain_name, где domain_name – имя вашего зарегистрированного домена и пароль. Нажимаем «Войти».
    Принимаем не подписанные сертификаты и удостоверяемся, что вы вошли в google apps под пользователем idpuser.
    В логе ${shHome}/logs/idp-process.log шибболета вы обязаны увидеть, как shibboleth idP обрабатывает ваш запрос. Там будет видно, что проходит процесс аутентификации через RemoteUserLoginHandler

    22:19:49.172 - DEBUG [edu.internet2.middleware.shibboleth.idp.authn.provider.RemoteUserLoginHandler:66] - Redirecting to <a href="https://idp.local.ru:8443/idp/Authn/RemoteUser">https://idp.local.ru:8443/idp/Authn/RemoteUser</a>
    
    

    Вообще логи в shibboleth idP довольно примитивные и в то же время информативные. Рекомендуем потратить немножко времени, Дабы разобраться в них.
    Дальше открываем наше приложение по урлу sp.local.ru:8443/sso/pages/private/page.jsp
    и глядим в логах, что shibboleth idP находит имеющуюся сессию для пользователя idpuser.

    Ну вот и все. Наша простейшая система SSO работает. Верим, что вы обнаружили что-то пригодное для себя.

    Примечания

    1 — Дозволено также применять Service Provider от изготовителя. В случае с Shibboleth это приводит к усложнению инфраструктуры приложения, от того что требуется ставить добавочный Apache-сервер перед Application Server’ом.
    2 — На момент написания данной статьи последняя версия Shibboleth idP 2.4.0
    3 — Мы применяли Java 7 в своем окружении.
    4 — Мы применяли CentOS 6.3 в качестве OS. Также проверялась на Ubuntu 12.04.
    5 — Для компиляции понадобятся библиотека servlet-api 2.5 и ${tomcatHome}/lib/catalina.jar
    6 — ru.eastbanctech.java.web.RemoteUserValve – полный путь до класса RemoteUserValve. В вашем случае требуется поправить исходя из иерархии пакетов.
    7 — Предлагаем реализовать самосильно в качестве упражнения.
    8 — Измените параметры выделенные красным цветом в зависимости от вашего окружения.

    Пригодные ссылки
    1. https://developers.google.com/google-apps/sso/saml_reference_implementation — SSO сервис для Google Apps. Объясняется каким образом дозволено интегрировать SSO в Google Docs средствами SAML.
    2. https://shibboleth.usc.edu/docs/google-apps/ — Инструкция по интеграции Shibboleth с Google docs.
    3. http://stackoverflow.com/questions/7553967/getting-a-value-from-httpservletrequest-getremoteuser-in-tomcat-without-modify — Как реализовать свой Tomcat Valve
    4. https://wiki.shibboleth.net/confluence/display/SHIB2/Home — документация по Shibboleth
Источник: programmingmaster.ru
Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB