개발/[Spring] 인증

[SSO] CAS 클라이언트 (3) - 샘플 프로젝트 : CAS 설정 하기

sheriff 2019. 6. 13. 10:56

이전 글에서 CAS 인증 서버에 클라이언트를 등록했다.

 

이제 클라이언트의 인증처리를 CAS 인증 서버에서 동작하도록 클라이언트 프로젝트에 작업해보자.

 

먼저, Spring Security의 CAS 모듈과 관련된 Bean을 설정한다. Spring Security와 Cas 인증 서버와 함께 동작하기 위함이다.

 

다음과 같이 설정 클래스를 생성하고 Bean을 추가해보자.

@Configuration
public class CasClientConfiguration {

    @Bean
    public ServiceProperties serviceProperties() {
        ServiceProperties serviceProperties = new ServiceProperties();
        serviceProperties.setService("http://localhost:9000/login/cas");
        serviceProperties.setSendRenew(false);
        return serviceProperties;
    }

    @Bean
    @Primary
    public AuthenticationEntryPoint authenticationEntryPoint(ServiceProperties sP) {
        CasAuthenticationEntryPoint entryPoint
                = new CasAuthenticationEntryPoint();
        entryPoint.setLoginUrl("https://localhost:8443/cas/login");
        entryPoint.setServiceProperties(sP);
        return entryPoint;
    }

    @Bean
    public TicketValidator ticketValidator() {
        return new Cas30ServiceTicketValidator(
                "https://localhost:8443/cas");
    }

    @Bean
    public CasAuthenticationProvider casAuthenticationProvider() {
        CasAuthenticationProvider provider = new CasAuthenticationProvider();
        provider.setServiceProperties(serviceProperties());
        provider.setTicketValidator(ticketValidator());
        provider.setUserDetailsService(
                s -> new User("kennen", "Mellon", true, true, true, true,
                        AuthorityUtils.createAuthorityList("ROLE_ADMIN")));
        provider.setKey("CAS_PROVIDER_LOCALHOST_9000");
        return provider;
    }
}

위에 추가한 Bean을 하나씩 설명하면

ServiceProperties

[현재 서비스에 대한 정보]
ExceptionTranslationFilter 의 doFilter 동작 중에 AccessDeniedException 이 발생하면 ExceptionTranslationFilter 에 있는 AuthenticationEntryPoint(CasAuthenticationEntryPoint)의 commence 메소드가 실행 된다.
이때 Cas 인증 서버로 이동해 로그인을 시도하게 되는데 Service 정보를 함께 전달 한다.
인증 서버에서 인증 시도 후에 다시 서비스로 Redirect 할 때 service 의 URL 로 사용하기 위해서 이다.

authenticationEntryPoint

Spring Security 에서 httpBasic 설정을 추가하게 되면 Default 로 BasicAuthenticationFilter 가 추가된다.
httpBasic().authenticationEntryPoint 를 이용해 AuthenticationEntryPoint 를 지정할 수 있는데,
CasAuthenticationEntryPoint 를 등록하기 위해 해당 Bean 을 추가한다.
인증이 필요한 페이지에 접근 시 AccessDeniedException 이 발생하면 CasAuthenticationEntryPoint 의 commence 가 실행된다.
Cas Server 의 로그인 페이지로 redirect 된다. (ServiceProperties Bean 참고)

casAuthenticationProvider

CasAuthenticationFilter 가 동작할 때 실제로 인증을 처리하는 주체이다.
Filter 가 동작하는 중에 인증을 진행하게 된다.
Bean 으로 등록한 ServiceProperties, ticketValidator, UserDetailsService 가 이 때 사용되며
Key 는 CasAuthenticationToken 을 만들 때 인자로 쓰인다.

ticketValidator

