关键词:多userDetailService AuthenticationProvider
场景: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);
}
postman 测试链接:http://localhost:8000/oauth/token?scope=read&grant_type=password&range=a
结论: Spring Security 在整个授权过程中,运用了13个(忘记多少个了)filter,这些filter 是在一个filter chain里存放并加载的,比较麻烦的是,这些filter没有办法替代以及覆盖,所以为了达到这个场景需求,来了一次曲线救国。我个人非常抵制Spring系列,原因有几点,对新手不友好,很多新手脱离了Spring 体系(不看实现、不看源码的那种人,大多因为看不懂),什么都不会,容易把人干废。用Spring,确实是能省很多时间,但当业务场景不被支持的时候,找方法的过程,也许比你省的时间还要多。到底用不用Spring? 个人建议,用的时候,一定要理解实现的原理,并且学习Spring的设计模式,2样都达到之后,自己撸,目的就一个,可控。
转载请注明出处