복붙노트

[SPRING] Spring의 CAS 서비스 속성에서 서비스 URL을 올바르게 설정하는 방법

SPRING

Spring의 CAS 서비스 속성에서 서비스 URL을 올바르게 설정하는 방법

Spring Security + CAS로 작업 할 때 CAS에 전송되는 콜백 URL, 즉 서비스 등록 정보로 작은 도로 블록을 계속 공격합니다. 나는 이것과 이것과 같은 많은 예제를 보았지만 모두 하드 코딩 된 URL (심지어 Spring의 CAS 문서)을 사용합니다. 전형적인 싹둑은 이렇게 보입니다 ...

  <bean id="serviceProperties" class="org.springframework.security.ui.cas.ServiceProperties">
    <property name="service" value="http://localhost:8080/click/j_spring_cas_security_check" />
  </bean>

먼저,이 WAR를 어디서나 배포 할 수 있기를 원하기 때문에 서버 이름이나 포트를 하드 코딩하지 않고 컴파일 타임에 특정 DNS 항목에 응용 프로그램을 연결하고 싶지 않습니다. 둘째, Spring이 자동으로 애플리케이션의 컨텍스트를 감지 할 수없고 URL의 요청을 자동으로 검색 할 수없는 이유를 모르겠습니다. 이 문장의 첫 번째 부분은 여전히 ​​유효하지만 Raghuram은이 링크로 아래에서 지적했듯이 보안상의 이유로 클라이언트로부터 HTTP 호스트 헤더를 신뢰할 수 없습니다.

이상 적으로 서비스 URL은 사용자가 요청한 것과 정확히 일치합니다 (요청이 mycompany.com의 하위 도메인과 같이 유효한 경우). 그래서 완벽하거나 최소한 내가 상대 경로 만 지정하고 싶습니다. 응용 프로그램 컨텍스트 루트를 설정하고 Spring에서 서비스 URL을 즉시 결정하도록합니다. 다음과 같은 것 ...

  <bean id="serviceProperties" class="org.springframework.security.ui.cas.ServiceProperties">
    <property name="service" value="/my_cas_callback" />
  </bean>

또는...

  <bean id="serviceProperties" class="org.springframework.security.ui.cas.ServiceProperties">
    <property name="service" value="${container.and.app.derived.value.here}" />
  </bean>

이 중 어떤 것이 가능하거나 쉽지 않습니까?

