具有不同 UsernamePasswordAuthToken 的多个 AuthenticationProvider 可以在没有回退身份验证的情况下对不同的登录表单进行身份验证

Multiple AuthenticationProvider with different UsernamePasswordAuthToken to authenticate different login forms without fallback authentication

在使用 spring 安全性时,我查看了 Whosebug 中有趣的线程,其中要求对 两组用户 进行身份验证以对抗 不同的身份验证提供商 表示员工反对 LDAP,客户反对 DATABASE。 Thread 提出了公认的解决方案,使用一个带有单选按钮的登录表单来区分员工和客户,并使用 自定义身份验证过滤器 根据用户类型区分登录请求并设置不同的 authenticationToken(customerAuthToken/employeeAuthToken) 并请求进行身份验证。将有两个 AuthenticationProvider 实现,身份验证由支持令牌完成和决定。 通过这种方式,线程能够提供有趣的解决方案来避免 spring 安全性默认提供的回退身份验证。

看看线程Configuring Spring Security 3.x to have multiple entry points

因为答案完全在 xml 配置中。我只是想让解决方案在 java 配置中可用。我会在回答中发布它。

现在我的问题随着 spring 版本的演变,是否可以通过任何新功能/最小配置来获得相同的功能从我的回答?

由于 this thread 提供了完整的信息,我只是发布 java 配置参考的代码。

这里我假设以下内容
1. 用户和管理员作为两组用户。
2. 为简单起见,对两者都使用内存验证。
- 如果 userType 是用户,则只有用户凭据应该有效。
- 如果 userType 是 Admin,则只有管理员凭据才有效。 - 并且应该能够为不同的权限提供相同的应用程序接口。

和代码
You can download working code from my github repository


CustomAuthenticationFilter

@Component
public class MyAuthenticationFilter extends UsernamePasswordAuthenticationFilter
{
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException
    {
        UsernamePasswordAuthenticationToken authToken = null;

        if ("user".equals(request.getParameter("userType"))) 
        {
            authToken = new UserUsernamePasswordAuthenticationToken(request.getParameter("userName"), request.getParameter("password"));
        }
        else 
        {
            authToken = new AdminUsernamePasswordAuthenticationToken(request.getParameter("userName"), request.getParameter("password"));
        }

        setDetails(request, authToken);

        return super.getAuthenticationManager().authenticate(authToken);
    }
}

CustomAuthentictionTokens

public class AdminUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken
{   
    public AdminUsernamePasswordAuthenticationToken(Object principal, Object credentials)
    {
        super(principal, credentials);
    }

    public AdminUsernamePasswordAuthenticationToken(Object principal, Object credentials,
            Collection<? extends GrantedAuthority> authorities)
    {
        super(principal, credentials, authorities);
    }
}

public class UserUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken
{
    public UserUsernamePasswordAuthenticationToken(Object principal, Object credentials)
    {
        super(principal, credentials);
    }

    public UserUsernamePasswordAuthenticationToken(Object principal, Object credentials,
            Collection<? extends GrantedAuthority> authorities)
    {
        super(principal, credentials, authorities);
    }}

CustomAuthentictionProvider - For Admin

@Component
public class AdminCustomAuthenticationProvider implements AuthenticationProvider
{
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException
    {
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();

        if (username.equals("admin") && password.equals("admin@123#"))
        {
            List<GrantedAuthority> authorityList = new ArrayList<>();
            GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_ADMIN");
            authorityList.add(authority);

            return new UserUsernamePasswordAuthenticationToken(username, password, authorityList);
        }
        return null;
    }

    @Override
    public boolean supports(Class<?> authentication)
    {
        return authentication.equals(AdminUsernamePasswordAuthenticationToken.class);
    }
}

CustomAuthentictionProvider - For User

@Component
public class UserCustomAuthenticationProvider implements AuthenticationProvider
{
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException
    {

        String username = authentication.getName();
        String password = authentication.getCredentials().toString();

        if (username.equals("user") && password.equals("user@123#"))
        {
            List<GrantedAuthority> authorityList = new ArrayList<>();
            GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_USER");
            authorityList.add(authority);

            return new UserUsernamePasswordAuthenticationToken(username, password, authorityList);
        }
        return null;
    }

    @Override
    public boolean supports(Class<?> authentication)
    {
        return authentication.equals(UserUsernamePasswordAuthenticationToken.class);
    }
}

CustomHandlers required for CustomFilter

@Component
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler
{
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException
    {
        response.sendRedirect(request.getContextPath() + "/login?error=true");
    }   
}

@Component
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler
{
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException
    {
        HttpSession session = request.getSession();
        if (session != null)
        {
            session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
        }
        response.sendRedirect(request.getContextPath() + "/app/user/dashboard");
    }
}

最后SpringSecurityConfiguration

@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter 
{
    @Autowired
    DataSource dataSource;

    @Autowired
    private AdminCustomAuthenticationProvider adminCustomAuthenticationProvider;

    @Autowired
    private UserCustomAuthenticationProvider userCustomAuthenticationProvider;

    @Autowired
    private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;

    @Autowired
    private CustomAuthenticationFailureHandler customAuthenticationFailureHandler;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception
    {
        auth.authenticationProvider(adminCustomAuthenticationProvider);
        auth.authenticationProvider(userCustomAuthenticationProvider);
    }

    @Bean
    public MyAuthenticationFilter myAuthenticationFilter() throws Exception
    {
        MyAuthenticationFilter authenticationFilter = new MyAuthenticationFilter();

        authenticationFilter.setAuthenticationSuccessHandler(customAuthenticationSuccessHandler);
        authenticationFilter.setAuthenticationFailureHandler(customAuthenticationFailureHandler);
        authenticationFilter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/login", "POST"));
        authenticationFilter.setAuthenticationManager(authenticationManagerBean());

        return authenticationFilter;
    }

    @Override
    protected void configure(final HttpSecurity http) throws Exception
    {
        http
        .addFilterBefore(myAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
        .csrf().disable()
        .authorizeRequests()
            .antMatchers("/resources/**", "/", "/login")
                .permitAll()
            .antMatchers("/config/*", "/app/admin/*")
                .hasRole("ADMIN")
            .antMatchers("/app/user/*")
                .hasAnyRole("ADMIN", "USER")
            .antMatchers("/api/**")
                .hasRole("APIUSER")
        .and().exceptionHandling()
            .accessDeniedPage("/403")
        .and().logout()
            .logoutSuccessHandler(new CustomLogoutSuccessHandler())
            .invalidateHttpSession(true);

        http.sessionManagement().maximumSessions(1).expiredUrl("/login?expired=true");
    }
}

希望它有助于理解在没有后备身份验证的情况下配置多重身份验证。