알프레스코

알프레스코의 티켓이 이유없이 없어지거나, 401 오류가 자주 발생한다면?

naucika 2014. 10. 22. 15:09

얼마전에 경험한 일입니다. 

 1시간정도 쓰다보면, 갑자기 401 세션 오류가 발생하면서, 로그아웃이 되는 현상이 자주 발견되었습니다.

SSO는 물론이거니와, share 쪽의 session-timout 이나, alf 의 session-time 여러가지 설정을 확인해 봤지만, 한시간 정도에 끊길만한 껀덕지가 없더군요. 계속 이 문제 때문에 골머리를 썩고 있었더랩니다. 결국, share 는 alf 의 rest-api 를 호출할때마다, alf 에서 발행한 티켓을 붙여서 호출하고 있었고, 문제는 이 alf 의 티켓이 invalidation 된다는 이유였습니다. 그래서 alf 의 티켓 설정을 확인해서 아예 expire 모드를 false 로 영구보관을 설정했습니다. 

 그래도. 안되더군요.. 간헐적으로 짧은 시간이 지나면 여전히 401이 떨어졌습니다. 언제 테켓이 invaldation 되는지를 확인하기 위해서, alf 의 코어 로직들 하나하나 뜯어서 트레이스 해 보았습니다. 이 때문에 몰랐던 걸 조금더 밝혀낼 수 있었습니다. 


결론부터 얘기하자면, alf 의 티켓은 기본값으로 in-memory 로 저장되고, 이때 저장소의 티켓 보관 maximum 기본값이 1000 입니다. 즉, 1000개 이상이 발행될 때 부터, 아직 expire 되지 않은 멀쩡한 티켓들이 한개씩 밀려서 사라지는 현상입니다. 수천명이 사용하고 여러가지로 로그인하고 있거든요. ㅎㅎ


아래처럼, alf 의 rest 콜을 할때마다, ticket 이 딸려오면, alf 의 ticket component 가 해당 티켓에 대한 validation 을 수행합니다. 


public void validate(String ticket) throws AuthenticationException {

String currentUser = null;

try {

clearCurrentSecurityContext();

currentUser = ticketComponent.validateTicket(ticket);

authenticationComponent.setCurrentUser(currentUser, UserNameValidationMode.NONE);

} catch (AuthenticationException ae) {

ae.printStackTrace();

clearCurrentSecurityContext();

throw ae;

}

}

public String validateTicket(String ticketString) throws AuthenticationException {

String ticketKey = getTicketKey(ticketString);

Ticket ticket = ticketsCache.get(ticketKey);

if (ticket == null) {

throw new AuthenticationException("Missing ticket for " + ticketString);

}

Ticket newTicket = ticket.getNewEntry();

if (newTicket == null) {

throw new TicketExpiredException("Ticket expired for " + ticketString);

}

if (oneOff) {

ticketsCache.remove(ticketKey);

} else if (newTicket != ticket) {

ticketsCache.put(ticketKey, newTicket);

}

currentTicket.set(ticketString);

return newTicket.getUserName();

}

물론, 이때 ticketsCache 에 해당 티켓이 없으면 AuthenticationException 을 던지고, 이게 결국 401 status 로 share 까지 전달됨으로, 결국 세션 타임아웃과 같은 현상이 발생하며 뜅겨 나가게 됩니다. 물론, 제대로 된 데이터를 반환하지 않게 됩니다. 


%별첨1

share 는 역시 remote-client-session 이란 메모리 내장 객체내에 한번 인증받은 alf 쪽의 티켓을 보관하고 있다가 재사용합니다. 그냥 rest-api 를 통해 (/login) 등으로 alf 의 티켓을 갱신해 봤자, share 의 connect-session 객체에 넣어주지 않으면 무용지물 입니다. (alfresco 쪽의 티켓만 더 늘어나 상황을 악화시킵니다. ㅎ)


%별첨2

alf 의 서버세션은 무의미 합니다. 인증과는 무관하며, 단지 webdev (alf 의 웹 클라이언트) 를 사용할 때에만 문제가 됩니다. 즉, share 를 사용하게 되면 alf 의 서버세션은 별 상관이 없습니다. 


%별첨3

alf 는 cronjob 을 통해 주기적으로, 서버의 expire 된 티켓을 제거합니다. (매일 00시) 그런데, expire 모드를 false 로 주게되면, 모든 티켓이 expire 되지 않음으로 궁극적으론 alf 서버의 메모리에 사용하지도 않는 티켓이 계속 쌓여 언젠가는 돌아가시게 됩니다. ㅎㅎ 즉, 짧진 않더라도 expire 를 주는게 좋습니다. 


expire 를 설정하게 되면, alfresco 는 아래처럼, validation 할 때마다 expire-date 를 갱신한 신규 티켓을 지속적으로 발행합니다. 이 때문에 기존에 저장되어 있던 expire 되는 티켓들을 정리할 필요가 반드시 있게 되죠. 

Ticket getNewEntry() {

switch (expires) {

case AFTER_FIXED_TIME:

if ((expiryDate != null) && (expiryDate.compareTo(new Date()) < 0)) {

return null;

} else {

return this;

}


case AFTER_INACTIVITY:

Date now = new Date();

if ((expiryDate != null) && (expiryDate.compareTo(now) < 0)) {

return null;

} else {

return new Ticket(expires, Duration.add(now, validDuration), userName, validDuration, ticketId);

}


case DO_NOT_EXPIRE:

default:

return this;

}

}


%별첨4

common 에디션은 기본 캐시를 사용하기 때문에, 이와 같이 ticketCache 의 maximum 이 영향을 줍니다. 그러나, ehcache 등을 사용하게 된다면, 설정에 따라 기본 천개가 넘어가면 로컬 파일 스토리지에 이관하여 보관하게 됩니다. 따라서, 확장캐시를 쓰면 이런 증상이 발생하지 않을수도 있습니다만, 사용자수에 따라 적정량을 조정하는게 성능에 좋겠습니다. 


캐시의 maximum 수치는 아래처럼 global.properties 에 설정해주면 됩니다. 

전, 만개로 늘려놨습니다. :-)

cache.ticketsCache.maxItems=10000