해결법

  1. ==============================

    1.Spring 2.6.5에서는 org.springframework.security.ui.cas.ServiceProperties를 확장 할 수있다.

    Spring 2.6.5에서는 org.springframework.security.ui.cas.ServiceProperties를 확장 할 수있다.

    Spring 3에서는 CasAuthenticationProvider와 CasEntryPoint를 서브 클래스 화하여 사용자 정의 버전의 ServiceProperties와 함께 사용하고 getService () 메소드를보다 동적 인 구현으로 재정 의하여이 방법을 사용할 수 있습니다.

    호스트 헤더를 사용하여 필요한 도메인을 계산하고 통제하에있는 도메인 / 하위 도메인 만 사용되는지 확인하여 호스트를보다 안전하게 만들 수 있습니다. 그런 다음이 값에 몇 가지 구성 가능한 값을 추가하십시오.

    물론 당신은 당신의 구현이 안전하지 않을 위험에 처할 것입니다 ... 그래서 조심하십시오.

    결국 다음과 같이 보일 수 있습니다.

    <bean id="serviceProperties" class="my.ServiceProperties">
        <property name="serviceRelativeUrl" value="/my_cas_callback" />
        <property name="validDomainPattern" value="*.mydomain.com" />
    </bean>
    
  2. ==============================

    2.나는 이것이 약간 낡았다는 것을 알고 있지만, 나는이 바로 그 문제를 해결해야만했고, 새로운 스택에서 실제로 아무것도 찾을 수 없었다.

    나는 이것이 약간 낡았다는 것을 알고 있지만, 나는이 바로 그 문제를 해결해야만했고, 새로운 스택에서 실제로 아무것도 찾을 수 없었다.

    우리는 여러 환경이 동일한 CAS 서비스 (dev, qa, uat 및 로컬 개발 환경)를 공유하고 있습니다. 우리는 역방향 프록시를 통해 클라이언트 측 웹 서버를 통해 직접 백엔드 서버 자체로 여러 URL에서 각 환경을 공격 할 수 있습니다. 즉, 단일 URL을 지정하는 것이 기껏해야 어렵다는 것을 의미합니다. 어쩌면 이렇게 할 수 있지만 동적 ServiceProperties.getService ()를 사용할 수있는 방법이 있습니다. 아마도 URL이 어떤 시점에서 도용되지 않도록하기 위해 일종의 서버 접미사 검사를 추가 할 것입니다.

    다음은 보안 리소스에 액세스하는 데 사용되는 URL에 관계없이 기본 CAS 플로우를 작동시키기 위해 수행 한 작업입니다.

    여기 내 스프링 구성 빈의 긴 형태가있다.

    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
    public class CasSecurityConfiguration extends WebSecurityConfigurerAdapter {
    

    그냥 일반적인 봄 구성 빈.

    @Value("${cas.server.url:https://localhost:9443/cas}")
    private String casServerUrl;
    
    @Value("${cas.service.validation.uri:/webapi/j_spring_cas_security_check}")
    private String casValidationUri;
    
    @Value("${cas.provider.key:whatever_your_key}")
    private String casProviderKey;
    

    일부 외부화 된 구성 매개 변수.

    @Bean
    public ServiceProperties serviceProperties() {
        ServiceProperties serviceProperties = new ServiceProperties();
        serviceProperties.setService(casValidationUri);
        serviceProperties.setSendRenew(false);
        serviceProperties.setAuthenticateAllArtifacts(true);
        return serviceProperties;
    }
    

    위의 핵심은 setAuthenticateAllArtifacts (true) 호출입니다. 이렇게하면 서비스 티켓 유효성 검사자가 하드 코딩 된 ServiceProperties.getService () 호출이 아닌 AuthenticationDetailsSource 구현을 사용하게됩니다.

    @Bean
    public Cas20ServiceTicketValidator cas20ServiceTicketValidator() {
        return new Cas20ServiceTicketValidator(casServerUrl);
    }
    

    표준 티켓 유효성 검사기 ..

    @Resource
    private UserDetailsService userDetailsService;
    
    @Bean
    public AuthenticationUserDetailsService authenticationUserDetailsService() {
        return new AuthenticationUserDetailsService() {
            @Override
            public UserDetails loadUserDetails(Authentication token) throws UsernameNotFoundException {
                String username = (token.getPrincipal() == null) ? "NONE_PROVIDED" : token.getName();
                return userDetailsService.loadUserByUsername(username);
            }
        };
    }
    

    기존 UserDetailsService에 대한 표준 후크

    @Bean
    public CasAuthenticationProvider casAuthenticationProvider() {
        CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider();
        casAuthenticationProvider.setAuthenticationUserDetailsService(authenticationUserDetailsService());
        casAuthenticationProvider.setServiceProperties(serviceProperties());
        casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator());
        casAuthenticationProvider.setKey(casProviderKey);
        return casAuthenticationProvider;
    }
    

    표준 인증 공급자

    @Bean
    public CasAuthenticationFilter casAuthenticationFilter() throws Exception {
        CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter();
        casAuthenticationFilter.setAuthenticationManager(authenticationManager());
        casAuthenticationFilter.setServiceProperties(serviceProperties());
        casAuthenticationFilter.setAuthenticationDetailsSource(dynamicServiceResolver());
        return casAuthenticationFilter;
    }
    

    여기서 핵심은 dynamicServiceResolver () 설정입니다.

    @Bean
    AuthenticationDetailsSource<HttpServletRequest,
            ServiceAuthenticationDetails> dynamicServiceResolver() {
        return new AuthenticationDetailsSource<HttpServletRequest, ServiceAuthenticationDetails>() {
            @Override
            public ServiceAuthenticationDetails buildDetails(HttpServletRequest context) {
                final String url = makeDynamicUrlFromRequest(serviceProperties());
                return new ServiceAuthenticationDetails() {
                    @Override
                    public String getServiceUrl() {
                        return url;
                    }
                };
            }
        };
    }
    

    makeDynamicUrlFromRequest () 메소드에서 동적으로 서비스 URL을 생성합니다. 이 비트는 티켓 유효성 검사시 사용됩니다.

    @Bean
    public CasAuthenticationEntryPoint casAuthenticationEntryPoint() {
    
        CasAuthenticationEntryPoint casAuthenticationEntryPoint = new CasAuthenticationEntryPoint() {
            @Override
            protected String createServiceUrl(final HttpServletRequest request, final HttpServletResponse response) {
                return CommonUtils.constructServiceUrl(null, response, makeDynamicUrlFromRequest(serviceProperties())
                        , null, serviceProperties().getArtifactParameter(), false);
            }
        };
        casAuthenticationEntryPoint.setLoginUrl(casServerUrl + "/login");
        casAuthenticationEntryPoint.setServiceProperties(serviceProperties());
        return casAuthenticationEntryPoint;
    }
    

    이 부분은 CAS가 로그인 화면으로 리디렉션하려고 할 때 동일한 동적 URL 작성자를 사용합니다.

    private String makeDynamicUrlFromRequest(ServiceProperties serviceProperties){
        return "https://howeverYouBuildYourOwnDynamicUrl.com";
    }
    

    이것은 무엇이든 만들 수 있습니다. 나는 우리가 설정 한 서비스의 URI를 유지하기 위해서만 ServiceProperties를 전달했다. 우리는 뒷면에 HATEAOS를 사용하고 다음과 같은 구현을가집니다.

    return UriComponentsBuilder.fromHttpUrl(
                linkTo(methodOn(ExposedRestResource.class)
                        .aMethodOnThatResource(null)).withSelfRel().getHref())
                .replacePath(serviceProperties.getService())
                .build(false)
                .toUriString();
    

    편집 : 다음은 유효한 서버 접미사 목록에 대해 수행 한 작업입니다.

    private List<String> validCasServerHostEndings;
    
    @Value("${cas.valid.server.suffixes:company.com,localhost}")
    private void setValidCasServerHostEndings(String endings){
        validCasServerHostEndings = new ArrayList<>();
        for (String ending : StringUtils.split(endings, ",")) {
            if (StringUtils.isNotBlank(ending)){
                validCasServerHostEndings.add(StringUtils.trim(ending));
            }
        }
    }
    
    private String makeDynamicUrlFromRequest(ServiceProperties serviceProperties){
        UriComponents url = UriComponentsBuilder.fromHttpUrl(
                linkTo(methodOn(ExposedRestResource.class)
                        .aMethodOnThatResource(null)).withSelfRel().getHref())
                .replacePath(serviceProperties.getService())
                .build(false);
        boolean valid = false;
        for (String validCasServerHostEnding : validCasServerHostEndings) {
            if (url.getHost().endsWith(validCasServerHostEnding)){
                valid = true;
                break;
            }
        }
        if (!valid){
            throw new AccessDeniedException("The server is unable to authenticate the requested url.");
        }
        return url.toString();
    }
    
  3. ==============================

    3.Maven 사용, 속성 자리 표시 자 추가 및 빌드 프로세스에서 구성

    Maven 사용, 속성 자리 표시 자 추가 및 빌드 프로세스에서 구성

  4. ==============================

    4.Pablojim 제안 CasAuthenticationProvider 하위 클래스했지만 솔루션은 매우 쉽습니다! SPEL (Spring Expression Language)을 사용하면 dinamically URL을 얻을 수 있습니다.

    Pablojim 제안 CasAuthenticationProvider 하위 클래스했지만 솔루션은 매우 쉽습니다! SPEL (Spring Expression Language)을 사용하면 dinamically URL을 얻을 수 있습니다.

    예 : 값 ="https : // # {T (java.net.InetAddress) .getLocalHost () .getHostName ()}

  5. ==============================

    5.나는 이것을 시도하지 않았지만 Spring Security는 Bob의 블로그 업데이트에 표시된 SavedRequestAwareAuthenticationSuccessHandler를 사용하여 이에 대한 해결책을 제시합니다.

    나는 이것을 시도하지 않았지만 Spring Security는 Bob의 블로그 업데이트에 표시된 SavedRequestAwareAuthenticationSuccessHandler를 사용하여 이에 대한 해결책을 제시합니다.

  6. from https://stackoverflow.com/questions/4555206/how-to-correctly-set-the-service-url-in-springs-cas-service-properties by cc-by-sa and MIT license