2016-12-02 55 views
0

我正在尋求將當前整體系統遷移到微服務架構的解決方案。我想使用Spring Integration和Spring Security來集成和保護這些服務。根據我的理解,保護後端服務更像是單點登錄(SSO)。我使用Jasig CAS 4.2.7(似乎Spring Security的工作正常)來集中驗證用戶,Spring Integration 4.2.11.RELEASE和Spring Security 4.0.4.RELEASE。如何在使用Spring與CAS集成時訪問安全的後端服務?

我創建了一個Maven項目,其中包含兩個名爲web和service的模塊,這兩個模塊都是web應用程序。我在相同的本地Tomcat(版本7.0.36)上部署這三個war文件,並將jimi和bob添加到CAS屬性文件中,以確保它們通過CAS的身份驗證。當我嘗試訪問URL​​時,我得到了前端應用程序的身份驗證,但在後端服務上禁止訪問。

POM文件如下所示。

<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>prototype.integration.security</groupId> 
    <artifactId>prototype-integration-security</artifactId> 
    <version>0.0.1-SNAPSHOT</version> 
    <packaging>pom</packaging> 

    <name>prototype-integration-security</name> 

    <properties> 
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 
    </properties> 

    <build> 
    <plugins> 
     <plugin> 
     <groupId>org.apache.maven.plugins</groupId> 
     <artifactId>maven-compiler-plugin</artifactId> 
     <version>3.5.1</version> 
     <configuration> 
      <source>1.7</source> 
      <target>1.7</target> 
     </configuration> 
     </plugin> 
     <plugin> 
     <groupId>org.apache.maven.plugins</groupId> 
     <artifactId>maven-war-plugin</artifactId> 
     <version>2.6</version> 
     <configuration> 
      <warName>${project.name}</warName> 
     </configuration> 
     </plugin> 
    </plugins> 
    </build> 

    <dependencies> 
    <dependency> 
     <groupId>junit</groupId> 
     <artifactId>junit</artifactId> 
     <version>4.12</version> 
     <scope>test</scope> 
    </dependency> 
    <dependency> 
     <groupId>org.springframework.integration</groupId> 
     <artifactId>spring-integration-http</artifactId> 
     <version>4.2.11.RELEASE</version> 
    </dependency> 
    <dependency> 
     <groupId>org.springframework.security</groupId> 
     <artifactId>spring-security-web</artifactId> 
     <version>4.0.4.RELEASE</version> 
    </dependency> 
    <dependency> 
     <groupId>org.springframework</groupId> 
     <artifactId>spring-test</artifactId> 
     <version>4.2.7.RELEASE</version> 
     <scope>test</scope> 
    </dependency> 
    <dependency> 
     <groupId>org.apache.logging.log4j</groupId> 
     <artifactId>log4j-slf4j-impl</artifactId> 
     <version>2.7</version> 
    </dependency> 
    <dependency> 
     <groupId>org.apache.logging.log4j</groupId> 
     <artifactId>log4j-jcl</artifactId> 
     <version>2.7</version> 
    </dependency> 
    <dependency> 
     <groupId>javax</groupId> 
     <artifactId>javaee-api</artifactId> 
     <version>7.0</version> 
     <scope>provided</scope> 
    </dependency> 
    <dependency> 
     <groupId>org.springframework.integration</groupId> 
     <artifactId>spring-integration-security</artifactId> 
     <version>4.2.11.RELEASE</version> 
    </dependency> 
    <dependency> 
     <groupId>org.apache.logging.log4j</groupId> 
     <artifactId>log4j-core</artifactId> 
     <version>2.7</version> 
    </dependency> 
    <dependency> 
     <groupId>org.springframework.security</groupId> 
     <artifactId>spring-security-config</artifactId> 
     <version>4.0.4.RELEASE</version> 
    </dependency> 
    <dependency> 
     <groupId>org.springframework.security</groupId> 
     <artifactId>spring-security-cas</artifactId> 
     <version>4.0.4.RELEASE</version> 
    </dependency> 
    <dependency> 
     <groupId>org.apache.httpcomponents</groupId> 
     <artifactId>httpclient</artifactId> 
     <version>4.5.1</version> 
    </dependency> 
    <dependency> 
     <groupId>com.fasterxml.jackson.core</groupId> 
     <artifactId>jackson-databind</artifactId> 
     <version>2.6.4</version> 
    </dependency> 
    </dependencies> 
    <modules> 
    <module>prototype-integration-security-web</module> 
    <module>prototype-integration-security-service</module> 
    </modules> 
