Spring Security 多个用户表

Spring Security 多个用户表

柳性安 3,570 2022-10-01

Spring Security 多用户表

多个userDetailService提供用户信息,管理员 + 用户表

场景

web后端登录(user表)与app用户登录(member表),字段相差大,不想合并成一张表。不管是在userDetailService里轮查,还是重写ProviderManager , 都存在一个问题, 那就是两张表用户名不能重复(包括 邮箱、电话、第三方平台的openId)

目的

使用不同的userDetailService来处理登录信息

方案

密码模式与客户端模式共存== 请求oauth/token时增加range参数,来确定使用哪个userDetailService

定义CustomUserDetailService

public interface CustomUserDetailService extends UserDetailsService {

    Boolean supports(String range);// 判断依据
}

实现user表与member表的userDetailService

@Slf4j
@Service("adminUserDetailService")
public class AdminUserDetailService implements CustomUserDetailService {

    private final String RANGE = "a";

        @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        // 处理 byLoginName byPhoneCode byOpenId byEmail 
         return new org.springframework.security.core.userdetails.User(略);
    }

    @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 {
        // 处理 byLoginName byPhoneCode byOpenId byEmail 
        return new org.springframework.security.core.userdetails.User(略);
    }

    @Override
    public Boolean supports(String range){
        return range.equals(RANGE);
    }
}

根据 org.springframework.security.authentication.dao.DaoAuthenticationProvider 重写AuthenticationProvider类,关键点在于:重写retrieveUser()方法,并提供多个userDetailService来处理不同的range请求 其他的方法照搬,只贴关键改动部分代码

public class AuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
     ··············
    private List<CustomUserDetailService> userDetailsServices;

    protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        this.prepareTimingAttackProtection();

        try {
            Map detail = (Map) authentication.getDetails();
            UserDetails loadedUser = null;
            for (CustomUserDetailService userDetailsService : this.getUserDetailsServices()){
                if(userDetailsService.supports(detail.get("range").toString())){
                    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);
    }