Java 如何在Spring security中允许多个登录?
我希望为我的spring security应用程序启用多个登录,例如,如果我有两个电子邮件地址,我希望允许用户使用多个电子邮件地址登录,并且用户可以从一个电子邮件帐户切换到另一个,正如所示。我怎么能用Spring security做到这一点 Spring安全性中似乎只有一个Java 如何在Spring security中允许多个登录?,java,spring,spring-mvc,spring-security,Java,Spring,Spring Mvc,Spring Security,我希望为我的spring security应用程序启用多个登录,例如,如果我有两个电子邮件地址,我希望允许用户使用多个电子邮件地址登录,并且用户可以从一个电子邮件帐户切换到另一个,正如所示。我怎么能用Spring security做到这一点 Spring安全性中似乎只有一个主体,而不是主体列表。我能做到吗 SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 提前谢谢。希望您能尽快回复。在某种程度上Spri
主体
,而不是主体列表。我能做到吗
SecurityContextHolder.getContext().getAuthentication().getPrincipal();
提前谢谢。希望您能尽快回复。在某种程度上
Spring-Security
支持用户切换。它更像Linux下的su
。
不过,您可以重用中的一些代码来创建自己的用户开关
首先,您需要创建
- 自定义Spring安全性,其中包含一个允许交换机访问的用户名列表
- 自定义,用于填充自定义用户详细信息
- 基于Spring的SwitchUserFilter的自定义UserSwitchFilter
UserDetails
和UserDetails服务
只是示例,可能与您自己的实现不同。其思想是在UserDetails中保存一个用户名列表,以便在CustomUserSwitchFilter中进行后续处理
CustomUserDetails:
public class CustomUserDetails extends User {
private final Set<String> linkedAccounts;
public CustomUserDetails(String username, String password, Set<String> linkedAccounts, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
this.linkedAccounts = linkedAccounts;
}
public Set<String> getLinkedAccounts() {
return linkedAccounts;
}
}
与Spring Security UserSwitchFilter的主要区别:
- 添加的方法
检查是否允许从当前经过身份验证的用户切换到该特定用户checkSwitchAllowed
- 切换基于查询参数,而不是url,以获得更好的用户体验(请参见
)。因此requiresSwitchUser
和无需切换用户URL
targetUrl
- 自定义UserSwitchFilter没有
的概念。因此无需退出exitUserUrl
不修改用户权限createSwitchUserToken
public class CustomSwitchUserFilter extends GenericFilterBean implements ApplicationEventPublisherAware, MessageSourceAware {
public static final String SPRING_SECURITY_SWITCH_USERNAME_KEY = "j_switch_username";
private ApplicationEventPublisher eventPublisher;
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private String switchFailureUrl;
private String usernameParameter = SPRING_SECURITY_SWITCH_USERNAME_KEY;
private UserDetailsService userDetailsService;
private UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker();
private AuthenticationFailureHandler failureHandler;
@Override
public void afterPropertiesSet() {
Assert.notNull(userDetailsService, "userDetailsService must be specified");
if (failureHandler == null) {
failureHandler = switchFailureUrl == null ? new SimpleUrlAuthenticationFailureHandler() :
new SimpleUrlAuthenticationFailureHandler(switchFailureUrl);
} else {
Assert.isNull(switchFailureUrl, "You cannot set both a switchFailureUrl and a failureHandler");
}
}
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);
} catch (AuthenticationException e) {
logger.debug("Switch User failed", e);
failureHandler.onAuthenticationFailure(request, response, e);
return;
}
}
chain.doFilter(request, response);
}
protected Authentication attemptSwitchUser(HttpServletRequest request) throws AuthenticationException {
UsernamePasswordAuthenticationToken targetUserRequest;
String username = request.getParameter(usernameParameter);
if (username == null) {
username = "";
}
if (logger.isDebugEnabled()) {
logger.debug("Attempt to switch to user [" + username + "]");
}
UserDetails targetUser = userDetailsService.loadUserByUsername(username);
userDetailsChecker.check(targetUser);
checkSwitchAllowed(targetUser);
// OK, create the switch user token
targetUserRequest = createSwitchUserToken(request, targetUser);
if (logger.isDebugEnabled()) {
logger.debug("Switch User Token [" + targetUserRequest + "]");
}
// publish event
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new AuthenticationSwitchUserEvent(SecurityContextHolder.getContext().getAuthentication(), targetUser));
}
return targetUserRequest;
}
private void checkSwitchAllowed(UserDetails targetUser) {
CustomUserDetails details = (CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String targetUsername = targetUser.getUsername();
//target username has to be in linked accounts otherwise this is an unauthorized switch
if(!details.getLinkedAccounts().contains(targetUsername)) {
throw new InsufficientAuthenticationException("user switch not allowed");
}
}
private UsernamePasswordAuthenticationToken createSwitchUserToken(HttpServletRequest request, UserDetails targetUser) {
UsernamePasswordAuthenticationToken targetUserRequest;
// get the original authorities
Collection<? extends GrantedAuthority> orig = targetUser.getAuthorities();
// add the new switch user authority
List<GrantedAuthority> newAuths = new ArrayList<GrantedAuthority>(orig);
// create the new authentication token
targetUserRequest = new UsernamePasswordAuthenticationToken(targetUser, targetUser.getPassword(), newAuths);
// set details
targetUserRequest.setDetails(authenticationDetailsSource.buildDetails(request));
return targetUserRequest;
}
protected boolean requiresSwitchUser(HttpServletRequest request) {
Map<String, String[]> parameterMap = request.getParameterMap();
return parameterMap.containsKey(usernameParameter);
}
public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher)
throws BeansException {
this.eventPublisher = eventPublisher;
}
public void setAuthenticationDetailsSource(AuthenticationDetailsSource<HttpServletRequest,?> authenticationDetailsSource) {
Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource required");
this.authenticationDetailsSource = authenticationDetailsSource;
}
public void setMessageSource(MessageSource messageSource) {
Assert.notNull(messageSource, "messageSource cannot be null");
this.messages = new MessageSourceAccessor(messageSource);
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
public void setSwitchFailureUrl(String switchFailureUrl) {
Assert.isTrue(StringUtils.hasText(usernameParameter) && UrlUtils.isValidRedirectUrl(switchFailureUrl),
"switchFailureUrl cannot be empty and must be a valid redirect URL");
this.switchFailureUrl = switchFailureUrl;
}
public void setFailureHandler(AuthenticationFailureHandler failureHandler) {
Assert.notNull(failureHandler, "failureHandler cannot be null");
this.failureHandler = failureHandler;
}
public void setUserDetailsChecker(UserDetailsChecker userDetailsChecker) {
this.userDetailsChecker = userDetailsChecker;
}
public void setUsernameParameter(String usernameParameter) {
this.usernameParameter = usernameParameter;
}
}
你可以找到一个有效的例子
public class CustomSwitchUserFilter extends GenericFilterBean implements ApplicationEventPublisherAware, MessageSourceAware {
public static final String SPRING_SECURITY_SWITCH_USERNAME_KEY = "j_switch_username";
private ApplicationEventPublisher eventPublisher;
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private String switchFailureUrl;
private String usernameParameter = SPRING_SECURITY_SWITCH_USERNAME_KEY;
private UserDetailsService userDetailsService;
private UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker();
private AuthenticationFailureHandler failureHandler;
@Override
public void afterPropertiesSet() {
Assert.notNull(userDetailsService, "userDetailsService must be specified");
if (failureHandler == null) {
failureHandler = switchFailureUrl == null ? new SimpleUrlAuthenticationFailureHandler() :
new SimpleUrlAuthenticationFailureHandler(switchFailureUrl);
} else {
Assert.isNull(switchFailureUrl, "You cannot set both a switchFailureUrl and a failureHandler");
}
}
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);
} catch (AuthenticationException e) {
logger.debug("Switch User failed", e);
failureHandler.onAuthenticationFailure(request, response, e);
return;
}
}
chain.doFilter(request, response);
}
protected Authentication attemptSwitchUser(HttpServletRequest request) throws AuthenticationException {
UsernamePasswordAuthenticationToken targetUserRequest;
String username = request.getParameter(usernameParameter);
if (username == null) {
username = "";
}
if (logger.isDebugEnabled()) {
logger.debug("Attempt to switch to user [" + username + "]");
}
UserDetails targetUser = userDetailsService.loadUserByUsername(username);
userDetailsChecker.check(targetUser);
checkSwitchAllowed(targetUser);
// OK, create the switch user token
targetUserRequest = createSwitchUserToken(request, targetUser);
if (logger.isDebugEnabled()) {
logger.debug("Switch User Token [" + targetUserRequest + "]");
}
// publish event
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new AuthenticationSwitchUserEvent(SecurityContextHolder.getContext().getAuthentication(), targetUser));
}
return targetUserRequest;
}
private void checkSwitchAllowed(UserDetails targetUser) {
CustomUserDetails details = (CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String targetUsername = targetUser.getUsername();
//target username has to be in linked accounts otherwise this is an unauthorized switch
if(!details.getLinkedAccounts().contains(targetUsername)) {
throw new InsufficientAuthenticationException("user switch not allowed");
}
}
private UsernamePasswordAuthenticationToken createSwitchUserToken(HttpServletRequest request, UserDetails targetUser) {
UsernamePasswordAuthenticationToken targetUserRequest;
// get the original authorities
Collection<? extends GrantedAuthority> orig = targetUser.getAuthorities();
// add the new switch user authority
List<GrantedAuthority> newAuths = new ArrayList<GrantedAuthority>(orig);
// create the new authentication token
targetUserRequest = new UsernamePasswordAuthenticationToken(targetUser, targetUser.getPassword(), newAuths);
// set details
targetUserRequest.setDetails(authenticationDetailsSource.buildDetails(request));
return targetUserRequest;
}
protected boolean requiresSwitchUser(HttpServletRequest request) {
Map<String, String[]> parameterMap = request.getParameterMap();
return parameterMap.containsKey(usernameParameter);
}
public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher)
throws BeansException {
this.eventPublisher = eventPublisher;
}
public void setAuthenticationDetailsSource(AuthenticationDetailsSource<HttpServletRequest,?> authenticationDetailsSource) {
Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource required");
this.authenticationDetailsSource = authenticationDetailsSource;
}
public void setMessageSource(MessageSource messageSource) {
Assert.notNull(messageSource, "messageSource cannot be null");
this.messages = new MessageSourceAccessor(messageSource);
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
public void setSwitchFailureUrl(String switchFailureUrl) {
Assert.isTrue(StringUtils.hasText(usernameParameter) && UrlUtils.isValidRedirectUrl(switchFailureUrl),
"switchFailureUrl cannot be empty and must be a valid redirect URL");
this.switchFailureUrl = switchFailureUrl;
}
public void setFailureHandler(AuthenticationFailureHandler failureHandler) {
Assert.notNull(failureHandler, "failureHandler cannot be null");
this.failureHandler = failureHandler;
}
public void setUserDetailsChecker(UserDetailsChecker userDetailsChecker) {
this.userDetailsChecker = userDetailsChecker;
}
public void setUsernameParameter(String usernameParameter) {
this.usernameParameter = usernameParameter;
}
}
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider user-service-ref="userDetailsService"/>
</security:authentication-manager>
<security:http use-expressions="true">
<security:intercept-url pattern="/**" access="isFullyAuthenticated()" />
<security:form-login login-page="/login.do" />
<security:logout logout-success-url="/login.do" />
<security:custom-filter ref="switchUserProcessingFilter" after="FILTER_SECURITY_INTERCEPTOR" />
</security:http>
<bean id="switchUserProcessingFilter" class="security.CustomSwitchUserFilter">
<property name="userDetailsService" ref="userDetailsService" />
</bean>