개발놀이터

CAS 인증서버 구축하기 (6) : CAS 인증정책 본문

Spring/Spring Security

CAS 인증서버 구축하기 (6) : CAS 인증정책

마늘냄새폴폴 2024. 2. 29. 23:01

왜 뜬금없이 CAS 서버의 인증정책에 대해서 알아보냐... 

 

제가 지금 문제 상황에 놓여있기 때문입니다. 이를 위해 공식문서를 얼마나 뒤져봤는지 아주 눈알이 빠지기 직전입니다. 

 

우선 제가 이 문제 상황에 빠지게 된 경위에 대해서 설명드리겠습니다. 

 

CAS 공식문서엔 다음과 같이 나와있습니다. 

 

https://apereo.github.io/cas/6.6.x/authentication/Database-Authentication.html

 

CAS - Database Authentication

This property controls the maximum lifetime of a connection in the pool. When a connection reaches this timeout, even if recently used, it will be retired from the pool. An in-use connection will never be retired, only when it is idle will it be removed. T

apereo.github.io

cas: 
  authn: 
    jdbc: 
      query[0]: 
        driver-class: "org.hsqldb.jdbcDriver"

 

이 application.yml 설정을 보시면 직관적으로 다음과 같은 생각을 하게 됩니다. 

 

query[0] 이라고? 그럼 query[1]도 있겠는데? 

 

아하 CAS 서버는 데이터베이스 기반 인증처리를 할 때 설정되어있는 query[0], query[1] 혹은 query[2] 까지 모두 데이터베이스에 찔러보고 값이 있으면 인증을 진행하는구나. 

 

그래서 저도 그걸 철썩같이 믿었습니다. 

 

cas:
  jdbc:
    show-sql: true
  authn:
    policy:
      any:
        enabled: true
    jdbc:
      query[0]:
        driver-class: "com.mysql.cj.jdbc.Driver"
        url: "jdbc:mysql://{public IP}:3306/test?autoReconnect=true&allowMultiQueries=true&serverTimezone=UTC"
        sql: "select id, login_id, login_pw from test.sso where login_id = ?;"
        user: "mysql"
        password: "root"
        field-password: "password"
        password-encoder:
          type: "NONE"
      query[1]:
        driver-class: "com.mysql.cj.jdbc.Driver"
        url: "jdbc:mysql://{public IP}:3306/test2?autoReconnect=true&allowMultiQueries=true&serverTimezone=UTC"
        sql: "select id, username, password from test2.kva_user where username = ?;"
        user: "mysql"
        password: "root"
        field-password: "password"
        password-encoder:
          type: "NONE"


그래서 이렇게 작성했습니다. 

 

그리고 실험해본 결과... 쿼리를 query[0] 만 날려보고 query[1] 은 전송이 안됩니다. 무슨 수를 쓰더라도.

 

우선 여러번의 실험끝에 query[1]이 전송되지 않는다는 사실을 발견했습니다. 

 

"선생님 저기 show-sql에 true로 되어있는걸로 보면 확실히 알 수 있지않나요?"

 

네. 그래서 저렇게 작성했는데 SQL이 안보였습니다. 그래서 여러번 실험을 할 수 밖에 없었구요. 

 

  1. query[0] 이 성공하고 query[1] 은 실패하는 케이스
  2. query[0] 이 실패하고 query[1] 은 성공하는 케이스
  3. query[0] 을 삭제하고 query[1] 을 query[0] 으로 변경해본 케이스
  4. query[1] 이 실패한 데이터를 query[0] 에 성공하게 데이터를 변경해본 케이스

이 다양한 상황에서 어느 하나도 두번째 쿼리가 날아가는 상황은 찾을 수 없었습니다. query[1] 이 전송되는지 확인하기 위해 쿼리를 날릴때마다 데이터베이스 커넥션과 프로세스를 확인했습니다. 

 

결국 가설을 세웠습니다. 

 

  1. query[1] 에 해당하는 데이터베이스 연결이 안되어있고 query[0] 만 전송되는 상황
  2. query[0] 과 query[1] 에 해당하는 데이터베이스 연결은 잘 되었고 query[1] 만 쿼리가 전송되지 않는 상황

저는 DEBUG 레벨 로그를 보고 첫번째 가설을 폐기하였습니다. 

 

그 이유는 다음과 같습니다. 

 

  • 로그에 데이터베이스 커넥션과 관련된 Exception이 없었다. 
  • DEBUG 레벨에서 application.yml에서 설정한 값들이 정상적으로 관리되고 있는 것을 확인하였다. 

