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);
}