将 Tomcat 上的 Spring Security 和 Waffle 与角色检查集成

Posted

技术标签:

【中文标题】将 Tomcat 上的 Spring Security 和 Waffle 与角色检查集成【英文标题】:Integrating Spring Security and Waffle on Tomcat with Role checks 【发布时间】:2013-02-04 15:15:52 【问题描述】:

正如标题所示,我正在尝试使用角色在 Tomcat 上集成 Spring Security 和 Waffle。该应用程序将部署到 Windows 环境中,用户已经通过域身份验证并且我想执行单点登录。更进一步,我想检查经过身份验证的用户所属的组并配置拦截器以防止不是已批准组成员的用户访问网络应用程序。

应用上下文如下所示:

<?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" />

这不会检查用户的角色,但至少会根据域身份验证限制对应用程序的访问。不幸的是,在这种情况下,每次用户点击应用程序时,它仍然会提示用户。至少这种配置实际上允许域用户访问应用程序,不像“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

然而,localhost、catalina 等没有提供关于华夫饼的额外细节。

我们能找到的与角色相关的唯一日志信息是:

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

[再次编辑] 请求失败调用的标头信息。值得注意的是,当使用 localhost 时,Waffle-filter 示例可以正常工作,而无需用户提示。使用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

【问题讨论】:

看起来我正在获得 ROLE_ANONYMOUS ......这没有任何意义。有人吗? 能否为失败的请求发布 HTTP 日志? 已按要求添加日志:) 日志显示认证成功。问题出在春天的某个地方。现在它只是授予那些 Spring 权限 - 有些东西没有通过那些华夫饼设置到您的应用程序 - 可能稍后重新分配权限?您需要以某种方式让 Waffle 注销并查看它在说什么,显然按照您的方式设置 FINE 是不够的。 您好,您解决了吗? 【参考方案1】:

我今天也遇到了同样的问题。

问题原来与 Spring 安全上下文中生成的 GrantedAuthority 的名称有关。这将默认设置为 ROLE_"domain"\"role",例如ROLE_MYDOMAIN\MYROLE

这是您应该在 hasRole 检查中检查的名称。

请参阅文档:https://github.com/Waffle/waffle/blob/master/Docs/spring/SpringSecurityAuthenticationProvider.md,

授权

成功登录后,Waffle 将使用 GrantedAuthority 实例填充 Spring Security 的 Authentication 对象。

默认情况下,Waffle 将使用以下内容填充 Authentication 对象:

带有字符串 ROLE_USER 的 GrantedAuthority。 用户所属的每个组一个 GrantedAuthority。 GrantedAuthority 字符串将是带有 ROLE_ 前缀的大写组名。例如,如果用户是Everyone 组的成员,他将获得ROLE_EVERYONE 授予的权限。 可以通过在 waffleSpringAuthenticationProvider 上配置不同的 defaultGrantedAuthority 和 grantAuthorityFactory 来更改默认行为

【讨论】:

这是不准确的,如果您尝试执行建议的操作,则会引发此异常:“原因:java.lang.IllegalArgumentException:角色不应以'ROLE_'开头,因为它是自动插入的。 "

以上是关于将 Tomcat 上的 Spring Security 和 Waffle 与角色检查集成的主要内容,如果未能解决你的问题,请参考以下文章

将 Tomcat 上的 Spring Security 和 Waffle 与角色检查集成

Spring Boot 的 Keycloak 错误:tomcat 上的“不是子类型”

Tomcat 上的 Spring Security SAML 元数据 URL

如何在运行在 Tomcat 上的 Spring Web 应用程序中使用 Spring 的响应式 WebClient

Tomcat 上的 Spring Security/JSF/Hibernate 意外会话劫持?

Tomcat上的Spring Boot War部署不起作用