【问题标题】:Custom spring security filter not called at runtime运行时未调用自定义弹簧安全过滤器
【发布时间】:2018-11-09 13:51:36
【问题描述】:

我正在尝试在 Spring Boot Rest 服务项目中启用 Spring Security,但遇到了一些问题。

我用这段代码配置了它

@Configuration
@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {

    @Autowired
    private LdapAuthenticationProvider ldapAuthenticationProvider;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .httpBasic().and()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        auth.authenticationProvider(ldapAuthenticationProvider);
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

}

并实现了一个自定义身份验证提供程序以登录 LDAP(它具有非标准配置,因此我无法使默认 ldap 提供程序工作)

@Component
public class LdapAuthenticationProvider implements AuthenticationProvider {

    @Override
    public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {

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

        LdapConnection ldap = new LdapConnection();
        String uid = ldap.getUserUID(email);
        if(uid == null || uid == ""){
            throw new BadCredentialsException("User " + email + " not found");
        }

        if(ldap.login(uid, password)){

            return new UsernamePasswordAuthenticationToken(uid, null, new ArrayList<>());
        }else{
            throw new BadCredentialsException("Bad credentials");
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return true;  
        //To indicate that this authenticationprovider can handle the auth request. since there's currently only one way of logging in, always return true

    }

}

这段代码运行良好,因为使用基本授权标头调用我的服务,它能够正确登录并返回调用的服务。当我尝试插入不同的授权/身份验证时,问题就开始了。我不想使用基本身份验证,而是想从我的反应前端中的表单传递凭据,所以我想在 POST 调用中将它们作为 json 主体传递。 (想法是生成一个 jwt 令牌并将其用于以下通信)。

所以我把configure方法改成这样:

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .addFilter(new JWTAuthenticationFilter(authenticationManager()))
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

并定义了一个自定义身份验证过滤器:

public class JWTAuthenticationFilter extends
        UsernamePasswordAuthenticationFilter {

    @Autowired
    private AuthenticationManager authenticationManager;

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException{
        String requestBody;
        try{
            requestBody = IOUtils.toString(req.getReader());
            JsonParser jsonParser = JsonParserFactory.getJsonParser();
            Map<String, Object> requestMap = jsonParser.parseMap(requestBody);
            return authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(requestMap.get("email"), requestMap.get("password"), new ArrayList<>()));
        }catch(IOException e){
            throw new InternalAuthenticationServiceException("Something goes wrong parsing the request body",e );
        }
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain, Authentication auth) throws IOException{

        JwtTokenProvider tokenProvider = new JwtTokenProvider();
        String token = tokenProvider.generateToken(auth.getPrincipal().toString());
        Cookie cookie = new Cookie("jwt",token);
        cookie.setHttpOnly(true);
        cookie.setSecure(true);
        res.addCookie(cookie);

    }

}

问题是,无论我在做什么,运行时似乎根本没有进入这个过滤器。我错过了什么?我想这是一件又大又愚蠢的事情,但我想不通......

更新:问题似乎是 UsernamePassWordAuthenticationFilter 只能通过表单调用。然后,我将代码更改为扩展 AbstractAuthenticationProcessingFilter。

修改后的过滤器:

public class JWTAuthenticationFilter extends
        AbstractAuthenticationProcessingFilter {


    private AuthenticationManager authenticationManager;

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        super("/api/secureLogin");
        this.authenticationManager = authenticationManager;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException{
        String requestBody;
        try{
            requestBody = IOUtils.toString(req.getReader());
            JsonParser jsonParser = JsonParserFactory.getJsonParser();
            Map<String, Object> requestMap = jsonParser.parseMap(requestBody);
            UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(requestMap.get("email"), requestMap.get("password"), new ArrayList<>()); 
            return authenticationManager.authenticate(token);
        }catch(IOException e){
            throw new InternalAuthenticationServiceException("Something goes wrong parsing the request body",e );
        }
    }
}

以及修改后的配置方法:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable().authorizeRequests()
        .antMatchers(HttpMethod.POST, "/api/secureLogin").permitAll()
        .antMatchers(HttpMethod.GET, "/api").permitAll()
        .antMatchers("/api/**").authenticated()
        .and()
        .addFilterBefore(new JWTAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class)
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}

