Java Spring安全性-记住使用多个身份验证提供程序进行身份验证

Java Spring安全性-记住使用多个身份验证提供程序进行身份验证,java,spring,authentication,spring-security,remember-me,Java,Spring,Authentication,Spring Security,Remember Me,我在web应用程序中使用SpringSecurity3.2.0 我有两个安全角色:具有存储在数据库中的凭据的用户和具有存储在配置文件中的E凭据的管理员 它工作正常,直到我打开“记住我”身份验证。这是我的security.xmlconfig: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http

我在web应用程序中使用SpringSecurity3.2.0

我有两个安全角色:具有存储在数据库中的凭据的用户和具有存储在配置文件中的E凭据的管理员

它工作正常,直到我打开“记住我”身份验证。这是我的
security.xml
config:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:security="http://www.springframework.org/schema/security"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
   http://www.springframework.org/schema/security
   http://www.springframework.org/schema/security/spring-security-3.2.xsd">

    <security:global-method-security secured-annotations="enabled"/>

    <security:http auto-config="true">
        <security:remember-me key="myAppKey" user-service-ref="adminSrv"/>
        <security:remember-me key="myAppKey" user-service-ref="jdbcUserSrv"/>
        <security:form-login login-page="/login" default-target-url="/"
            authentication-failure-url="/loginfailed" />
        <security:logout logout-success-url="/logout"/>
    </security:http>

    <security:authentication-manager>
        <security:authentication-provider>
            <security:jdbc-user-service data-source-ref="dataSource" id="jdbcUserSrv"
                                        users-by-username-query="/* SQL query */"
                                        authorities-by-username-query="/* Another SQL query */"
                    />
        </security:authentication-provider>
        <security:authentication-provider>
            <security:user-service id="adminSrv">
                <security:user name="admin" authorities="ROLE_ADMIN" password="passwd"/>

            </security:user-service>
        </security:authentication-provider>
    </security:authentication-manager>
</beans>
但是,如果它成功通过身份验证,并且如果我打开我的应用程序索引页面,它将显示为“user1”登录

如果
记住我
jdbcUserSrv的配置先于adminSrv的配置,则“admin”用户会出现错误

当前配置在没有
记住我
选项的情况下工作正常,但我需要启用它们


请告诉我如何避免这些错误。

有两个“记住我”元素只会覆盖另一个元素。拥有多个UserDetailsService实例也会给您带来麻烦。例如,我假设您可能有与您的用户关联的数据。如果内存中和jdbc UserDetails服务实例中都存在名为admin的用户,那么如何知道数据属于哪个admin用户?因此,最好使用单个UserDetailsService

或者,您可以创建一个UserDetailsService,将其委托给多个UserDetailsService实例。例如:

public class DelegatingUserDetailsService implements UserDetailsService {
    private final List<UserDetailsService> userDetailsServices;

    public DelegatingUserDetailsService(
            List<UserDetailsService> userDetailsServices) {
        this.userDetailsServices = userDetailsServices;
    }

    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {
        RuntimeException last = null;
        for(UserDetailsService uds : userDetailsServices) {
            try {
                return uds.loadUserByUsername(username);
            } catch(RuntimeException error) {
                last = error;
            }
        }
        throw last;
    }
}
公共类DelegatingUserDetails服务实现UserDetails服务{
私有最终列表用户详细信息服务;
公共授权服务(
列出用户详细信息(服务){
this.userDetailsServices=userDetailsServices;
}
公共用户详细信息loadUserByUsername(字符串用户名)
抛出UsernameNotFoundException{
RuntimeException last=null;
对于(UserDetailsService uds:userDetailsServices){
试一试{
返回uds.loadUserByUsername(用户名);
}捕获(运行时异常错误){
last=错误;
}
}
扔到最后;
}
}
然后你可以记住我用它来代替:

<bean id="delegateUds" class="DelegatingUserDetailsService">
     <constructor-arg>
         <list>
             <ref bean="jdbcUserSrv"/>
             <ref bean="adminSrv"/>
         </list>
     </constructor-arg>
</bean>
<security:http auto-config="true">
    <security:remember-me key="myAppKey" user-service-ref="delegateUds"/>
    <security:form-login login-page="/login" default-target-url="/"
        authentication-failure-url="/loginfailed" />
    <security:logout logout-success-url="/logout"/>
</security:http>

如果您在Spring安全Java配置(Spring 5.0.6)中使用RememberMe

和多个身份验证提供程序,如下所示:

public void configureGlobal(AuthenticationManagerBuilder auth,
                            UserDetailsService userDetailsService) Exception {
        auth
            .inMemoryAuthentication()
                .withUser("ADMIN")
                    .password("{noopassword")
                    .roles("ADMIN", "USER")
                    .and()
                .withUser("USER")
                    .password("{noop}password)
                    .roles("USER");
        auth
            .userDetailsService(userDetailsService)
            .passwordEncoder(new BCryptPasswordEncoder());
}
将引发相同的异常,但仍将登录。 但令人惊讶的是,这会奏效:

@Autowired
@Order(1)
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {

    auth
            .inMemoryAuthentication()
                .withUser("ADMIN")
                    .password("{noop}password")
                    .roles("ADMIN", "USER")
                    .and()
                .withUser("USER")
                    .password("{noop}password")
                    .roles("USER");
}
@Autowired
@Order(2)
public void configureGlobal(AuthenticationManagerBuilder auth,
                            UserDetailsService userDetailsService) throws Exception {
    auth
            .userDetailsService(userDetailsService)
            .passwordEncoder(new BCryptPasswordEncoder());
}

重要的是,即使它们有不同的方法参数,如果只有一个需要,它们都必须有@Autowired

谢谢您的回复。我决定实现我自己的UserDetailsService,但您的解决方案也非常有用。好了,spring要求您记住我,这需要UserDetailsService bean。在spring doc上提到过,
public void configureGlobal(AuthenticationManagerBuilder auth,
                            UserDetailsService userDetailsService) Exception {
        auth
            .inMemoryAuthentication()
                .withUser("ADMIN")
                    .password("{noopassword")
                    .roles("ADMIN", "USER")
                    .and()
                .withUser("USER")
                    .password("{noop}password)
                    .roles("USER");
        auth
            .userDetailsService(userDetailsService)
            .passwordEncoder(new BCryptPasswordEncoder());
}
@Autowired
@Order(1)
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {

    auth
            .inMemoryAuthentication()
                .withUser("ADMIN")
                    .password("{noop}password")
                    .roles("ADMIN", "USER")
                    .and()
                .withUser("USER")
                    .password("{noop}password")
                    .roles("USER");
}
@Autowired
@Order(2)
public void configureGlobal(AuthenticationManagerBuilder auth,
                            UserDetailsService userDetailsService) throws Exception {
    auth
            .userDetailsService(userDetailsService)
            .passwordEncoder(new BCryptPasswordEncoder());
}