您的位置:首页 > 其它

SwitchUserFilter源码解析

2017-12-20 00:00 253 查看

本文就来解析一下SwitchUserFilter的源码

SwitchUserFilter

spring-security-web-4.2.3.RELEASE-sources.jar!/org/springframework/security/web/authentication/switchuser/SwitchUserFilter.java

public class SwitchUserFilter extends GenericFilterBean
implements ApplicationEventPublisherAware, MessageSourceAware {
//......
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;

// check for switch or exit request
if (requiresSwitchUser(request)) {
// if set, attempt switch and store original
try {
Authentication targetUser = attemptSwitchUser(request);

// update the current context to the new target user
SecurityContextHolder.getContext().setAuthentication(targetUser);

// redirect to target url
this.successHandler.onAuthenticationSuccess(request, response,
targetUser);
}
catch (AuthenticationException e) {
this.logger.debug("Switch User failed", e);
this.failureHandler.onAuthenticationFailure(request, response, e);
}

return;
}
else if (requiresExitUser(request)) {
// get the original authentication object (if exists)
Authentication originalUser = attemptExitUser(request);

// update the current context back to the original user
SecurityContextHolder.getContext().setAuthentication(originalUser);

// redirect to target url
this.successHandler.onAuthenticationSuccess(request, response, originalUser);

return;
}

chain.doFilter(request, response);
}
}


首先会判断url是不是/login/impersonate或者/logout/impersonate,如果不是则不会进入这个filter

attemptSwitchUser

/**
* Attempt to switch to another user. If the user does not exist or is not active,
* return null.
*
* @return The new <code>Authentication</code> request if successfully switched to
* another user, <code>null</code> otherwise.
*
* @throws UsernameNotFoundException If the target user is not found.
* @throws LockedException if the account is locked.
* @throws DisabledException If the target user is disabled.
* @throws AccountExpiredException If the target user account is expired.
* @throws CredentialsExpiredException If the target user credentials are expired.
*/
protected Authentication attemptSwitchUser(HttpServletRequest request)
throws AuthenticationException {
UsernamePasswordAuthenticationToken targetUserRequest;

String username = request.getParameter(this.usernameParameter);

if (username == null) {
username = "";
}

if (this.logger.isDebugEnabled()) {
this.logger.debug("Attempt to switch to user [" + username + "]");
}

UserDetails targetUser = this.userDetailsService.loadUserByUsername(username);
this.userDetailsChecker.check(targetUser);

// OK, create the switch user token
targetUserRequest = createSwitchUserToken(request, targetUser);

if (this.logger.isDebugEnabled()) {
this.logger.debug("Switch User Token [" + targetUserRequest + "]");
}

// publish event
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new AuthenticationSwitchUserEvent(
SecurityContextHolder.getContext().getAuthentication(), targetUser));
}

return targetUserRequest;
}


从url读取username参数,然后调用userDetailsService.loadUserByUsername(username)获取目标用户信息,然后判断目标账户是否正常,正常则切换,不正常则抛异常

AccountStatusUserDetailsChecker

spring-security-core-4.2.3.RELEASE-sources.jar!/org/springframework/security/authentication/Acco
7fe0
untStatusUserDetailsChecker.java

public class AccountStatusUserDetailsChecker implements UserDetailsChecker {

protected final MessageSourceAccessor messages = SpringSecurityMessageSource
.getAccessor();

public void check(UserDetails user) {
if (!user.isAccountNonLocked()) {
throw new LockedException(messages.getMessage(
"AccountStatusUserDetailsChecker.locked", "User account is locked"));
}

if (!user.isEnabled()) {
throw new DisabledException(messages.getMessage(
"AccountStatusUserDetailsChecker.disabled", "User is disabled"));
}

if (!user.isAccountNonExpired()) {
throw new AccountExpiredException(
messages.getMessage("AccountStatusUserDetailsChecker.expired",
"User account has expired"));
}

if (!user.isCredentialsNonExpired()) {
throw new CredentialsExpiredException(messages.getMessage(
"AccountStatusUserDetailsChecker.credentialsExpired",
"User credentials have expired"));
}
}
}

