title: Integrating Spring Security and Waffle on Tomcat with Role checks
body: <p>As the title suggests, I'm trying to integrate Spring Security and Waffle on Tomcat using Roles.  The app will be deployed to a Windows environment where users will already have been domain authenticated and I want to be exercising Single Sign On.  To take things further, I want to check the groups that the authenticated user belongs to and configure interceptors to prevent users who are not members of the approved group(s) from accessing the web app.</p>
  
  <p>Here's what the app context looks like:</p>
  
  <pre><code>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
  
  &lt;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"&gt;
  
  
  &lt;mvc:annotation-driven /&gt;
  &lt;cache:annotation-driven /&gt;
  &lt;import resource="mvc-config.xml"/&gt;
  
  
  &lt;!--WAFFLE CONFIG--&gt;
  
  &lt;!-- windows authentication provider --&gt;
  &lt;bean id="waffleWindowsAuthProvider" class="waffle.windows.auth.impl.WindowsAuthProviderImpl" /&gt;
  
  &lt;!-- collection of security filters --&gt;
  &lt;bean id="negotiateSecurityFilterProvider" class="waffle.servlet.spi.NegotiateSecurityFilterProvider"&gt;
      &lt;constructor-arg ref="waffleWindowsAuthProvider" /&gt;
  &lt;/bean&gt;
  
  &lt;bean id="waffleSecurityFilterProviderCollection" class="waffle.servlet.spi.SecurityFilterProviderCollection"&gt;
      &lt;constructor-arg&gt;
          &lt;list&gt;
              &lt;ref bean="negotiateSecurityFilterProvider" /&gt;
              &lt;ref bean="basicSecurityFilterProvider" /&gt;
          &lt;/list&gt;
      &lt;/constructor-arg&gt;
  &lt;/bean&gt;
  
  &lt;!-- spring filter entry point --&gt;
  &lt;sec:http use-expressions="true" entry-point-ref="negotiateSecurityFilterEntryPoint"&gt;
      &lt;sec:intercept-url pattern="/**" access="hasRole('APP_USER')" /&gt;
      &lt;sec:custom-filter ref="waffleNegotiateSecurityFilter" position="BASIC_AUTH_FILTER" /&gt;
  &lt;/sec:http&gt;
  
  &lt;bean id="basicSecurityFilterProvider" class="waffle.servlet.spi.BasicSecurityFilterProvider"&gt;
      &lt;constructor-arg ref="waffleWindowsAuthProvider" /&gt;
  &lt;/bean&gt;   
  
  &lt;bean id="negotiateSecurityFilterEntryPoint" class="waffle.spring.NegotiateSecurityFilterEntryPoint"&gt;
      &lt;property name="Provider" ref="waffleSecurityFilterProviderCollection" /&gt;
  &lt;/bean&gt;
  
  &lt;!-- spring authentication provider --&gt;
  &lt;sec:authentication-manager alias="authenticationProvider" /&gt;
  
  &lt;!-- spring security filter --&gt;
  &lt;bean id="waffleNegotiateSecurityFilter" class="waffle.spring.NegotiateSecurityFilter"&gt;
      &lt;property name="Provider" ref="waffleSecurityFilterProviderCollection" /&gt;
      &lt;property name="AllowGuestLogin" value="false" /&gt;
      &lt;property name="PrincipalFormat" value="fqn" /&gt;
      &lt;property name="RoleFormat" value="both" /&gt;
  &lt;/bean&gt;
  
  &lt;!--END WAFFLE CONFIG--&gt;
  
  
  &lt;!-- the mvc resources tag does the magic --&gt;
  &lt;mvc:resources mapping="/css/**" location="/css/" /&gt;
  &lt;mvc:resources mapping="/js/**" location="/js/" /&gt;
  &lt;mvc:resources mapping="/img/**" location="/img/" /&gt;
  
      &lt;bean id="multipartResolver"
      class="org.springframework.web.multipart.commons.CommonsMultipartResolver"&gt;
      &lt;property name="maxUploadSize" value="1000000" /&gt;
  &lt;/bean&gt;
  
      &lt;bean id="excelExportView" class="com.mycompany.appname.view.ExcelExportView"&gt;&lt;/bean&gt;
  
  &lt;context:component-scan base-package="com.mycompany.appname" /&gt;
      &lt;bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager"&gt;
          &lt;property name="caches"&gt;
          &lt;set&gt;
              &lt;bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="columnNames"/&gt;
          &lt;/set&gt;
          &lt;/property&gt;
      &lt;/bean&gt;
  
      &lt;beans profile="dev"&gt;
          &lt;bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource"&gt;
              &lt;qualifier value="internal"/&gt;
              &lt;property name="driverClassName" value="${jdbc.driverClassName}" /&gt;
              &lt;property name="url" value="${jdbc.internal.url}" /&gt;
              &lt;property name="username" value="${jdbc.internal.username}" /&gt;
              &lt;!--&lt;property name="password" value="${jdbc.internal.password}"/&gt;--&gt;
              &lt;property name="minEvictableIdleTimeMillis" value="120000"/&gt;
              &lt;property name="testOnBorrow" value="true" /&gt;
              &lt;property name="timeBetweenEvictionRunsMillis" value="120000"/&gt;
              &lt;property name="minIdle" value="1"/&gt;
          &lt;/bean&gt;
  
          &lt;bean id="dataSourceExternal" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource"&gt;
              &lt;qualifier value="external"/&gt;
              &lt;property name="driverClassName" value="${jdbc.driverClassName}" /&gt;
              &lt;property name="url" value="${jdbc.external.url}" /&gt;
              &lt;property name="username" value="${jdbc.external.username}" /&gt;
              &lt;!--&lt;property name="password" value="${jdbc.external.password}"/&gt;--&gt;
              &lt;property name="minEvictableIdleTimeMillis" value="120000"/&gt;
              &lt;property name="testOnBorrow" value="true" /&gt;
              &lt;property name="timeBetweenEvictionRunsMillis" value="120000"/&gt;
              &lt;property name="minIdle" value="1"/&gt;
          &lt;/bean&gt;
          &lt;bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"&gt;
                  &lt;property name="location" value="/WEB-INF/db-dev.properties"&gt;&lt;/property&gt;
          &lt;/bean&gt;
      &lt;/beans&gt;
  
      &lt;beans profile="test"&gt;
          &lt;bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource"&gt;
              &lt;qualifier value="internal"/&gt;
              &lt;property name="driverClassName" value="${jdbc.driverClassName}" /&gt;
              &lt;property name="url" value="${jdbc.internal.url}" /&gt;
              &lt;property name="minEvictableIdleTimeMillis" value="120000"/&gt;
              &lt;property name="testOnBorrow" value="true" /&gt;
              &lt;property name="timeBetweenEvictionRunsMillis" value="120000"/&gt;
              &lt;property name="minIdle" value="1"/&gt;
          &lt;/bean&gt;
  
          &lt;bean id="dataSourceExternal" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource"&gt;
              &lt;qualifier value="external"/&gt;
              &lt;property name="driverClassName" value="${jdbc.driverClassName}" /&gt;
              &lt;property name="url" value="${jdbc.external.url}" /&gt;
              &lt;property name="minEvictableIdleTimeMillis" value="120000"/&gt;
              &lt;property name="testOnBorrow" value="true" /&gt;
              &lt;property name="timeBetweenEvictionRunsMillis" value="120000"/&gt;
              &lt;property name="minIdle" value="1"/&gt;
          &lt;/bean&gt;
          &lt;bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"&gt;
                  &lt;property name="location" value="/WEB-INF/db-test.properties"&gt;&lt;/property&gt;
          &lt;/bean&gt;
      &lt;/beans&gt;
  
      &lt;beans profile="production"&gt;
          &lt;bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource"&gt;
              &lt;qualifier value="internal"/&gt;
              &lt;property name="driverClassName" value="${jdbc.driverClassName}" /&gt;
              &lt;property name="url" value="${jdbc.internal.url}" /&gt;
              &lt;property name="minEvictableIdleTimeMillis" value="120000"/&gt;
              &lt;property name="testOnBorrow" value="true" /&gt;
              &lt;property name="timeBetweenEvictionRunsMillis" value="120000"/&gt;
              &lt;property name="minIdle" value="1"/&gt;
          &lt;/bean&gt;
  
          &lt;bean id="dataSourceExternal" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource"&gt;
              &lt;qualifier value="external"/&gt;
              &lt;property name="driverClassName" value="${jdbc.driverClassName}" /&gt;
              &lt;property name="url" value="${jdbc.external.url}" /&gt;
              &lt;property name="minEvictableIdleTimeMillis" value="120000"/&gt;
              &lt;property name="testOnBorrow" value="true" /&gt;
              &lt;property name="timeBetweenEvictionRunsMillis" value="120000"/&gt;
              &lt;property name="minIdle" value="1"/&gt;
          &lt;/bean&gt;
          &lt;bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"&gt;
                  &lt;property name="location" value="/WEB-INF/db-prod.properties"&gt;&lt;/property&gt;
          &lt;/bean&gt;
      &lt;/beans&gt;
  </code></pre>
  
  <p></p>
  
  <p>And the web.xml</p>
  
  <pre><code>&lt;?xml version="1.0" encoding="ISO-8859-1"?&gt;
  </code></pre>
  
  <p></p>
  
  <pre><code>&lt;filter&gt;
      &lt;filter-name&gt;springSecurityFilterChain&lt;/filter-name&gt;
      &lt;filter-class&gt;org.springframework.web.filter.DelegatingFilterProxy&lt;/filter-class&gt;
  &lt;/filter&gt;
  &lt;filter-mapping&gt;
      &lt;filter-name&gt;springSecurityFilterChain&lt;/filter-name&gt;
      &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
  &lt;/filter-mapping&gt;
  
  &lt;!-- Beans in these files will makeup the configuration of the root web application context --&gt;
  &lt;context-param&gt;
      &lt;param-name&gt;contextConfigLocation&lt;/param-name&gt;
      &lt;param-value&gt;/WEB-INF/appname-servlet.xml&lt;/param-value&gt;     
  &lt;/context-param&gt;
  
  &lt;context-param&gt;
      &lt;param-name&gt;log4jConfigLocation&lt;/param-name&gt;
      &lt;param-value&gt;/WEB-INF/log4j.properties&lt;/param-value&gt;
  &lt;/context-param&gt;
  
  &lt;listener&gt;
      &lt;listener-class&gt;org.springframework.web.util.Log4jConfigListener&lt;/listener-class&gt;
  &lt;/listener&gt;
  
  &lt;!--  Protect against XSS --&gt;
  &lt;context-param&gt;
      &lt;param-name&gt;defaultHtmlEscape&lt;/param-name&gt;
      &lt;param-value&gt;true&lt;/param-value&gt;
  &lt;/context-param&gt;
  
  &lt;listener&gt;
      &lt;listener-class&gt;org.springframework.web.context.ContextLoaderListener&lt;/listener-class&gt;
  &lt;/listener&gt; 
  
  
  &lt;!-- Deploys the 'accounts' dispatcher servlet whose configuration resides in /WEB-INF/mvc-config.xml --&gt;
  &lt;servlet&gt;
      &lt;servlet-name&gt;appname&lt;/servlet-name&gt;
      &lt;servlet-class&gt;org.springframework.web.servlet.DispatcherServlet&lt;/servlet-class&gt;
      &lt;init-param&gt;
          &lt;param-name&gt;contextConfigLocation&lt;/param-name&gt;
          &lt;param-value&gt;/WEB-INF/appname-servlet.xml&lt;/param-value&gt;
      &lt;/init-param&gt;
  &lt;/servlet&gt;
  
  &lt;!-- Maps all URLs to the 'appname' servlet --&gt;
  &lt;servlet-mapping&gt;
      &lt;servlet-name&gt;appname&lt;/servlet-name&gt;
      &lt;url-pattern&gt;*.htm&lt;/url-pattern&gt;
  &lt;/servlet-mapping&gt;
  
  &lt;!-- 
  &lt;error-page&gt;
      &lt;exception-type&gt;java.lang.Exception&lt;/exception-type&gt;
      &lt;location&gt;/error.jsp&lt;/location&gt;
  &lt;/error-page&gt;
   --&gt;
      &lt;welcome-file-list&gt;
          &lt;welcome-file&gt;/WEB-INF/views/appname_main.jsp&lt;/welcome-file&gt;
      &lt;/welcome-file-list&gt;     
  &lt;session-config&gt;
      &lt;session-timeout&gt;60&lt;/session-timeout&gt;
              &lt;cookie-config&gt;
                &lt;http-only&gt;true&lt;/http-only&gt;
              &lt;/cookie-config&gt;                
  &lt;/session-config&gt;
  </code></pre>
  
  <p></p>
  
  <p>What's happening right now is all attempts to hit the app "appName" result in immediate prompting for authentication.  From reading up on Waffle, I can only assume this is the fall-back authentication because it failed to get the Windows token and authenticate the user (either through a failed attempt or invalid credentials).</p>
  
  <p>Previous attempts have included not using 'hasRole' but instead using </p>
  
  <pre><code>access="IS_AUTHENTICATED_FULLY" /&gt;
  </code></pre>
  
  <p>This wouldn't check the role of the user, but at least would restrict access to the app based on domain authentication.  Unfortunately in this case, it still prompted the user every time they hit the app.  At least this configuration actually allowed domain users access to the app, unlike the 'hasRole' approach which has returned an Access Denied each time.</p>
  
  <p>Any insights would be appreciated...</p>
  
  <p>[EDIT:  Adding some detail from our logs]</p>
  
  <p>It turns out I was actually getting false positive results when I thought single sign-on was working with "IS_AUTHENTICATED_FULLY".  The browser was caching the credentials and applying them on the request, so SSO was never really working.  I get prompted at all times.  ROLE_USER yields the same results:  prompting and accepting the credentials.</p>
  
  <p>Strangely, we've run into trouble trying to get some detail out of waffle.  We added the following lines to Tomcat's conf logging.properties:</p>
  
  <pre><code>waffle.servlet.NegotiateSecurityFilter.level = FINE
  waffle.servlet.spi.SecurityFilterProviderCollection.level = FINE
  waffle.servlet.spi.NegotiateSecurityFilterProvider.level = FINE
  waffle.servlet.spi.BasicSecurityFilterProvider.level = FINE
  </code></pre>
  
  <p>Yet, localhost, catalina, etc produce no extra detail regarding waffle.</p>
  
  <p>The only logging info we could find related to the roles in play was this:</p>
  
  <pre><code>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'&gt;
  2013-02-21 11:25:10,527 DEBUG [org.springframework.security.web.FilterChainProxy] -     &lt;/WEB-INF/views/ourappname_main.jsp at position 6 of 8 in additional filter chain; firing     Filter: 'SessionManagementFilter'&gt;
  2013-02-21 11:25:10,528 DEBUG [org.springframework.security.web.FilterChainProxy] -     &lt;/WEB-INF/views/ourappname_main.jsp at position 7 of 8 in additional filter chain; firing     Filter: 'ExceptionTranslationFilter'&gt;
  2013-02-21 11:25:10,528 DEBUG [org.springframework.security.web.FilterChainProxy] -     &lt;/WEB-INF/views/ourappname_main.jsp at position 8 of 8 in additional filter chain; firing     Filter: 'FilterSecurityInterceptor'&gt;
  2013-02-21 11:25:10,529 DEBUG    [org.springframework.security.web.access.intercept.FilterSecurityInterceptor] - &lt;Secure     object: FilterInvocation: URL: /WEB-INF/views/ourappname_main.jsp; Attributes:     [IS_AUTHENTICATED_FULLY]&gt;
     2013-02-21 11:25:10,529 DEBUG         [org.springframework.security.web.access.intercept.FilterSecurityInterceptor] - &lt;Previously         Authenticated:        org.springframework.security.authentication.AnonymousAuthenticationToken@905571d8:         Principal: anonymous
  </code></pre>
  
  <p>From WFETCH we captured this:</p>
  
  <pre><code>User; Credentials: [PROTECTED]; 
  Authenticated: true; 
  Details: org.springframework.security.web.authentication.WebAuthenticationDetails@0: 
  RemoteIpAddress: 10.10.10.10; 
  SessionId: null; 
  Granted Authorities: ROLE_ANONYMOUS&gt;
  http://10.10.10.10/ourappname/
  Transfer-Encoding: chunked
  Date: Thu, 21 Feb 2013 16:29:42 GMT
  </code></pre>
  
  <p>[EDIT AGAIN]
  The Header info from the failed call as requested.  It is worth noting that the Waffle-filter sample is working as desired with no prompting of the user when localhost is used.  When the IP or domain is used, it prompts.  I'm guessing this is a system administration/trusted host issue?  </p>
  
  <pre><code>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
  </code></pre>