때문에 제 가설은 2번으로 더 굳어지게 되었습니다. 

 

여기서 CAS의 인증 정책에 대해서 조금 알아볼 필요가 있습니다. 

 

CAS 인증정책

https://apereo.github.io/cas/6.6.x/authentication/Configuring-Authentication-Policy.html

 

인증 정책은 기본적으로 첫번째로 인증에 실패하면 다음 인증 체인은 끝납니다. 

 

이 말은 무엇이냐. 지금 제가 사용하는 데이터베이스 기반 인증 정책이 실패하면 해당 인증 체인은 종료된다는 것입니다. 

 

즉, query[0]이 실패하면 해당 인증은 끝 query[1]이 추가적으로 실행되지 않습니다. 

 

이것을 보고 처음엔 이해가 되지 않았습니다. 

 

애초에 query[0] 같이 개발자로 하여금 배열이라고 생각이 들게 하여 query[1] 을 직관적으로 생각하게 만들고 query[1] 이 실행조차 되지 않는것이 말이 안되는 것 같았습니다. 

 

또한, 만약 데이터베이스 기반 인증 정책을 사용한다고 하더라도 application.yml 에 등록된 query 들은 전부 실행해보고 인증 실패를 판단해야지 왜 query[0] 하나만 실행해보고 인증 성공 여부를 판단하는지도 이해가 안됐습니다. 

 

그리고 제 직관상 CAS 서버는 각각의 서버들의 인증을 담당해야 하기 때문에 인증을 위해 각각의 서버에 물려있는 데이터베이스를 모두 확인해봐야 한다고 생각했습니다. 

 

때문에, 인증을 직접 수행하는 Handler라는 것을 커스텀하게 작성하여 모든 데이터베이스에 쿼리를 날려보도록 설계하기도 하였습니다. 하지만 구글링 결과 그렇게 의문을 가진 사람도 한명도 없었고 시도해본 사람도 한명도 없었습니다. 

 

모든 정황들은 제 생각이 틀렸다고 말해주고 있었습니다. 

 

결국은 제 직관이 틀렸다는 것을 반증하고 있었는데. 만약 이것이 모두 의도되었다고 가정하고 문제 상황을 생각해보았습니다. 

 

첫번째

만약 CAS 서버에 연결된 서비스들이 수십개라면? 이 상황엔 로그인을 위해서 수십개의 DB에 쿼리를 날려봐야합니다. 그때 발생하는 네트워크 I/O와 데이터베이스 커넥션 생성에 필요한 리소스를 생각하면 애플리케이션 규모가 큰 경우에 문제가 발생할 수 있을 것 같았습니다. 

 

두번째

저의 다양한 실험 끝에 query[1] 은 실행이 되지 않는다는 것을 확인했습니다. 물론 이를 show-sql로 확인하면 확실하게 알 수 있었지만 sql이 보이지 않는 상황에서의 최선이었습니다. 

 

저의 실험끝에 query[1] 이 실행되지 않는 다는 것이 제 생각이 틀렸다는 것을 반증하고 있었습니다. 

 

세번째

데이터베이스 기반 인증을 위해 직접적으로 인증을 수행하는 Handler에 대한 구현체를 모두 살펴봤습니다. 코드를 분석해본 결과 그 어디에서도 여러개의 DataSource를 사용하지 않는 것으로 확인되었습니다. 

 

심지어 반복문이 그 어디에도 사용되지 않았습니다. 

 

결론

결론적으로 제 생각이 틀렸고 제가 모르는 이유가 있음을 암시하고 있었습니다. 제가 직관적으로 생각한대로 CAS가 작동하면 모종의 문제가 생길 수 있는 것 같습니다. 

 

또한, 이 문제 상황을 유심히 듣던 제 동기가 조금 찾아보더니 이렇게 얘기하더군요

 

"만약 query[0] 만 실행된다면 CAS 서버는 다른 서비스로부터 인증을 위해 떨어져 나왔듯이 데이터베이스도 회원 인증만을 위한 데이터베이스가 따로 있어야 하는 것이 아니냐"

 

처음엔 그렇게 하는 경우엔 데이터베이스 마이그레이션이 필수적인데 그거에 들어가는 리소스가 많지 않겠냐고 얘기했지만 결국 이 방법이 맞는 방법인가 싶은 생각이 들었습니다. 

 

다음은 CAS를 위해 마이그레이션을 공부하고 다음 포스팅엔 마이그레이션으로 찾아뵙도록 하겠습니다.