Как уже говорилось ранее, Alfresco имеет множество встроенных механизмов авторизации. Однако, встроенной поддержки прозрачной аутентификации (SSO, single-sign-on) Alfresco не имеет. Поэтому задача стоит так: создать подсистему авторизации, позволяющей пользователям Alfresco входить без ввода пароля, используя доменную NTLM-авторизацию. Данная подсистема должна работать в Windows 7, Vista, XP, быть надежной и безопасной. Как эта задача была решена, читайте далее.
В качестве рабочего механизма авторизации Alfresco выбираем External. Для этого в файле C:\Alfresco\tomcat\shared\classes\alfresco-global.properties устанавливаем следующие настройки:
authentication.chain=external1:external,alfrescoNtlm1:alfrescoNtlm external.authentication.enabled=true external.authentication.proxyHeader=X-Alfresco-Remote-User external.authentication.proxyUserName=
Чтобы взаимодействие между приложением Share и приложением Alfresco проходило посредством cookies, в файле необходимо прописать:
<config evaluator="string-compare" condition="Remote"> <remote> <endpoint> <id>alfresco-noauth</id> <name>Alfresco - unauthenticated access</name> <description>Access to Alfresco Repository WebScripts that do not require authentication</description> <connector-id>alfresco</connector-id> <endpoint-url>http://localhost:8080/alfresco/s</endpoint-url> <identity>none</identity> </endpoint> <connector> <id>alfrescoCookie</id> <name>Alfresco Connector</name> <description>Connects to an Alfresco instance using cookie- based authentication</description> <class>org.springframework.extensions.webscripts.connector.AlfrescoConnector</class> </connector> <endpoint> <id>alfresco</id> <name>Alfresco - user access</name> <description>Access to Alfresco Repository WebScripts that require user authentication</description> <connector-id>alfrescoCookie</connector-id> <endpoint-url>http://localhost:8080/alfresco/wcs</endpoint-url> <identity>user</identity> <external-auth>true</external-auth> </endpoint> </remote> </config>
Если этого не сделать, Share не сможет связаться с репозиторием Alfresco.
Теперь Alfresco будет доверять переданной ей серверной переменной RemoteUser. Чтобы сформировать эту переменную, создадим подсистему из модулей Apache. В качестве модуля, отвечающего за доменную прозрачную авторизацию пользователя, выберем sspi_auth_module.
Чтобы его подключить, пропишем в настройках Apache в файле httpd.conf:
LoadModule sspi_auth_module modules/mod_auth_sspi.so
Заодно, пропишем в httpd.conf настройки, которые пригодятся в дальнейшем:
LoadFile "C:/Perl/bin/perl512.dll" LoadModule perl_module modules/mod_perl.so Listen 10.0.20.245:80 ServerName vm-lysenko <VirtualHost 10.0.20.245:80> ServerName vm-lysenko DocumentRoot "C:\alf" UseCanonicalName Off PerlModule Apache2::RequestRec RedirectMatch ^/$ /share/ RedirectMatch ^/alfresco$ /alfresco/ RedirectMatch ^/share$ /share/ RedirectMatch ^/login$ /login/ <Proxy ajp://127.0.0.1:8009/login/> Order deny,allow Deny from none Allow from all </Proxy> <Proxy ajp://127.0.0.1:8009/alfresco/> Order deny,allow Deny from none Allow from all </Proxy> <Proxy ajp://127.0.0.1:8009/share/> Order deny,allow Deny from none Allow from all PerlFixupHandler Apache2::SetRemoteUser </Proxy> ProxyPass /login/ ajp://127.0.0.1:8009/login/ ProxyPassReverse /login/ ajp://127.0.0.1:8009/login/ ProxyPass /alfresco/ ajp://127.0.0.1:8009/alfresco/ ProxyPassReverse /alfresco/ ajp://127.0.0.1:8009/alfresco/ ProxyPass /share/ ajp://127.0.0.1:8009/share/ ProxyPassReverse /share/ ajp://127.0.0.1:8009/share/ <Location "/login/login.jsp"> AuthName "Tomcat" AuthType SSPI SSPIAuth On SSPIPackage NTLM SSPIAuthoritative On SSPIOfferBasic Off SSPIOmitDomain On SSPIUsernameCase lower # SSPIPerRequestAuth Off # SSPIBasicPreferred # SSPIUsernameCase lower require valid-user </Location> </VirtualHost>
Из вышеуказанных настроек видно, что Apache играет роль прокси-сервера, и пропускает через себя все запросы от пользователя к Alfresco Share. Каждый раз, когда мы обращаемся к Share, мы применяем PerlFixupHandler Apache2::SetRemoteUser, чтобы установить переменную RemoteUser. Для того, чтобы устанавливать эту переменную, используется следующий perl-скрипт: (в папке c:\Perl\site\lib\Apache2\ файл SetRemoteUser.pm)
package Apache2::SetRemoteUser; use strict; use warnings; use CGI::Cookie; use Apache2::Const -compile => qw(OK); use Apache2::Connection; use Apache::DBI; sub handler { my $r = shift; my $session_value = ""; my $raw_cookie = $r->headers_in->{'Cookie'} || ''; if ($raw_cookie ne '') { my %cookie = CGI::Cookie->parse($raw_cookie); if (exists $cookie{'JSESSIONID'}) { $session_value = $cookie{'JSESSIONID'}->value || "unknown-session"; } } my $ip = $r->connection->remote_ip() || "unknown-ip"; my $browser = $r->headers_in->{ 'User-Agent' } || "unknown-agent"; my $authorization = $r->headers_in->{ 'Authorization' } || "not-auth"; my $url = $r->uri || "unknown-url"; my $dbh = DBI->connect("DBI:mysql:sessions:localhost:3306", "session_user", "session_password", { PrintError => 1, # warn( ) on errors RaiseError => 0, # don't die on error AutoCommit => 1, # commit executes immediately } ) or die "Cannot connect to database: $DBI::errstr"; my $do_sql = "SELECT username FROM session WHERE unique_value = MD5('" . $session_value . $ip . $browser . "')"; my $sth = $dbh->prepare($do_sql); $sth->execute( ); my $result = $sth->fetchrow_hashref(); my $u_name = $result->{'username'} || ""; $dbh->disconnect( ); # NOOP under Apache::DBI if ($u_name ne "") { $r->user($u_name); } return Apache2::Const::OK; } 1;
Данный скрипт проверяет, есть ли зарегистрированная сессия с указанным именем пользователя, если такая сессия есть – то remoteUser устанавливается. В качестве места хранения сессий выбрана СУБД MySQL. Структура таблицы сессий:
CREATE TABLE `session` ( `session_ID` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(30) DEFAULT NULL, `session_value` varchar(50) DEFAULT NULL, `unique_value` varchar(255) DEFAULT NULL, `session_date` datetime DEFAULT NULL, PRIMARY KEY (`session_ID`) ) ENGINE=InnoDB
Perl-скрипт только считывает данные из этой таблицы. Запись туда ведет jsp-приложение login, которое уже упоминалось в настройках httpd.conf. Данное приложение должно быть развернуто на том же сервере tomcat, что и приложения Share и Alfresco. Основной его частью является файл login.jsp, который и отвечает за «узнавание» пользователей и запись сессии в базу:
<%@ page session="true" %> <%@ page import="java.io.*" %> <%@ page import="java.sql.*;" %> <% String remoteUser = request.getRemoteUser(); String returnPath = request.getParameter("r"); Boolean login = false; Integer error_status = 0; if (remoteUser != null) { String driver = "org.gjt.mm.mysql.Driver"; Class.forName(driver).newInstance(); Connection con = null; ResultSet rst = null; Statement stmt = null; PreparedStatement pstmt = null; String found_name=""; String sessionValue=""; String sql=""; String ip = request.getRemoteAddr(); String browser = request.getHeader("User-Agent"); Cookie cookies[] = request.getCookies(); if (cookies != null) { for (int i = 0; i < cookies.length; i++) { if (((String)cookies[i].getName()).equals("JSESSIONID")) { sessionValue = cookies[i].getValue(); } } } try { String session_url = "jdbc:mysql://localhost:3306/sessions"; String alfresco_url = "jdbc:mysql://localhost:3307/alfresco"; con = DriverManager.getConnection(alfresco_url, "root", "root"); sql = "SELECT authority FROM alf_authority WHERE authority = '" + remoteUser + "'"; stmt = con.createStatement(); rst = stmt.executeQuery(sql); while(rst.next()){ found_name = rst.getString(1); } con.close(); stmt.close(); if (sessionValue.equals("")) { error_status = 1; } if (!(found_name.equals(remoteUser) && !found_name.equals(""))) { error_status = 2; } if (error_status.equals(0)) { con=DriverManager.getConnection(session_url, "session_user", "session_password"); sql = "DELETE FROM session WHERE (session_value = '" + sessionValue + "') OR (username = '" + remoteUser + "')"; pstmt = con.prepareStatement(sql); pstmt.executeUpdate(); sql = "INSERT INTO session SET session_value = '" + sessionValue + "', username = '" + remoteUser + "', session_date = NOW(), unique_value = MD5('" + sessionValue + ip + browser + "')"; pstmt = con.prepareStatement(sql); pstmt.executeUpdate(); login = true; } else { login = false; } rst.close(); pstmt.close(); con.close(); } catch(Exception e){ System.out.println(e.getMessage()); } if (login) { if (returnPath != null) { response.sendRedirect(returnPath); } else { response.sendRedirect("/share/page/user/" + remoteUser + "/dashboard"); } } else { if (error_status.equals(1)) { response.sendRedirect("login.jsp"); } else { response.sendRedirect("could-not-login.jsp"); } } } %>
Login.jsp проверяет, есть ли определенный пользователь в базе Alfresco. Это необходимо для того, чтобы пускать только тех доменных пользователей, которые зарегистрированы в Alfresco. Также этот скрипт записывает в таблицу сессий уникальные данные пользователя, необходимые для его безошибочной идентификации: браузер, IP и т.д.
Каждый раз, когда приложение Share не узнало пользователя, оно выдает страницу с вводом логина и пароля. В данную страницу необходимо внедрить обработчик, который вместо выдачи формы ввода логина и пароля будет перенаправлять на наш скрипт доменной авторизации. Для этого в файл slingshot-login.ftl нужно добавить строку:
<script> window.location.href = "/login/login.jsp?r=" + window.location.href; </script>
Параметр r нужен для того, чтобы после логина вернуться к той странице, к которой хотел обратиться пользователь.
Теперь, когда установлены все дополнительные подсистемы, необходимо правильно настроить Tomcat. В файле server.xml должны быть прописаны два коннектора со следующими параметрами:
<Connector port="8080" URIEncoding="UTF-8" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" tomcatAuthentication="false" emptySessionPath="true" /> <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" tomcatAuthentication="false" emptySessionPath="true" />
Коннектор с портом 8009 необходим для проксирования Апачем. В целях безопасности необходимо закрыть порты 8080 и 8009 от доступа извне, к ним должен иметь доступ только локальный сервер. Все внешние запросы принимает на себя Apache по порту 80. Если не закрыть упомянутые порты, то злоумышленник сможет обмануть систему и войти под любым нужным ему пользователем.
Параметр tomcatAuthentication="false" нужен для того, чтобы выключить встроенные в Tomcat механизмы авторизации, так как они нам не нужны и будут мешать. Параметр emptySessionPath="true" нужен для того, чтобы сессия, сформированная в одном приложении, действовала и в другом. Если этот параметр не установить, то сессия приложения share не будет действовать в приложении alfresco, не говоря уже о нашем приложении login. Для описываемой процедуры авторизации требуется, чтобы приложение login могло свободно передавать сессию в приложение share.