2016-12-02 4 views
0

현재 전체 론적 시스템을 마이크로 서비스 아키텍처로 마이그레이션하는 솔루션을 찾고 있습니다. Spring Integration과 Spring Security를 ​​사용하여 서비스를 통합하고 보안합니다. 내 이해에 따르면 백엔드 서비스를 보호하는 것은 SSO (Single Sign On)와 같습니다. Jasig CAS 4.2.7 (Spring Security와 잘 작동하는 것 같습니다)을 사용하여 사용자를 중앙에서 인증하고, Spring Integration 4.2.11.RELEASE 및 Spring Security 4.0.4.RELEASE를 사용합니다.CAS와 Spring 통합을 사용할 때 보안 백엔드 서비스에 액세스하는 방법은 무엇입니까?

웹 응용 프로그램 인 web 및 service라는 두 개의 모듈로 Maven 프로젝트를 만들었습니다. 3 개의 war 파일을 동일한 로컬 Tomcat (버전 7.0.36)에 배포하고 jimi와 bob을 CAS 등록 정보 파일에 추가하여 CAS의 인증을 통과하도록합니다. URL http://localhost:8080/prototype-integration-security-web/user에 액세스하려고하면 프런트 엔드 응용 프로그램에 인증되지만 백엔드 서비스에서는 액세스가 금지됩니다.

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> 

웹 모듈의 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> 

서비스 모듈의 컨텍스트 구성 파일에서 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-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> 

추가 코드가 필요하지 않으므로 이것이 스프링 통합을 좋아합니다. 잘못되었거나 구성이 누락 되었습니까? 아이디어, 의견 및 제안을 공유하십시오. 미리 감사드립니다.

답변

0

나는 전에 CAS를 사용 해본 적이 없지만 headers.serviceTicket을받는 방법을 공유하지 않은 것처럼 보입니다.

내가 URL의 PARAM 좋은 통해 당신이 생각 ticket을 전파 할 생각하지만, 우선 우리가 들어오는 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에 대한 그 봄 보안 페이지의 설명에 따르면, 우리는이 :

을 ...우리가 <int-http:inbound-channel-adapter>에 요청 PARAM 걱정 그냥 SecurityContext에 의존 할 필요가 없습니다처럼 자격 증명이 서비스 티켓 불투명 값이됩니다 동안 교장은 ... CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER에 따라서

동일 할 것이다 보인다 <int-http:outbound-gateway> :

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

감사합니다. 나는 다음과 같이 시도했지만 행운이 없다면 티켓이 MultipleValueMap의 public이 아니라는 것을 알 수있다. 티켓 키 - 값 쌍이 없다는 것을 의미한다. ' CasAuthenticationFilter가 티켓을 remvoed했을 수도 있고 어딘가에 티켓을 보관할 자체 필터를 구현해야 할 수도 있습니다. –

+0

제 답변에서 업데이트를 찾으십시오. –

+0

안녕하세요, Artem, 많은 의견에 감사 드리며 지금은 ServiceContextHolder에서 Service Ticket을받을 수 있습니다. 그러나 아직 작동하지 않습니다, 내 액세스가 403 오류로 거부되었습니다. 백엔드 서비스는 웹 모듈의 원래 outbound-channel-adapter를 대체하는 outbound-gateway의'transfer-cookie = "true"'를 설정하여 쿠키를 전송하려고 시도하지만 티켓을 전혀 검증하지 않는 것 같습니다. –

관련 문제