Spring Security实现多用户系统登录
Spring Security在认证过程中,是通过userDetailService的loadUserByUsername方法根据用户名来查找用户信息的,这样如果想在系统中有多个用户登录系统默认是不能实现的。所以想实现多用户系统登录的问题,我们就得有多个userDetailService的实现,在认证的时候根据策略选择相应的userDetailService。接下来就介绍怎么一步步完成多用户系统的认证。
首先,我们定义一个CustomUserDetailService将userDetailService包装一下,提供一个supports方法作为选择策略。
public interface CustomUserDetailService extends UserDetailsService { Boolean supports(String range);// 判断依据 }
两个系统的表定为user表与member表,接下来我们实现对应的userDetailService,并指定serveice的名称
@Slf4j @Service("adminUserDetailService") public class AdminUserDetailService implements CustomUserDetailService { private final String RANGE = "a"; @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { // ...... } @Override public Boolean supports(String range){ return RANGE.equals(range); } }
@Service("memberUserDetailService") @Slf4j public class MemberUserDetailService implements CustomUserDetailService { private final String RANGE = "m"; @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { // ...... } @Override public Boolean supports(String range){ return RANGE.equals(range); } }
接下来我们需要重写AuthenticationProvider类,让它能提供多个userDetailService,并根据策略选择相应的userDetailService进行处理。这里我们复制org.springframework.security.authentication.dao.DaoAuthenticationProvider这个实现类作为我们的实现类,然后定义一个getUserDetailsServices()方法接收多个userDetailService,然后重写retrieveUser()方法来选择应使用的userDetailService。
public class AuthenticationProvider extends AbstractUserDetailsAuthenticationProvider { ·············· private List<CustomUserDetailService> userDetailsServices; protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { this.prepareTimingAttackProtection(); try { String detail = (String) authentication.getDetails(); UserDetails loadedUser = null; for (CustomUserDetailService userDetailsService : this.getUserDetailsServices()){ if(userDetailsService.supports(detail)){ loadedUser = userDetailsService.loadUserByUsername(username); break; } } if (loadedUser == null) { throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation"); } else { return loadedUser; } } catch (UsernameNotFoundException var4) { this.mitigateAgainstTimingAttack(authentication); throw var4; } catch (InternalAuthenticationServiceException var5) { throw var5; } catch (Exception var6) { throw new InternalAuthenticationServiceException(var6.getMessage(), var6); } } public List<CustomUserDetailService> getUserDetailsServices() { return userDetailsServices; } public void setUserDetailsServices(List<CustomUserDetailService> userDetailsServices) { this.userDetailsServices = userDetailsServices; } ·········· }
接下来配置WebSecurityConfigurerAdapter
@Qualifier("adminUserDetailService") @Autowired private CustomUserDetailService adminUserDetailService; @Qualifier("memberUserDetailService") @Autowired private CustomUserDetailService memberUserDetailService; @Override protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { AuthenticationProvider authenticationProvider = new AuthenticationProvider(); authenticationProvider.setPasswordEncoder(passwordEncoder()); List<CustomUserDetailService> userDetailServices = new ArrayList<>(); userDetailServices.add(memberUserDetailService); userDetailServices.add(adminUserDetailService); authenticationProvider.setUserDetailsServices(userDetailServices); authenticationManagerBuilder.authenticationProvider(authenticationProvider); }
最后就是处理登录了,前面我们重写AuthenticationProvider中的retrieveUser()方法时是通过UsernamePasswordAuthenticationToken中的details来进行判断使用哪个userDetailService的,所以在创建UsernamePasswordAuthenticationToken后需要把"range"设置到details中。details是父类中的一个Object类型的成员变量。在登录服务中,创建authenticationToken后,我们设置好details,这里我是在用户实体中新增了一个flag参数,不同的系统传入不同值,当然,如果各个系统调用的登录接口不同,我们也可以不用新增参数直接在这里写死。
public abstract class AbstractAuthenticationToken implements Authentication, CredentialsContainer { private final Collection<GrantedAuthority> authorities; private Object details; ...... }
public String login(User user) { String username = user.getUsername(); String password = user.getPassword(); // 用户验证 Authentication authentication = null; try { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); authenticationToken.setDetails(user.getFlag()); AuthenticationContextHolder.setContext(authenticationToken); // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername authentication = authenticationManager.authenticate(authenticationToken); } catch (Exception e) { }
这样就完成了多用户系统登录的实现。
引用文章:http://dexian.net.cn/archives/springsecurity-duo-ge-yong-hu-biao