Here's what I've been able to piece together. I think the main difference between the test scenario and a web app setup is that the Subject that is bound to the tread and saved in the session is recreated between requests - in the filter chain (see AbstractShiroFilter, line 359).
During the creation process the DefaultSecurityManager.createSubject calls it's save method (at line 350).
That, in turn, calls DefaultSubjectDAO.mergePrincipals (at line 163): save -> saveToSession -> mergePrincipals.
Here's where things get interesting. Consider Jochen's scenario, right after runAs is executed:
1. The session now contains the following attributes:
- DefaultSubjectContext.PRINCIPALS_SESSION_KEY = user1
- DelegatingSubject.RUN_AS_PRINCIPALS_SESSION_KEY = [user2];
2. When the next request is fired, we enter the filter chain and get to mergePrincipals.
At this point
PrincipalCollection currentPrincipals = subject.getPrincipals(); (DefaultSubjectDAO, line 177)
will return "user2" as it is the top item in the runAs stack.
PrincipalCollection existingPrincipals = (PrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY); (line 187)
will return "user1" as it is saved in the session.
And here the initial principal is overwritten (lines 196 to 198):
// currentPrincipals == user2, existingPrincipals = user1
Whew, hope I got that right. I've attached a diff with changes that solved this issue for me. The changes are rather minor - initial prinicpal is saved to session during login and restored when the runAs stack is emptied.
P.S. All line numbers and the diff file are taken from 1.2.1 relase tag (https://svn.apache.org/repos/asf/shiro/tags/shiro-root-1.2.1)