タイトルが示すように、ロールを使用して Tomcat に Spring Security と Waffle を統合しようとしています。アプリは、ユーザーが既にドメイン認証されている Windows 環境にデプロイされ、シングル サインオンを実行したいと考えています。さらに、認証されたユーザーが属するグループを確認し、承認されたグループのメンバーではないユーザーが Web アプリにアクセスできないようにインターセプターを構成します。
アプリのコンテキストは次のようになります。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:sec="http://www.springframework.org/schema/security"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.1.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-3.1.xsd">
<mvc:annotation-driven />
<cache:annotation-driven />
<import resource="mvc-config.xml"/>
<!--WAFFLE CONFIG-->
<!-- windows authentication provider -->
<bean id="waffleWindowsAuthProvider" class="waffle.windows.auth.impl.WindowsAuthProviderImpl" />
<!-- collection of security filters -->
<bean id="negotiateSecurityFilterProvider" class="waffle.servlet.spi.NegotiateSecurityFilterProvider">
<constructor-arg ref="waffleWindowsAuthProvider" />
</bean>
<bean id="waffleSecurityFilterProviderCollection" class="waffle.servlet.spi.SecurityFilterProviderCollection">
<constructor-arg>
<list>
<ref bean="negotiateSecurityFilterProvider" />
<ref bean="basicSecurityFilterProvider" />
</list>
</constructor-arg>
</bean>
<!-- spring filter entry point -->
<sec:http use-expressions="true" entry-point-ref="negotiateSecurityFilterEntryPoint">
<sec:intercept-url pattern="/**" access="hasRole('APP_USER')" />
<sec:custom-filter ref="waffleNegotiateSecurityFilter" position="BASIC_AUTH_FILTER" />
</sec:http>
<bean id="basicSecurityFilterProvider" class="waffle.servlet.spi.BasicSecurityFilterProvider">
<constructor-arg ref="waffleWindowsAuthProvider" />
</bean>
<bean id="negotiateSecurityFilterEntryPoint" class="waffle.spring.NegotiateSecurityFilterEntryPoint">
<property name="Provider" ref="waffleSecurityFilterProviderCollection" />
</bean>
<!-- spring authentication provider -->
<sec:authentication-manager alias="authenticationProvider" />
<!-- spring security filter -->
<bean id="waffleNegotiateSecurityFilter" class="waffle.spring.NegotiateSecurityFilter">
<property name="Provider" ref="waffleSecurityFilterProviderCollection" />
<property name="AllowGuestLogin" value="false" />
<property name="PrincipalFormat" value="fqn" />
<property name="RoleFormat" value="both" />
</bean>
<!--END WAFFLE CONFIG-->
<!-- the mvc resources tag does the magic -->
<mvc:resources mapping="/css/**" location="/css/" />
<mvc:resources mapping="/js/**" location="/js/" />
<mvc:resources mapping="/img/**" location="/img/" />
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="1000000" />
</bean>
<bean id="excelExportView" class="com.mycompany.appname.view.ExcelExportView"></bean>
<context:component-scan base-package="com.mycompany.appname" />
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="columnNames"/>
</set>
</property>
</bean>
<beans profile="dev">
<bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource">
<qualifier value="internal"/>
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.internal.url}" />
<property name="username" value="${jdbc.internal.username}" />
<!--<property name="password" value="${jdbc.internal.password}"/>-->
<property name="minEvictableIdleTimeMillis" value="120000"/>
<property name="testOnBorrow" value="true" />
<property name="timeBetweenEvictionRunsMillis" value="120000"/>
<property name="minIdle" value="1"/>
</bean>
<bean id="dataSourceExternal" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource">
<qualifier value="external"/>
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.external.url}" />
<property name="username" value="${jdbc.external.username}" />
<!--<property name="password" value="${jdbc.external.password}"/>-->
<property name="minEvictableIdleTimeMillis" value="120000"/>
<property name="testOnBorrow" value="true" />
<property name="timeBetweenEvictionRunsMillis" value="120000"/>
<property name="minIdle" value="1"/>
</bean>
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="/WEB-INF/db-dev.properties"></property>
</bean>
</beans>
<beans profile="test">
<bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource">
<qualifier value="internal"/>
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.internal.url}" />
<property name="minEvictableIdleTimeMillis" value="120000"/>
<property name="testOnBorrow" value="true" />
<property name="timeBetweenEvictionRunsMillis" value="120000"/>
<property name="minIdle" value="1"/>
</bean>
<bean id="dataSourceExternal" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource">
<qualifier value="external"/>
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.external.url}" />
<property name="minEvictableIdleTimeMillis" value="120000"/>
<property name="testOnBorrow" value="true" />
<property name="timeBetweenEvictionRunsMillis" value="120000"/>
<property name="minIdle" value="1"/>
</bean>
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="/WEB-INF/db-test.properties"></property>
</bean>
</beans>
<beans profile="production">
<bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource">
<qualifier value="internal"/>
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.internal.url}" />
<property name="minEvictableIdleTimeMillis" value="120000"/>
<property name="testOnBorrow" value="true" />
<property name="timeBetweenEvictionRunsMillis" value="120000"/>
<property name="minIdle" value="1"/>
</bean>
<bean id="dataSourceExternal" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource">
<qualifier value="external"/>
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.external.url}" />
<property name="minEvictableIdleTimeMillis" value="120000"/>
<property name="testOnBorrow" value="true" />
<property name="timeBetweenEvictionRunsMillis" value="120000"/>
<property name="minIdle" value="1"/>
</bean>
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="/WEB-INF/db-prod.properties"></property>
</bean>
</beans>
そしてweb.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Beans in these files will makeup the configuration of the root web application context -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/appname-servlet.xml</param-value>
</context-param>
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>/WEB-INF/log4j.properties</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
<!-- Protect against XSS -->
<context-param>
<param-name>defaultHtmlEscape</param-name>
<param-value>true</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Deploys the 'accounts' dispatcher servlet whose configuration resides in /WEB-INF/mvc-config.xml -->
<servlet>
<servlet-name>appname</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/appname-servlet.xml</param-value>
</init-param>
</servlet>
<!-- Maps all URLs to the 'appname' servlet -->
<servlet-mapping>
<servlet-name>appname</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>
<!--
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/error.jsp</location>
</error-page>
-->
<welcome-file-list>
<welcome-file>/WEB-INF/views/appname_main.jsp</welcome-file>
</welcome-file-list>
<session-config>
<session-timeout>60</session-timeout>
<cookie-config>
<http-only>true</http-only>
</cookie-config>
</session-config>
現在起こっていることは、アプリ「appName」にアクセスしようとすると、すぐに認証を求めるプロンプトが表示されるということです。Waffle を読んだところ、Windows トークンの取得とユーザーの認証に失敗したため (失敗した試行または無効な資格情報によって)、これがフォールバック認証であるとしか思えません。
以前の試みには、「hasRole」を使用せずに代わりに使用することが含まれていました
access="IS_AUTHENTICATED_FULLY" />
これはユーザーの役割をチェックしませんが、少なくともドメイン認証に基づいてアプリへのアクセスを制限します。残念ながら、この場合、ユーザーがアプリにアクセスするたびにプロンプトが表示されます。毎回 Access Denied を返す「hasRole」アプローチとは異なり、少なくともこの構成では、実際にはドメイン ユーザーにアプリへのアクセスを許可していました。
任意の洞察をいただければ幸いです...
[編集: ログから詳細を追加]
シングルサインオンが「IS_AUTHENTICATED_FULLY」で機能していると思っていたところ、実際には誤検知の結果が得られていたことが判明しました。ブラウザーは資格情報をキャッシュして要求に適用していたため、SSO は実際には機能していませんでした。いつでもプロンプトが表示されます。ROLE_USER は同じ結果をもたらします: 資格情報を要求して受け入れます。
不思議なことに、ワッフルから詳細を取得しようとして問題が発生しました。Tomcat の conf logging.properties に次の行を追加しました。
waffle.servlet.NegotiateSecurityFilter.level = FINE
waffle.servlet.spi.SecurityFilterProviderCollection.level = FINE
waffle.servlet.spi.NegotiateSecurityFilterProvider.level = FINE
waffle.servlet.spi.BasicSecurityFilterProvider.level = FINE
それでも、ローカルホスト、カタリナなどは、ワッフルに関する追加の詳細を生成しません.
プレイ中のロールに関連する唯一のログ情報は次のとおりです。
token:'org.springframework.security.authentication.AnonymousAuthenticationToken@905571d8: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@0: RemoteIpAddress: 10.10.90.70; SessionId: null; Granted Authorities: ROLE_ANONYMOUS'>
2013-02-21 11:25:10,527 DEBUG [org.springframework.security.web.FilterChainProxy] - </WEB-INF/views/ourappname_main.jsp at position 6 of 8 in additional filter chain; firing Filter: 'SessionManagementFilter'>
2013-02-21 11:25:10,528 DEBUG [org.springframework.security.web.FilterChainProxy] - </WEB-INF/views/ourappname_main.jsp at position 7 of 8 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'>
2013-02-21 11:25:10,528 DEBUG [org.springframework.security.web.FilterChainProxy] - </WEB-INF/views/ourappname_main.jsp at position 8 of 8 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'>
2013-02-21 11:25:10,529 DEBUG [org.springframework.security.web.access.intercept.FilterSecurityInterceptor] - <Secure object: FilterInvocation: URL: /WEB-INF/views/ourappname_main.jsp; Attributes: [IS_AUTHENTICATED_FULLY]>
2013-02-21 11:25:10,529 DEBUG [org.springframework.security.web.access.intercept.FilterSecurityInterceptor] - <Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@905571d8: Principal: anonymous
WFETCH からこれを取得しました。
User; Credentials: [PROTECTED];
Authenticated: true;
Details: org.springframework.security.web.authentication.WebAuthenticationDetails@0:
RemoteIpAddress: 10.10.10.10;
SessionId: null;
Granted Authorities: ROLE_ANONYMOUS>
http://10.10.10.10/ourappname/
Transfer-Encoding: chunked
Date: Thu, 21 Feb 2013 16:29:42 GMT
[もう一度編集] 要求された失敗した呼び出しのヘッダー情報。Waffle-filter サンプルは、localhost が使用されている場合、ユーザーにプロンプトを表示することなく、期待どおりに機能していることに注意してください。IP またはドメインが使用されると、プロンプトが表示されます。これはシステム管理/信頼できるホストの問題だと思いますか?
GET /ourappnameHTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Accept-Language: en-US
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)
Accept-Encoding: gzip, deflate
Host: localhost:8080
Connection: Keep-Alive
HTTP/1.1 302 Found
Server: Apache-Coyote/1.1
Location: http://localhost:8080/ourappname/
Transfer-Encoding: chunked
Date: Thu, 21 Feb 2013 20:15:51 GMT
GET /ourappname/ HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Accept-Language: en-US
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)
Accept-Encoding: gzip, deflate
Host: localhost:8080
Connection: Keep-Alive
HTTP/1.1 401 Unauthorized
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=F2216F75CBA6AC8476189DA48A63A872; Domain=.domain.tld; Path=/something/; HttpOnly
Connection: keep-alive
WWW -Authenticate: Negotiate
WWW-Authenticate: NTLM
WWW-Authenticate: Basic realm="BasicSecurityFilterProvider"
Transfer-Encoding: chunked
Date: Thu, 21 Feb 2013 20:15:51 GMT
GET /fismacm/ HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Accept-Language: en-US
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)
Accept-Encoding: gzip, deflate
Host: localhost:8080
Connection: Keep-Alive
Authorization: Negotiate YHkGBisGAQUFAqBvMG2gMDAuBgorBgEEAYI3AgIKBgkqhkiC9xIBAgIGCSqGSIb3EgECAgYKKwYBBAGCNwICHqI5BDd OVExNU1NQAAEAAACXsgjiBAAEADMAAAALAAsAKAAAAAYBsR0AAAAPVzJLOFIyLURFVjFHT0xE
HTTP/1.1 401 Unauthorized
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=2CC0FDBF578629857113C6A72EE67FF5; Domain=.domain.tld; Path=/something/; HttpOnly
Connection: keep-alive
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM
WWW-Authenticate: Basic realm="BasicSecurityFilterProvider"
Transfer-Encoding: chunked
Date: Thu, 21 Feb 2013 20:15:51 GMT
GET /favicon.ico HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)
Host: localhost:8080
Connection: Keep-Alive
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Accept-Ranges: bytes
ETag: W/"21630-1349272326000"
Last-Modified: Wed, 03 Oct 2012 13:52:06 GMT
Content-Type: image/x-icon
Content-Length: 21630
Date: Thu, 21 Feb 2013 20:16:07 GMT