如何在spring security中为来自两个不同表的不同用户配置身份验证?

Posted

技术标签:

【中文标题】如何在spring security中为来自两个不同表的不同用户配置身份验证?【英文标题】:How to configure authentication in spring security for different users from two different tables? 【发布时间】:2020-07-08 15:57:24 【问题描述】:

在我的 Spring Boot 应用程序中,我有 2 种不同类型的用户 - 用户和供应商,它们存储在我的 SQL DB 的不同表中。

我只允许访问返回 JWT 的 /user/login 和 /vendor/login。

我无法理解如何配置 spring security 在有人请求 /user/login 时仅检查 USERS 表,而在供应商请求 /vendor/login 时仅检查 VENDORS 表。这可能吗?如果没有,任何人都可以建议我如何配置 Spring Security 以对来自不同表的用户进行身份验证?

这是我当前的配置,仅对用户进行身份验证 -

@Configuration
@EnableWebSecurity
public class SecurityConfigurer extends WebSecurityConfigurerAdapter 

    @Autowired
    private UserService myUserDetailsService;   // this fetches data from the USERS table

    @Autowired
    private JwtRequestFilter jwtRequestFilter;

    // *** How do I configure this to check both VENDORS OR USERS table? ***
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception 
        auth.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncoder());
    

    @Override
    protected void configure(HttpSecurity http) throws Exception 
        http.csrf().disable().authorizeRequests()
            .antMatchers("/user/auth/login").permitAll()
            .antMatchers("/vendor/auth/login").permitAll()
            .anyRequest().authenticated()
            .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception 
        return super.authenticationManagerBean();
    

    @Bean
    public BCryptPasswordEncoder passwordEncoder() 
        return new BCryptPasswordEncoder();
    

我为用户和供应商实现了 UserDetailsS​​ervice。这是 userService 的实现 -

@Service
public class UserService implements UserDetailsService 

    @Autowired
    private UserRepository repository;

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    public UserService() 
    

    public UserService(UserRepository repository) 
        this.repository = repository;
    

    public Users findOne(String id) 
        Optional<Users> user = repository.findById(id);
        return user.orElse(null);
    

    public List<Users> findAll() 
        List<Users> users = new ArrayList<>();
        repository.findAll().forEach(users::add);
        return users;
    

    public Users insert(Users user) throws UnknownError 
        // somecode here
    

    public Users update(String id, Users user) 
       // some code here
    

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException 
        final Users user = findByEmail(username);
        if (user == null) 
            throw new UsernameNotFoundException("No user Found");
        

        return new User(user.getEmail(), user.getPassword(), new ArrayList<>());
    

这里是UserController(VendorController和这个类似)-

@RestController
@RequestMapping(path = "/user")
public class AuthController 

    @Autowired
    UserService service;

    @Autowired
    AuthenticationManager authenticationManager;

    @PostMapping(path = "/login")
    public ResponseEntity<?> login(@RequestBody AuthenticationRequest form) throws Exception 
        try 
            authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(form.getEmail(), form.getPassword()));
         catch (Exception e) 
            throw new Exception("Incorrect Credentials");
        

        final UserDetails user = service.loadUserByUsername(form.getEmail());
        Users returnedUser = service.insert(user);
        ResponseStructure response = new ResponseStructure(true, returnedUser);
        return ResponseEntity.ok(response);
    

【问题讨论】:

【参考方案1】:

您可以让 UserService 查找两个存储库

@Service
public class UserService implements UserDetailsService 

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private VendorRepository vendorRepository;

   // all the other stuff

    private Users findByUsername(String username)
        return userRepository.findByUserName(username);
    

    private Vendors findByVendorName(String vendorName)
        return VendorRepository.findByVendorName(vendorName); // given you have such method declared in the Spring Data repository
    

    @Override
    public UserDetails loadUserByName(String name) throws UsernameNotFoundException 
        Users user = findByUsername(name);
        if (user != null) 
             return new User(user.getEmail(), user.getPassword(), new ArrayList<>());
        

        Vendors vendor = findByVendorName(name);

        if (vendor != null) 
             return new Vendor(vendor.getEmail(), vendor.getPassword(), new ArrayList<>());
        
        else
               throw new UsernameNotFoundException("No user Found");
        

    

【讨论】:

谢谢,但这不是一个理想的解决方案,因为我们首先要遍历 users 表,然后是 vendor 表,其次,如果供应商或用户有相同的名称和密码,那可能惹麻烦。 注意到您的请求来自不同的登录网址。也许你可以添加一个 userType 到 AuthenticationRequest 并根据用户类型选择正确的存储库。 您可以将 userType 属性添加为登录表单上的隐藏输入以使其简单 或者您可以将 HttpServletRequest 作为参数添加到登录方法并使用 getRequestURI() 来了解请求的位置来自

以上是关于如何在spring security中为来自两个不同表的不同用户配置身份验证?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Spring Security 中为 x 框架选项提供请求匹配器?

如何在jsp中为spring security auth异常显示自定义错误消息

Spring:HttpSession在集群Tomcat故障转移中为SPRING_SECURITY_CONTEXT返回了空对象

如何在 Spring Security 中为所有请求添加 jwt 身份验证标头?

无法在 Spring Security 中为 oauth/token 端点启用 CORS

Spring Security 自定义登录回退