restful实践

Spring Security 多用户表

2021-09-07  本文已影响0人  指尖行动

关键词:多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

image.png

结论: Spring Security 在整个授权过程中,运用了13个(忘记多少个了)filter,这些filter 是在一个filter chain里存放并加载的,比较麻烦的是,这些filter没有办法替代以及覆盖,所以为了达到这个场景需求,来了一次曲线救国。我个人非常抵制Spring系列,原因有几点,对新手不友好,很多新手脱离了Spring 体系(不看实现、不看源码的那种人,大多因为看不懂),什么都不会,容易把人干废。用Spring,确实是能省很多时间,但当业务场景不被支持的时候,找方法的过程,也许比你省的时间还要多。到底用不用Spring? 个人建议,用的时候,一定要理解实现的原理,并且学习Spring的设计模式,2样都达到之后,自己撸,目的就一个,可控。

转载请注明出处

上一篇下一篇

猜你喜欢

热点阅读