createSwitchUserToken

/**
* Create a switch user token that contains an additional <tt>GrantedAuthority</tt>
* that contains the original <code>Authentication</code> object.
*
* @param request The http servlet request.
* @param targetUser The target user
*
* @return The authentication token
*
* @see SwitchUserGrantedAuthority
*/
private UsernamePasswordAuthenticationToken createSwitchUserToken(
HttpServletRequest request, UserDetails targetUser) {

UsernamePasswordAuthenticationToken targetUserRequest;

// grant an additional authority that contains the original Authentication object
// which will be used to 'exit' from the current switched user.

Authentication currentAuth;

try {
// SEC-1763. Check first if we are already switched.
currentAuth = attemptExitUser(request);
}
catch (AuthenticationCredentialsNotFoundException e) {
currentAuth = SecurityContextHolder.getContext().getAuthentication();
}

GrantedAuthority switchAuthority = new SwitchUserGrantedAuthority(
this.switchAuthorityRole, currentAuth);

// get the original authorities
Collection<? extends GrantedAuthority> orig = targetUser.getAuthorities();

// Allow subclasses to change the authorities to be granted
if (this.switchUserAuthorityChanger != null) {
orig = this.switchUserAuthorityChanger.modifyGrantedAuthorities(targetUser,
currentAuth, orig);
}

// add the new switch user authority
List<GrantedAuthority> newAuths = new ArrayList<GrantedAuthority>(orig);
newAuths.add(switchAuthority);

// create the new authentication token
targetUserRequest = new UsernamePasswordAuthenticationToken(targetUser,
targetUser.getPassword(), newAuths);

// set details
targetUserRequest
.setDetails(this.authenticationDetailsSource.buildDetails(request));

return targetUserRequest;
}


找出目标账号,添加SwitchUserGrantedAuthority,然后创建UsernamePasswordAuthenticationToken

attemptExitUser

/**
* Attempt to exit from an already switched user.
*
* @param request The http servlet request
*
* @return The original <code>Authentication</code> object or <code>null</code>
* otherwise.
*
* @throws AuthenticationCredentialsNotFoundException If no
* <code>Authentication</code> associated with this request.
*/
protected Authentication attemptExitUser(HttpServletRequest request)
throws AuthenticationCredentialsNotFoundException {
// need to check to see if the current user has a SwitchUserGrantedAuthority
Authentication current = SecurityContextHolder.getContext().getAuthentication();

if (null == current) {
throw new AuthenticationCredentialsNotFoundException(
this.messages.getMessage("SwitchUserFilter.noCurrentUser",
"No current user associated with this request"));
}

// check to see if the current user did actual switch to another user
// if so, get the original source user so we can switch back
Authentication original = getSourceAuthentication(current);

if (original == null) {
this.logger.debug("Could not find original user Authentication object!");
throw new AuthenticationCredentialsNotFoundException(
this.messages.getMessage("SwitchUserFilter.noOriginalAuthentication",
"Could not find original Authentication object"));
}

// get the source user details
UserDetails originalUser = null;
Object obj = original.getPrincipal();

if ((obj != null) && obj instanceof UserDetails) {
originalUser = (UserDetails) obj;
}

// publish event
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(
new AuthenticationSwitchUserEvent(current, originalUser));
}

return original;
}


这个方法无论是登录切换,还是注销切换都需要调用。登录切换会调动这个方法判断是否已经切换过了.

getSourceAuthentication

/**
* Find the original <code>Authentication</code> object from the current user's
* granted authorities. A successfully switched user should have a
* <code>SwitchUserGrantedAuthority</code> that contains the original source user
* <code>Authentication</code> object.
*
* @param current The current <code>Authentication</code> object
*
* @return The source user <code>Authentication</code> object or <code>null</code>
* otherwise.
*/
private Authentication getSourceAuthentication(Authentication current) {
Authentication original = null;

// iterate over granted authorities and find the 'switch user' authority
Collection<? extends GrantedAuthority> authorities = current.getAuthorities();

for (GrantedAuthority auth : authorities) {
// check for switch user type of authority
if (auth instanceof SwitchUserGrantedAuthority) {
original = ((SwitchUserGrantedAuthority) auth).getSource();
this.logger.debug("Found original switch user granted authority ["
+ original + "]");
}
}

return original;
}


