How do I disable resolving login parameters passed

2019-01-18 01:30发布

The application logs all requested urls. This means, that it's critical not to authenticate using url parameters, because it would cause the situation in which logs are full of pairs (login=abc&password=123). For this reason I've configured spring-security to read parameters from request-body. It's done by adding the following line to the request-header:

'Content-Type': 'application/x-www-form-urlencoded'

The body will be:

{'login':'admin', 'password':'password'}

It's fine, but the QA forces me to disable the possibility of authentication via url paramters. At the moment a POST to the following URL will also authenticate:

https://example.com/foo?login=admin&password=password

Does anyone know a trick to disable this option? With an annotation preferably.

Due to the comment I decided to add some more details to my problem. My is configured with WebSecurityConfigurerAdapter. I have

http.usernameParameter("login")
    .passwordParameter("password")
(...)

This makes Spring searching login data in both - parameters and body. I wish to disable searching those parameters in the url.

4条回答
对你真心纯属浪费
2楼-- · 2019-01-18 02:01

This makes Spring searching login data in both - parameters and body. I wish to disable searching those parameters in the url.

I believe this is not possible since this behaviour is not implemented by Spring rather than JavaEE itself.

HttpServletRequest.getParameter doc states:

Returns the value of a request parameter as a String, or null if the parameter does not exist. Request parameters are extra information sent with the request. For HTTP servlets, parameters are contained in the query string or posted form data.

But you can try to alter this with filter that should look something like this:

public class DisableGetAuthFiler extends OncePerRequestFilter {
    ...

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        filterChain.doFilter(
                new HttpServletRequestWrapper(request) {
                    @Override
                    public String getParameter(String name) {
                        if (("login".equals(name) && getQueryString().contains("login"))
                                || ("password".equals(name) && getQueryString().contains("password"))) {
                            return null;
                        } else {
                            return super.getParameter(name);
                        }
                    }
                },
                response
        );
    }
}

EDIT Haim Raman proposed another solution that uses existing filter instead of introducing a new one. Only I would suggest overriding obtainUsername() and obtainPassword() instead of attemptAuthentication().

查看更多
乱世女痞
3楼-- · 2019-01-18 02:02

To the best of my knowledge and intuition, like jhan had mentioned, the appropriate solution would be to use annotation @RequestMapping(value="/login", method="RequestMethod.POST"). Then, no matter what parameters the user may pass with the URL, both the URL and URI will always default to /login. And that is what the logger will document. Not the username and password pairs, but "http://localhost:8080/login", or whatever your port is.

查看更多
时光不老,我们不散
4楼-- · 2019-01-18 02:04

I would like to suggest an alternative which is based on spring-security rater then a workaround as suggested by chimmi.

This answer provide a solution to the issue suggested by xenteros on bres26 answer as well

Override the exiting UsernamePasswordAuthenticationFilter implementation

public class ImprovedUsernamePasswordAuthenticationFilter 
                                    extends UsernamePasswordAuthenticationFilter {

    @Override
    protected String obtainUsername(HttpServletRequest request) {
        final String usernameParameter = getUsernameParameter();
        validateQueryParameter(request, usernameParameter);
        return super.obtainUsername(request);
    }

    @Override
    protected String obtainPassword(HttpServletRequest request) {
        final String passwordParameter = getPasswordParameter();
        validateQueryParameter(request, passwordParameter);
        return super.obtainPassword(request);
    }

    private void validateQueryParameter(HttpServletRequest request, String parameter) {
        final String queryString = request.getQueryString();
        if (!StringUtils.isEmpty(queryString)) {
            if (queryString.contains(parameter))
                throw new AuthenticationServiceException("Query parameters for login are a prohibit, use message body only!");

        }
    }

 }

You need to replace your own implementation with the existing one (see doc here)

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/", "/home","/login").permitAll()
                .anyRequest().authenticated()
                .and()
            .logout()
                .permitAll()
                .and()
             //Replace FORM_LOGIN_FILTER with your own custom implementation
             .addFilterAt(improvedUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
               .exceptionHandling()
               .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
               .and()
            //disable csrf to allow easy testing
             .csrf().disable();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .inMemoryAuthentication()
                .withUser("user").password("password").roles("USER");
    }

    public UsernamePasswordAuthenticationFilter improvedUsernamePasswordAuthenticationFilter() throws Exception {
        UsernamePasswordAuthenticationFilter authFilter = new ImprovedUsernamePasswordAuthenticationFilter();
        authFilter.setRequiresAuthenticationRequestMatcher(
                new AntPathRequestMatcher("/login", "POST")
         );
        authFilter
        .setAuthenticationManager(authenticationManager());
        authFilter
       .setAuthenticationSuccessHandler(
           new SavedRequestAwareAuthenticationSuccessHandler()
        );
       authFilter
       .setAuthenticationFailureHandler(
         new SimpleUrlAuthenticationFailureHandler("/login?error")
       );
        return authFilter;
    }
}

Advantages: it’s based on spring security and flexible to changes.
Disadvantage: Unfortunately I found Spring Java Config very hard to set and to read

EDIT: I accepted chimmi comment and overridden obtainUsername and obtainPassword
You can find the source code in github.

查看更多
相关推荐>>
5楼-- · 2019-01-18 02:05

You can achieve this by modifying the UsernamePasswordAuthenticationFilter's RequestMatcher. For example:

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .formLogin()
                    .withObjectPostProcessor(new ObjectPostProcessor<UsernamePasswordAuthenticationFilter>() {
                        @Override
                        public <O extends UsernamePasswordAuthenticationFilter> O postProcess(
                                O filter) {
                            AntPathRequestMatcher pathMatcher = new AntPathRequestMatcher("/login", "POST");
                            RequestMatcher noQuery = new RequestMatcher() {

                                @Override
                                public boolean matches(HttpServletRequest request) {
                                    return request.getQueryString() == null;
                                }
                            };
                            AndRequestMatcher matcher = new AndRequestMatcher(Arrays.asList(pathMatcher, noQuery));
                            filter.setRequiresAuthenticationRequestMatcher(matcher);
                            return filter;
                        }
                    })
                    .and()
                ...
        }
}

NOTE: The requirement below does not prevent a GET request from being issued (and thus leaking the credentials). It is really up to the UI to ensure this doesn't happen.

It's fine, but the QA forces me to disable the possibility of authentication via url paramters.

查看更多
登录 后发表回答