Spring 如何在authenticationManagerBuilder上注册多个UserDetails服务

Spring 如何在authenticationManagerBuilder上注册多个UserDetails服务,spring,spring-boot,authentication,spring-security,userdetailsservice,Spring,Spring Boot,Authentication,Spring Security,Userdetailsservice,我有两个不同的存储库供普通用户和管理员使用,并有单独的url端点进行身份验证。我希望身份验证管理器对端点使用单独的UserDetailsService,因为普通用户和管理员用户可以有相同的用户名和密码,但存储库不同 例如: 如果端点命中为用户登录,则为用户详细信息服务1和 如果端点命中是admin_login,则为UserDetailsService2 如何实现这一点?HttpSecurity.formLogin DSL只支持单个登录URL,因为这是最常见的。但是,您可以通过显式注册第二个用户名

我有两个不同的存储库供普通用户和管理员使用,并有单独的url端点进行身份验证。我希望身份验证管理器对端点使用单独的
UserDetailsService
,因为普通用户和管理员用户可以有相同的用户名和密码,但存储库不同

例如: 如果端点命中为用户登录,则为用户详细信息服务1和 如果端点命中是admin_login,则为UserDetailsService2
如何实现这一点?

HttpSecurity.formLogin DSL只支持单个登录URL,因为这是最常见的。但是,您可以通过显式注册第二个
用户名密码AuthenticationFilter
来做到这一点。有一些很好的图表说明了基于表单的登录是如何工作的

我创建了一个(确保使用链接的分支)。以下是正在发生的事情的摘要和描述:

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                // allow anyone to access the admin log in page
                .mvcMatchers("/admin_login").permitAll()
                // require admin access to any admin URLs
                .mvcMatchers("/admin/**").hasRole("ADMIN")
                // any other URL just requires to be authenticated
                .anyRequest().authenticated()
                .and()
            // configure the user based authentication
            .formLogin()
                // this means you should serve a log in page for users at GET /user_login
                // Spring Security will process POST /user_login as a user log in
                .loginPage("/user_login")
                // allow anyone to access the /user_login since they aren't authenticated when they see a log in page
                .permitAll()
                .and()
             // formLogin above only supports a single repository because that is what is most common
             // fortunately formLogin is just a shortcut for the code below
             // here we add the admin login form explicitly
            .addFilter(adminAuthenticationFilter());
    }

    // formLogin for users will pick up a UserDetailsService exposed as a Bean    
    @Bean
    static InMemoryUserDetailsManager userDetailsManager() {
        UserDetails user = User.withDefaultPasswordEncoder()
            .username("user")
                .password("user")
                .roles("USER")
                .build();
        return new InMemoryUserDetailsManager(user);
    }

    // create an admin version of UsernamePasswordAuthenticationFilter     
    private static UsernamePasswordAuthenticationFilter adminAuthenticationFilter() {
        // inject the adminAuthenticationProvider so only admins are authenticated with this Filter
        UsernamePasswordAuthenticationFilter result = new UsernamePasswordAuthenticationFilter(adminAuthenticationProvider());
        // only process POST /admin_login
        result.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin_login", "POST"));
        // errors should go to /admin_login?error
        result.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler("/admin_login?error"));
        return result;
    }

    // create an AuthenticationManager that is only used by Admin users
    private static AuthenticationManager adminAuthenticationProvider() {
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        authenticationProvider.setUserDetailsService(adminUsers());
        return new ProviderManager(authenticationProvider);
    }

    // we use the same username as user based to demon that it will work properly
   // the difference is that the password is admin and the user will have admin role so it can access URLs in /admin/
    static InMemoryUserDetailsManager adminUsers() {
        UserDetails user = User.withDefaultPasswordEncoder()
                .username("user")
                .password("admin")
                .roles("USER", "ADMIN")
                .build();
        return new InMemoryUserDetailsManager(user);
    }
}
如上所述,您负责创建登录页面并确保它们发布到正确的URL。第一步是创建一个控制器,将URL映射到要显示的视图。为了方便起见,这里我们使用单个控制器,但您可以将其拆分:

@Controller
public class LoginController {

    @GetMapping("/admin_login")
    String adminLogin() {
        return "admin_login";
    }

    @GetMapping("/user_login")
    String login() {
        return "user_login";
    }
}
然后需要有两个视图。第一个视图是admin_login.html。在Boot+Thymeleaf应用程序中,类似的内容将位于
src/main/resources/templates/user\u login.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <title>User Log In</title>
</head>
<body>
<div class="container">
    <h1>User Log In</h1>
    <form method="post"
          th:action="@{/user_login}">
        <div th:if="${param.error}">
            Invalid username and password.
        </div>
        <div th:if="${param.logout}">
            You have been logged out.
        </div>
        <div>
            <label for="username">Username</label>
            <input type="text"
                   id="username"
                   name="username"
                   placeholder="Username"
                   required autofocus>
        </div>
        <div class="form-group">
            <label for="password">Password</label>
            <input type="password"
                   id="password"
                   name="password"
                   placeholder="Password"
                   required>
        </div>
        <button type="submit">Sign in</button>
    </form>
</div>
</body>
</html>

用户登录
用户登录
无效的用户名和密码。
您已注销。
用户名
密码
登录
我在上面提供的链接中对此进行了详细介绍。关键是它使用HTTP参数
用户名
密码
/user\u login
提交帖子


对于管理员登录,您需要一个类似的视图,该视图使用HTTP参数
用户名
密码

发布到
/admin\u login
,您可以有这样的功能

@Override
    public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder
                .userDetailsService(service1)
                .passwordEncoder(passwordEncoder());
        authenticationManagerBuilder
                .userDetailsService(service2)
                .passwordEncoder(passwordEncoder());
    }