</project> 

兩個模塊的部署描述文件web.xml除了顯示名稱外如下所示。

<web-app xmlns="http://java.sun.com/xml/ns/javaee" 
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
     xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 
     xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
          http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 
     id="IntegrationSecurityWeb" version="3.0"> 
    <display-name>Integration Security Web Prototype</display-name> 

    <servlet> 
    <servlet-name>dispatcher</servlet-name> 
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 
    <load-on-startup>1</load-on-startup> 
    </servlet> 
    <servlet-mapping> 
    <servlet-name>dispatcher</servlet-name> 
    <url-pattern>/*</url-pattern> 
    </servlet-mapping> 

    <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> 
</web-app> 

在Web模塊的Spring應用程序上下文配置文件中,dispatcher-servlet.xml如下所示。

<beans xmlns="http://www.springframework.org/schema/beans" 
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
     xmlns:task="http://www.springframework.org/schema/task" 
     xmlns:security="http://www.springframework.org/schema/security" 
     xmlns:int="http://www.springframework.org/schema/integration" 
     xmlns:int-http="http://www.springframework.org/schema/integration/http" 
     xmlns:int-security="http://www.springframework.org/schema/integration/security" 
     xsi:schemaLocation="http://www.springframework.org/schema/beans 
          http://www.springframework.org/schema/beans/spring-beans.xsd 
          http://www.springframework.org/schema/task 
          http://www.springframework.org/schema/task/spring-task.xsd 
          http://www.springframework.org/schema/security 
          http://www.springframework.org/schema/security/spring-security.xsd 
          http://www.springframework.org/schema/integration 
          http://www.springframework.org/schema/integration/spring-integration-4.2.xsd 
          http://www.springframework.org/schema/integration/http 
          http://www.springframework.org/schema/integration/http/spring-integration-http-4.2.xsd 
          http://www.springframework.org/schema/integration/security 
          http://www.springframework.org/schema/integration/security/spring-integration-security-4.2.xsd"> 

    <bean id="restTemplate" class="org.springframework.web.client.RestTemplate"> 
    <constructor-arg> 
     <bean class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory"> 
     <constructor-arg> 
      <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> 
      <property name="targetClass" value="org.apache.http.impl.client.HttpClients"/> 
      <property name="targetMethod" value="createMinimal"/> 
      </bean> 
     </constructor-arg> 
     </bean> 
    </constructor-arg> 
    <property name="messageConverters"> 
     <list> 
     <bean class="org.springframework.http.converter.StringHttpMessageConverter" /> 
     <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" /> 
     <bean class="org.springframework.http.converter.FormHttpMessageConverter"> 
     </bean> 
     </list> 
    </property> 
    </bean> 

    <bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties"> 
    <property name="service" value="http://localhost:8080/prototype-integration-security-web/login/cas" /> 
    <property name="sendRenew" value="false" /> 
    </bean> 

    <!-- Access voters --> 
    <bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased"> 
    <constructor-arg name="decisionVoters"> 
     <list> 
     <bean class="org.springframework.security.access.vote.RoleHierarchyVoter"> 
      <constructor-arg> 
      <bean class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl"> 
       <property name="hierarchy"> 
       <value> 
        ROLE_ADMIN > ROLE_USER 
       </value> 
       </property> 
      </bean> 
      </constructor-arg> 
     </bean> 
     <bean class="org.springframework.security.access.vote.AuthenticatedVoter" /> 
     </list> 
    </constructor-arg> 
    </bean> 

    <bean id="casEntryPoint" class="org.springframework.security.cas.web.CasAuthenticationEntryPoint"> 
    <property name="loginUrl" value="https://localhost:8443/cas/login" /> 
    <property name="serviceProperties" ref="serviceProperties" /> 
    </bean> 

    <bean id="casFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter"> 
    <property name="authenticationManager" ref="authenticationManager" /> 
    </bean> 

    <!-- This filter handles a Single Logout Request from the CAS Server --> 
    <bean id="singleLogoutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter" /> 

    <!-- This filter redirects to the CAS Server to signal Single Logout should be performed --> 
    <bean id="requestSingleLogoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter"> 
    <constructor-arg value="http://localhost:8080/cas/logout" /> 
    <constructor-arg> 
     <bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler" /> 
    </constructor-arg> 
    <property name="filterProcessesUrl" value="/logout/cas" /> 
    </bean> 

    <security:http entry-point-ref="casEntryPoint" access-decision-manager-ref="accessDecisionManager" use-expressions="false"> 
    <security:intercept-url pattern="/admin/**" access="ROLE_ADMIN" /> 
    <security:intercept-url pattern="/**" access="ROLE_USER" /> 
    <security:form-login /> 
    <security:logout /> 
    <security:custom-filter before="LOGOUT_FILTER" ref="requestSingleLogoutFilter"/> 
    <security:custom-filter before="CAS_FILTER" ref="singleLogoutFilter"/> 
    <security:custom-filter position="CAS_FILTER" ref="casFilter" /> 
    </security:http> 

    <security:user-service id="userService"> 
    <security:user name="jimi" password="jimi" authorities="ROLE_ADMIN" /> 
    <security:user name="bob" password="bob" authorities="ROLE_USER" /> 
    </security:user-service> 

    <bean id="casAuthenticationProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider"> 
    <property name="authenticationUserDetailsService"> 
     <bean class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper"> 
     <constructor-arg index="0" ref="userService" /> 
     </bean> 
    </property> 
    <property name="serviceProperties" ref="serviceProperties" /> 
    <property name="ticketValidator"> 
     <bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator"> 
     <constructor-arg index="0" value="https://localhost:8443/cas" /> 
     </bean> 
    </property> 
    <property name="key" value="localCAS" /> 
    </bean> 

    <security:authentication-manager alias="authenticationManager"> 
    <security:authentication-provider ref="casAuthenticationProvider" /> 
    </security:authentication-manager> 

    <int:channel-interceptor order="99"> 
    <bean class="org.springframework.integration.security.channel.SecurityContextPropagationChannelInterceptor"/> 
    </int:channel-interceptor> 

    <task:executor id="pool" pool-size="5"/> 

    <int:poller id="poller" default="true" fixed-rate="1000"/> 

    <int-security:secured-channels> 
    <int-security:access-policy pattern="user*" send-access="ROLE_USER" /> 
    <int-security:access-policy pattern="admin*" send-access="ROLE_ADMIN" /> 
    </int-security:secured-channels> 

    <int-http:inbound-channel-adapter path="/user*" supported-methods="GET, POST" channel="userRequestChannel" /> 

    <int:channel id="userRequestChannel"> 
    <int:queue/> 
    </int:channel> 

    <int-http:outbound-channel-adapter url="http://localhost:8080/prototype-integration-security-service/query?ticket={ticket}" 
            http-method="GET" 
            rest-template="restTemplate" 
            channel="userRequestChannel"> 
    <int-http:uri-variable name="ticket" expression="T(org.springframework.security.core.context.SecurityContextHolder).context.authentication.credentials"/> 
    </int-http:outbound-channel-adapter> 

    <int-http:inbound-channel-adapter path="/admin/callback*" 
            supported-methods="GET, POST" 
            channel="adminRequestChannel" /> 

    <int:channel id="adminRequestChannel"> 
    <int:queue/> 
    </int:channel> 

    <int:logging-channel-adapter id="logging" channel="adminRequestChannel" level="DEBUG" /> 
</beans> 

在服務模塊的上下文配置文件,調度員servlet.xml中看起來如下。

<beans xmlns="http://www.springframework.org/schema/beans" 
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
     xmlns:task="http://www.springframework.org/schema/task" 
     xmlns:security="http://www.springframework.org/schema/security" 
     xmlns:int="http://www.springframework.org/schema/integration" 
     xmlns:int-http="http://www.springframework.org/schema/integration/http" 
     xmlns:int-security="http://www.springframework.org/schema/integration/security" 
     xsi:schemaLocation="http://www.springframework.org/schema/beans 
          http://www.springframework.org/schema/beans/spring-beans.xsd 
          http://www.springframework.org/schema/task 
          http://www.springframework.org/schema/task/spring-task.xsd 
          http://www.springframework.org/schema/security 
          http://www.springframework.org/schema/security/spring-security.xsd 
          http://www.springframework.org/schema/integration 
          http://www.springframework.org/schema/integration/spring-integration-4.2.xsd 
          http://www.springframework.org/schema/integration/http 
          http://www.springframework.org/schema/integration/http/spring-integration-http-4.2.xsd 
          http://www.springframework.org/schema/integration/security 
          http://www.springframework.org/schema/integration/security/spring-integration-security-4.2.xsd"> 

    <bean id="restTemplate" class="org.springframework.web.client.RestTemplate"> 
    <constructor-arg> 
     <bean class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory"> 
     <constructor-arg> 
      <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> 
      <property name="targetClass" value="org.apache.http.impl.client.HttpClients"/> 
      <property name="targetMethod" value="createMinimal"/> 
      </bean> 
     </constructor-arg> 
     </bean> 
    </constructor-arg> 
    <property name="messageConverters"> 
     <list> 
     <bean class="org.springframework.http.converter.StringHttpMessageConverter" /> 
     <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" /> 
     <bean class="org.springframework.http.converter.FormHttpMessageConverter"> 
     </bean> 
     </list> 
    </property> 
    </bean> 

    <bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties"> 
    <property name="service" value="http://localhost:8080/prototype-integration-security-service/login/cas" /> 
    <property name="sendRenew" value="false" /> 
    </bean> 

    <!-- Access voters --> 
    <bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased"> 
    <constructor-arg name="decisionVoters"> 
     <list> 
     <bean class="org.springframework.security.access.vote.RoleHierarchyVoter"> 
      <constructor-arg> 
      <bean class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl"> 
       <property name="hierarchy"> 
       <value> 
        ROLE_ADMIN > ROLE_USER 
       </value> 
       </property> 
      </bean> 
      </constructor-arg> 
     </bean> 
     <bean class="org.springframework.security.access.vote.AuthenticatedVoter" /> 
     <!-- <bean class="org.springframework.security.web.access.expression.WebExpressionVoter" /> --> 
     </list> 
    </constructor-arg> 
    </bean> 

    <bean id="casEntryPoint" class="org.springframework.security.cas.web.CasAuthenticationEntryPoint"> 
    <property name="loginUrl" value="https://localhost:8443/cas/login" /> 
    <property name="serviceProperties" ref="serviceProperties" /> 
    </bean> 

    <bean id="casFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter"> 
    <property name="authenticationManager" ref="authenticationManager" /> 
    </bean> 

    <!-- This filter handles a Single Logout Request from the CAS Server --> 
    <bean id="singleLogoutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter" /> 

    <!-- This filter redirects to the CAS Server to signal Single Logout should be performed --> 
    <bean id="requestSingleLogoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter"> 
    <constructor-arg value="https://localhost:8443/cas/logout" /> 
    <constructor-arg> 
     <bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler" /> 
    </constructor-arg> 
    <property name="filterProcessesUrl" value="/logout/cas" /> 
    </bean> 

    <security:http entry-point-ref="casEntryPoint" access-decision-manager-ref="accessDecisionManager" use-expressions="false"> 
    <security:intercept-url pattern="/**" access="ROLE_ADMIN"/> 
    <security:form-login /> 
    <security:logout /> 
    <security:custom-filter before="LOGOUT_FILTER" ref="requestSingleLogoutFilter"/> 
    <security:custom-filter before="CAS_FILTER" ref="singleLogoutFilter"/> 
    <security:custom-filter position="CAS_FILTER" ref="casFilter" /> 
    </security:http> 

    <security:user-service id="userService"> 
    <security:user name="jimi" password="jimi" authorities="ROLE_ADMIN" /> 
    <security:user name="bob" password="bob" authorities="ROLE_USER" /> 
    </security:user-service> 

    <bean id="casAuthenticationProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider"> 
    <property name="authenticationUserDetailsService"> 
     <bean class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper"> 
     <constructor-arg index="0" ref="userService" /> 
     </bean> 
    </property> 
    <property name="serviceProperties" ref="serviceProperties" /> 
    <property name="ticketValidator"> 
     <bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator"> 
     <constructor-arg index="0" value="https://localhost:8443/cas" /> 
     </bean> 
    </property> 
    <property name="key" value="localCAS" /> 
    </bean> 

    <security:authentication-manager alias="authenticationManager"> 
    <security:authentication-provider ref="casAuthenticationProvider" /> 
    </security:authentication-manager> 

    <int:channel-interceptor order="99"> 
    <bean class="org.springframework.integration.security.channel.SecurityContextPropagationChannelInterceptor"/> 
    </int:channel-interceptor> 

    <task:executor id="pool" pool-size="5"/> 

    <int:poller id="poller" default="true" fixed-rate="1000"/> 

    <int-security:secured-channels> 
    <int-security:access-policy pattern=".*" send-access="ROLE_ADMIN" /> 
    </int-security:secured-channels> 

    <int-http:inbound-channel-adapter path="/query*" supported-methods="GET, POST" channel="requestChannel" /> 

    <int:channel id="requestChannel"> 
    <int:queue/> 
    </int:channel> 

    <int-http:outbound-channel-adapter url="http://localhost:8080/prototype-integration-security-web/admin/callback?ticket={ticket}" 
            http-method="GET" 
            rest-template="restTemplate" 
            channel="requestChannel"> 
    <int-http:uri-variable name="ticket" expression="T(org.springframework.security.core.context.SecurityContextHolder).context.authentication.credentials" /> 
    </int-http:outbound-channel-adapter> 
</beans> 

不需要額外的代碼,這就是爲什麼我喜歡Spring集成。我做錯了什麼或錯過了一些配置?請分享您的想法,意見和建議。提前致謝。

回答

0

我從來沒有使用過CAS,但看起來你不同意,你怎麼弄headers.serviceTicket

我覺得你的想法傳播ticket通過URL參數是不錯的,但首先我們必須從傳入的URL中提取它:

成功登錄後,CAS將在用戶的瀏覽器重定向回原創服務。它還將包含一個票據參數,它是一個代表「服務票據」的不透明字符串。繼續前面的例子,瀏覽器重定向到的URL可能是https://server3.company.com/webapp/login/cas?ticket=ST-0-ER94xMJmn6pha35CQRoZ

http://docs.spring.io/spring-security/site/docs/4.2.0.RELEASE/reference/htmlsingle/#cas

爲此,我們可以這樣做:

<int-http:inbound-channel-adapter path="/user*" supported-methods="GET, POST" channel="userRequestChannel"> 
     <int-http:header name="serviceTicket" expression="#requestParams.ticket"/> 
</int-http:inbound-channel-adapter> 

否則,請對此事份額異常,並嘗試跟蹤網絡流量來確定的差距。

UPDATE

根據這個春季安全頁面上的內容描述,CAS,我們有:

...主要將等於CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER,而憑據將服務票證不透明值...

所以,看起來我們並不需要擔心的請求PARAM在<int-http:inbound-channel-adapter>,只是依靠SecurityContext<int-http:outbound-gateway>

<int-http:uri-variable name="ticket" 
     expression="T(org.springframework.security.core.context.SecurityContextHolder).context.authentication.credentials"/> 
+0

謝謝你的建議。我試圖如下,沒有運氣得到了一個異常說,票不是公開的MultipleValueMap,我猜是票的關鍵值對不存在。 ' 也許CasAuthenticationFilter已經取消了票證,我必須實現自己的自定義過濾器以將票證存放在某處。 –

+0

請在我的答案中找到更新。 –

+0

嗨,Artem,非常感謝您的建議,現在我可以從SecurityContextHolder獲取Service Ticket。然而,它還沒有工作,我的訪問仍然被403錯誤拒絕。雖然我嘗試通過設置出站網關的transfer-cookie =「true」來代替Web模塊的原始出站通道適配器,但似乎後端服務根本不驗證票證。 –

相關問題