自定义 AuthenticationFilter 设置“LoginProcessingUrl”无效

Custom AuthenticationFilter Setting `LoginProcessingUrl` is Invalid

我正在构建一个 OAuth2 授权服务器,它支持 Restful API,具有 Spring 授权服务器和 Spring 安全性。 我想要一个React搭建的SPA应用,在/login提供登录界面,通过Post请求将登录信息提交到 /api/login路径。 我扩展 UsernamePasswordauthenticationFilter 以支持 Restful 风格的 Post 请求来解析来自正文的 Json 数据:

public class RestfulUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    public RestfulUsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    private String jsonUsername;
    private String jsonPassword;

    @Override
    protected String obtainPassword(HttpServletRequest request) {
        if (request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
            return this.jsonPassword;
        } else {
            return super.obtainPassword(request);
        }

    }

    @Override
    protected String obtainUsername(HttpServletRequest request) {

        if (request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
            return this.jsonUsername;
        } else {
            return super.obtainUsername(request);
        }

    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException {
        if ("application/json".equals(request.getHeader("Content-Type"))) {
            try {
                /*
                 * HttpServletRequest can be read only once
                 */

                //json transformation
                Map<String, String> requestMap = new ObjectMapper().readValue(request.getInputStream(), Map.class);


                this.jsonUsername = requestMap.get("username");
                this.jsonPassword = requestMap.get("password");
            } catch (Exception e) {
                throw new AuthenticationServiceException(e.getMessage(), e);
            }
        }

        return super.attemptAuthentication(request, response);
    }
}

在配置中,我将自定义的RestfulUsernamePasswordauthenticationFilter替换为UsernamePasswordauthenticationFilter,并使用.loginProcessUrl将处理Post请求的路径设置为/api/login :

    @Override
    protected void configure(HttpSecurity http)
            throws Exception {
        http.addFilterAt(new RestfulUsernamePasswordAuthenticationFilter(authenticationManagerBean()),
                UsernamePasswordAuthenticationFilter.class);
        http.authorizeRequests()
                .antMatchers("/api/all")
                .permitAll()
                .antMatchers("/api/auth")
                .hasRole("USER")
                .antMatchers("/api/admin")
                .hasRole("ADMIN")
                .antMatchers("/login", "/register", "/api/login")
                .permitAll();



        http.formLogin()
                .loginPage("/login")
                .loginProcessingUrl("/api/login");
        http.csrf().disable();
    }

问题是,虽然我设置了通过.loginProcessingUrl处理Post请求的路径,但似乎不起作用。 当一个Post请求提交到/api/login时,它会像所有未认证的请求一样被重定向到/login,提交到/login的请求将正常生效。

在调试的过程中,发现.loginProcessingUrl会在一个UsernamePasswordconfirationFilter中注册这个路径,但不会被我自定义的RestfulUsernamePasswordShareationFilter处理。在调试的过程中,发现.loginProcessingUrl会在一个UsernamePasswordFilter中注册这个路径。 我想知道是否有任何方法可以使 .loginProcessingUrl 在我的自定义 AuthenticationFilter 上工作。 同时,当我添加更多自定义Filter时,是否可以轻松自定义他们接受请求的路径? 也许以后我会添加更多需要读取Restful信息的AuthenticationProvider。我应该如何设计这些Filter和Provider的架构,使其更容易扩展?

我想我在阅读 this blog 后解决了问题。正如它所说:

After you provided your custom filter, you can no longer use the Spring HttpSecurity builder. If you still use it, you’ll configure the default Filter, not yours!

We tell Spring to use our implementatin by the “addFIlterBefore” function.

After this little modification, the test APIs work the same way, the only difference is that you should provide ‘login’ and ‘password’ params in the POST request body (and not the query string).

所以我必须像这样手动设置过滤器 class 的 AntPathRequestMatchersetRequiresAuthenticationRequestMatcher

RestfulUsernamePasswordAuthenticationFilter restfulFilter = new RestfulUsernamePasswordAuthenticationFilter(
                this.authenticationManagerBean());
restfulFilter.setRequiresAuthenticationRequestMatcher(
                new AntPathRequestMatcher("/api/login", "POST"));
http.addFilterBefore(restfulFilter,
                UsernamePasswordAuthenticationFilter.class);

这样就可以像正规的loginProcessingUrl一样配置处理POST请求的路径

您以后也可以使用相同的方法添加更多自定义过滤器和authenticationProvider,只需手动配置即可。