使用多个 AuthenticationProvider 时如何从 PreAuthenticatedAuthenticationProvider 重定向 UsernameNotFoundExceptio

Posted

技术标签:

【中文标题】使用多个 AuthenticationProvider 时如何从 PreAuthenticatedAuthenticationProvider 重定向 UsernameNotFoundException?【英文标题】:How to redirect UsernameNotFoundException from PreAuthenticatedAuthenticationProvider when using multiple AuthenticationProviders? 【发布时间】:2015-12-30 22:04:13 【问题描述】:

使用 Spring Security 4.02,任何人都可以提供一些提示,说明我在使用多个 PreAuthenticatedAuthenticationProvider 时如何处理 UsernameNotFoundException一个特定的 URL 而不是表单登录页面?

让我进一步解释一下我在访问受代理后 SSO 保护的 Web 应用程序时要完成的工作。并非所有通过 SSO 身份验证的用户都可以访问此应用程序。所以我需要考虑 3 种访问场景:

    经过身份验证的用户(存在标头)已获得授权(用户名/角色存在于应用的数据库中) 经过身份验证的用户(存在标头)未经授权(用户名/角色存在于应用的数据库中) 应用数据库中存在用户名/角色的未经身份验证的用户

访问网站时的操作应该是:

    经过身份验证/授权的用户直接进入目标 URL 经过身份验证/未经授权的用户被重定向到错误/信息页面 未经身份验证的用户被重定向到表单登录页面进行身份验证

在我当前的配置下,场景 1 和 3 似乎可以正常工作。对于场景 2,我尝试将 RequestHeaderAuthenticationFilter#setExceptionIfHeaderMissing 设置为 true 和 false。

如果setExceptionIfHeaderMissing=false,经过身份验证/未经授权的请求由ExceptionTranslationFilter 处理,其中AccessDeniedException 被抛出,用户被重定向到表单登录页面。

如果setExceptionIfHeaderMissing=true,经过身份验证/未经授权的请求遇到来自AbstractPreAuthenticatedProcessingFilter.doAuthenticatePreAuthenticatedCredentialsNotFoundException并返回HTTP 500。

所以我已经阅读并重新阅读了 Spring Security 参考和 api 文档,并搜索了网络,但无法完全弄清楚我需要做什么。我想我需要以某种方式启用某种过滤器或处理程序来捕获带有重定向响应的PreAuthenticatedCredentialsNotFoundException。但我似乎无法用所有可用的弹簧工具来实现它。有人可以提供一些细节吗?非常感谢提前!

这是我的配置:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter  

    private static final String AUTHENTICATION_HEADER_NAME = "PKE_SUBJECT";

    @Autowired
    CustomUserDetailsServiceImpl customUserDetailsServiceImpl;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception 
        auth.authenticationProvider(preAuthenticatedAuthenticationProvider());
        auth.inMemoryAuthentication()
            .withUser("user").password("password").roles("USER").and()
            .withUser("admin").password("password").roles("USER", "ADMIN");
        auth.userDetailsService(customUserDetailsServiceImpl);
    

    @Override
    protected void configure(HttpSecurity http) throws Exception 
        http.csrf().and()
            .authorizeRequests()
                .antMatchers("/javax.faces.resource/**", "/resources/**", "/templates/**", "/public/**").permitAll()                
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .permitAll()
                .and()
            .logout()
                .logoutSuccessUrl("/public/welcome.xhtml")
                .and()
            .addFilter(requestHeaderAuthenticationFilter());    
    

    @Bean PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider() throws Exception 
        PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
        provider.setPreAuthenticatedUserDetailsService(userDetailsServiceWrapper());
        return provider;
    

    @Bean
    public RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter() throws Exception 
        RequestHeaderAuthenticationFilter filter = new RequestHeaderAuthenticationFilter();
        filter.setPrincipalRequestHeader(AUTHENTICATION_HEADER_NAME);
        filter.setAuthenticationManager(authenticationManagerBean());
        filter.setExceptionIfHeaderMissing(true);
        return filter;
    

    @Bean
    public UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> 
            userDetailsServiceWrapper() throws Exception 

        UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> wrapper 
                = new UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>();
        wrapper.setUserDetailsService(customUserDetailsServiceImpl);
        return wrapper;
    

我自定义的 UserDetailsS​​ervice:

@Service("customUserDetailsService")
public class CustomUserDetailsServiceImpl implements UserDetailsService 

    @Autowired
    UserRepo userRepo;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException 

        UserDetailDO userDetail = userRepo.getUserDetailById(username);
        if(userDetail == null) 
            throw new UsernameNotFoundException("user is not authorized for this application");         
        

        List<UserRoleDO> roles = userRepo.getRolesByUsername(username);
        List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();

        if(CollectionUtils.isNotEmpty(roles)) 
            for(UserRoleDO role : roles) 
                SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role.getRole());
                authorities.add(authority);             
            
        

        UserDetails user = new User(username, "N/A", authorities);      
        return user;
    

【问题讨论】:

【参考方案1】:

我意识到我不需要处理异常。我所做的就是改变我的想法。我意识到,即使 customUserDetailsS​​ervice 没有找到用户名,该请求仍然是经过身份验证的请求,因为该请求被 SSO 和代理服务器所信任。

因此,我没有返回 UsernameNotFoundException,而是返回了带有空 Authorities 集合的 org.springframework.security.core.userdetails.User。并且由于 RequestHeaderAuthenticationFilter.setExceptionIfHeaderMissing = false 默认情况下,不会抛出异常,然后将经过身份验证的请求传递给访问过滤器,在该过滤器中确定该请求无权访问任何资源。因此,不是重定向到下一个身份验证过滤器,即表单登录提供程序,而是返回 403 Access Denied http 状态,然后我可以覆盖它以重定向到用户友好的错误页面。

【讨论】:

据我所知,权限集合不能为空或为空

以上是关于使用多个 AuthenticationProvider 时如何从 PreAuthenticatedAuthenticationProvider 重定向 UsernameNotFoundExceptio的主要内容,如果未能解决你的问题,请参考以下文章

将多个 CTE 与多个临时表一起使用

PHPUnit:如何使用多个参数模拟多个方法调用?

iOS:使用多个相同的视图控制器处理多个 uilocalnotification

在 MySQL 中使用 LEFT JOINing 多个表搜索多个值

多个视口与多个帧缓冲区之间的使用差异是啥?

使用 iOS 地理围栏跟踪多个(20 多个)位置