[티켓 유효성 체크]
실행 되기 전 순서
1. ExceptionTranslationFilter 진행 중에 AccessDeniedException 발생
2. CasAuthenticationEntryPoint.commence 실행
3. Cas 서버에서 인증 성공 후에 - CasAuthenticationFilter.doFilter 실행
4. CasAuthenticationFilter.attemptAuthentication 실행
5. AuthenticationManager.authenticate 실행
6. ProviderManager.authenticate 실행
7. AuthenticationProvider.authenticate 실행
8. CasAuthenticationProvider.authenticate 실행
9. 8번 실행 중에 authenticateNow 실행
10. authenticateNow 메소드 안에서 ticketValidator 사용
설명
Cas 서버에서 인증 성공을 하게 되면 CasAuthenticationFilter 로 들어오게 된다.
이 때 redirect 된 서비스가 인증요청을 보낸 서비스가 맞는지 확인하기 위해서 한번 더 체크를 한다.
redirect 될 때 받은 ticket 과 현재 서비스의 이름을 다시 Cas 서버로 보내고
일치 하면 XML 형식으로 응답을 받게 된다.
이후에는 해당 xml 응답을 parsing 해서 UserDetails 로 변환한 뒤 CasAuthenticationToken 을 만들게 된다.

 

 

이제 특정 경로를 인증이 필요하도록 설정하고, 이 때 CasAuthenticationEntryPoint가 사용 되도록 Spring Security를 설정해보자.

 

WebSecurityConfigurerAdapter를 상속받는 Configuration을 만든다.

 

또한 CasAuthenticationFilter를 Bean으로 추가하고,

CasAuthenticationFilter에서 사용 할 AuthenticationManager 역시 Bean으로 추가한다.

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private AuthenticationEntryPoint authenticationEntryPoint;
    @Autowired
    ServiceProperties serviceProperties;
    @Autowired
    private AuthenticationProvider authenticationProvider;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().regexMatchers("/secured.*", "/login").authenticated()
                .and().authorizeRequests().antMatchers("/favicon.ico", "/static/**").permitAll()
                .and().authorizeRequests().antMatchers("/admin/**").hasAuthority("ADMIN")
                .and().authorizeRequests().antMatchers("/user/**").hasAuthority("USER")
                .and().authorizeRequests().regexMatchers("/").permitAll()
                .and().httpBasic().authenticationEntryPoint(authenticationEntryPoint);
    }
   
    @Bean
    public CasAuthenticationFilter casAuthenticationFilter() throws Exception {
        CasAuthenticationFilter filter = new CasAuthenticationFilter();
        filter.setServiceProperties(serviceProperties); // Bean 위치 : CasConfig.java
        filter.setAuthenticationManager(authenticationManager());
        return filter;
    }

    @Bean
    public AuthenticationManager authenticationManager() {
        return new ProviderManager(Arrays.asList(authenticationProvider));
    }
}

/secured, /login 에 대한 요청은 인증이 필요하도록 설정하고 '/' 에 대한 요청은 public 페이지로 설정했다.

Controller를 생성하고 테스트 해보자.

 

HomeController

@RestController
public class HomeController {

    @GetMapping("/")
    public String home(){
        return "home";
    }
}

SecuredController

@RestController
public class SecuredController {

    @GetMapping("/secured")
    public Authentication secured() {
        Authentication auth = SecurityContextHolder.getContext()
                .getAuthentication();
        return auth;
    }
}

 

먼저 HomeController에 요청을 보내보자.

그리고 /secured 또는 /login 요청을 보내보자.

URL을 확인해보자. 

이전 요청과 포트 번호가 다른 것을 확인할 수 있다. 인증서버로 Redirect 된 것이다.

인증서버에서 인증 가능한 사용자 ID, Password를 입력하자.

그리고 이 ID,Password는 위에서 설정한 CasAuthenticationProvider 에서 UserDetailsService 로 등록했다.

 

로그인 하면 다음과 같은 결과를 확인할 수 있다.

인증이 필요한 요청인 /secured URL을 확인 할 수 있다. 

또한 Controller에서 Return 했던 Authentication 내용을 확인할 수 있다.

 

로그인은 성공했다.

하지만 Single Sign On 은 다수의 Client Application이 한번의 로그인으로 인증처리 하는 것이 목적이기 때문에 Client Application을 하나 더 만들어서 테스트 해본다.

 

다음 글에서는 두 개의 Client가 한번의 로그인으로 인증처리가 되는지 확인해 보자.

 

 

어떻게 인증이 진행 되는지에 대한 자세한 설명은 잠시 미루고 Single Sign On 그리고 Single Sign Out 이 잘 동작하는지를 먼저 확인해보자.