这个方法会检查,当前账号是否具有SwitchUserGrantedAuthority,如果有则找出切换前的账号。
对于登录切换,通过这个方法判断是否已经切换过(
如果你调用这个方法自己切换自己,则这里会抛出AuthenticationCredentialsNotFoundException异常,createSwitchUserToken会捕获这个异常,然后将登录态切换成当前的登录态;不过比没切换之前多了个SwitchUserGrantedAuthority
)。
而对于注销切换,则通过这个找出切换前的身份,如果找不到则抛出AuthenticationCredentialsNotFoundException,但是外层没有捕获

if (requiresExitUser(request)) {
// get the original authentication object (if exists)
Authentication originalUser = attemptExitUser(request);

// update the current context back to the original user
SecurityContextHolder.getContext().setAuthentication(originalUser);

// redirect to target url
this.successHandler.onAuthenticationSuccess(request, response, originalUser);

return;
}


因而会返回错误页面

SwitchUserGrantedAuthority

spring-security-web-4.2.3.RELEASE-sources.jar!/org/springframework/security/web/authentication/switchuser/SwitchUserGrantedAuthority.java

/**
* Custom {@code GrantedAuthority} used by
* {@link org.springframework.security.web.authentication.switchuser.SwitchUserFilter}
* <p>
* Stores the {@code Authentication} object of the original user to be used later when
* 'exiting' from a user switch.
*
* @author Mark St.Godard
*
* @see org.springframework.security.web.authentication.switchuser.SwitchUserFilter
*/
public final class SwitchUserGrantedAuthority implements GrantedAuthority {

private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

// ~ Instance fields
// ================================================================================================
private final String role;
private final Authentication source;

// ~ Constructors
// ===================================================================================================

public SwitchUserGrantedAuthority(String role, Authentication source) {
this.role = role;
this.source = source;
}

// ~ Methods
// ========================================================================================================

/**
* Returns the original user associated with a successful user switch.
*
* @return The original <code>Authentication</code> object of the switched user.
*/
public Authentication getSource() {
return source;
}

public String getAuthority() {
return role;
}

public int hashCode() {
return 31 ^ source.hashCode() ^ role.hashCode();
}

public boolean equals(Object obj) {
if (this == obj) {
return true;
}

if (obj instanceof SwitchUserGrantedAuthority) {
SwitchUserGrantedAuthority swa = (SwitchUserGrantedAuthority) obj;
return this.role.equals(swa.role) && this.source.equals(swa.source);
}

return false;
}

public String toString() {
return "Switch User Authority [" + role + "," + source + "]";
}
}


这个保存了账户切换的关联关系

小结

切换权限判断
这个通过security config里头配置,在FilterSecurityInterceptor里头进行鉴权

账号关联
通过SwitchUserGrantedAuthority来保存切换之前的账号信息

状态切换(
登录切换/注销切换
)
获取目标用户的UsernamePasswordAuthenticationToken,之后调用

// update the current context to the new target user
SecurityContextHolder.getContext().setAuthentication(targetUser);

// redirect to target url
this.successHandler.onAuthenticationSuccess(request, response,
targetUser);


这两个方法一个再上下文切换登录态,一个是调用登录成功之后的处理。这里没有改变sessionId。但是如果是正常登陆的话,会切换sessionId的。

登录切换是通过userDetailsService.loadUserByUsername(username)获取目标用户信息,然后创建UsernamePasswordAuthenticationToken;

注销切换则是通过SwitchUserGrantedAuthority获取原账号的UsernamePasswordAuthenticationToken
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  SecurityFilter
相关文章推荐