如何在使用多个AuthenticationProvider时从PreAuthenticatedAuthenticationProvider重定向UsernameNotFoundException?

时间:2015-10-02 16:16:12

标签: spring authentication spring-security authorization pre-authentication

使用Spring Security 4.02,任何人都可以帮助我提供一些提示,了解如何在使用多个UsernameNotFoundException时从PreAuthenticatedAuthenticationProvider处理AuthenticationProviders,以便经过身份验证的请求具有正确的标头,但是未经授权,是否发送到特定的URL而不是表单登录页面?

让我进一步解释一下我正在尝试访问由代理后面的SSO保护的Web应用程序。并非所有通过SSO身份验证的用户都可以访问此应用。所以我需要考虑3种访问方案:

  1. 经过身份验证的用户(标头存在)已获得授权(用户名/角色存在于应用程序的数据库中)
  2. 经过身份验证的用户(标头已存在)未经授权(用户/角色 >存在于应用的数据库中)
  3. 应用程序数据库中存在用户名/角色的未经身份验证的用户
  4. 访问网站时的操作应为:

    1. 经过身份验证/授权的用户直接进入目标网址
    2. 经过身份验证/未经授权的用户被重定向到错误/信息页面
    3. 未经身份验证的用户被重定向到用于身份验证的表单登录页面
    4. 使用我当前的配置,方案1& 3似乎按预期工作。对于方案2,我尝试将RequestHeaderAuthenticationFilter#setExceptionIfHeaderMissing设置为true和false。

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

      如果setExceptionIfHeaderMissing=true,经过身份验证/未经授权的请求会从PreAuthenticatedCredentialsNotFoundException遇到AbstractPreAuthenticatedProcessingFilter.doAuthenticate并返回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 个答案:

答案 0 :(得分:3)

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

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