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

posted @ 2023-06-29 17:54  hello龙兄  阅读(1676)  评论(0编辑  收藏  举报