【问题讨论】:

  • 当您希望此过滤器使用时,请确认您正在点击“/login”端点。
  • 我在 /api/login 上有一个自定义控制器(基本上是写一个字符串)并且具有相同的行为。我也试过 /login (我没有定义任何东西)但它没有改变:过滤器没有被击中。
  • 好的,所以我发现只有在我使用默认表单登录时才会调用它(在我的配置方法中添加.formLogin())。然后我可以定义一个自定义表单,但作为一个 web 服务应用程序(用户界面在其他地方管理)我不想在这里生成一个登录表单,因为我想使用我已经拥有的休息调用......我应该扩展一个不同的类然后 UsernamePasswordAuthenticationFilter?在那种情况下,是否有一个类可以用于我的场景,或者我应该扩展 AbstractAuthenticationProcessingFilter 类吗?
  • 所以,这里有几件事。第一个是,默认情况下,UsernamePasswordAuthenticationFilter#attemptAuthentication 仅在 /login 或任何配置为 filterProcessesUrl 的情况下调用——过滤器可能会拒绝 requiresAuthentication 方法中的请求。其次,是的,如果这是一个休息电话,那么扩展 UsernamePasswordAuthenticationFilter 有点奇怪。如果您看一下successAuthentication,您会看到默认的successHandler 想要在完成后重定向调用。我可能只是扩展 OncePerRequestFilter。
  • 谢谢,我已经使用 AbstractAuthenticationProcessingFilter 解决了(请参阅帖子上的更新),但我会看看你提到的 OncePerRequestFilter 类。

标签: spring spring-boot spring-security


【解决方案1】:

我看到您在覆盖配置方法时添加了配置,请尝试在您的 web.xml 中添加过滤器映射。在“web-app”节点下是这样的:

<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter>
    <filter-name>JWTAuthenticationFilter</filter-name>
    <filter-class>com.yourProject.JWTAuthenticationFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>JWTAuthenticationFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

【讨论】:

  • 我没有任何 web.xml,因为我正在使用 Spring Boot。无论如何,配置注释不应该是必需的,我可能在之前的尝试中错误地将它留在那里,我将删除它。
  • 您的回答对我帮助很大,谢谢!
【解决方案2】:

您希望通过访问路径api/secureLogin 来触发过滤器。默认情况下,UsernamePasswordAuthenticationFilter 仅通过访问 /login 触发。

如果您在扩展 UsernamePasswordAuthenticationFilterJWTAuthenticationFilter 的构造函数中添加以下行,它应该可以工作:

  this.setFilterProcessesUrl("/api/secureLogin");

【讨论】:

    【解决方案3】:

    嗨@Mikyjpeg

    您可以使用 UsernamePassWordAuthenticationFilter。不必像您提到的那样从表单中调用它。

    只要通过带有/login url(而不是你的api/secureLogin url)的POST 方法调用它,它就会被执行。

    构造函数使用了一个请求匹配器,它只允许这个 url & request 方法:

    public UsernamePasswordAuthenticationFilter() {
      super(new AntPathRequestMatcher("/login", "POST"));
    }
    

    【讨论】:

      【解决方案4】:

      ADAM 的回答对我有用。那些正在使用基于注释的配置的人可以添加以下内容

      @Override protected Filter[] getServletFilters() {
         return new Filter[]{
              new JwtAuthenticationFilter()
         }; 
      }
      

      在您的 AppInitialiser 中扩展 AbstractAnnotationConfigDispatcherServletInitializer

      【讨论】:

        【解决方案5】:
        public class BeforeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
        
            public BeforeAuthenticationFilter() {
                super(new AntPathRequestMatcher("api/secureLogin", "POST"));
                super.setFilterProcessesUrl("api/secureLogin");
            }
        

        【讨论】:

        • 正如目前所写,您的答案尚不清楚。请edit 添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。你可以找到更多关于如何写好答案的信息in the help center
        • 请提供更多详细信息并确保在尝试回答任何其他问题之前阅读how to write good answer
        猜你喜欢
        • 2014-05-30
        • 2015-09-20
        • 2015-07-15
        • 1970-01-01
        • 1970-01-01
        • 2017-03-18
        • 2017-03-18
        相关资源
        最近更新 更多