Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Security - Error 405 when invoking login-processing-url

I have a sprinc core + spring mvc + security up and running, using XML configuration and these are the versions I am currently using within my pom.xml:

<spring.version>4.0.1.RELEASE</spring.version>
<spring.security.version>3.2.0.RELEASE</spring.security.version>

So I decided to go ahead and move all my xml configuration to java based configuration. What happens is that the login-processing-url for Spring security stopped working and now gives me an error 405, saying that POST is not allowed. For instance, this is my spring security xml file:

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
   xmlns:context="http://www.springframework.org/schema/context"
   xmlns:util="http://www.springframework.org/schema/util"
   xmlns:p="http://www.springframework.org/schema/p" 
   xmlns="http://www.springframework.org/schema/security"
   xsi:schemaLocation="http://www.springframework.org/schema/beans      http://www.springframework.org/schema/beans/spring-beans-4.0.xsd 
                   http://www.springframework.org/schema/context    http://www.springframework.org/schema/context/spring-context-4.0.xsd
                   http://www.springframework.org/schema/util       http://www.springframework.org/schema/util/spring-util-4.0.xsd
                       http://www.springframework.org/schema/security   http://www.springframework.org/schema/security/spring-security-3.2.xsd">

<!-- Satic content -->
<http pattern="/assets/**" security="none" />
<http pattern="/css/**" security="none" />
<http pattern="/img/**" security="none" />
<http pattern="/js/**" security="none" />

<!--http://docs.spring.io/spring-security/site/docs/3.2.0.RELEASE/reference/htmlsingle/#ns-global-method-->
<global-method-security pre-post-annotations="enabled" />

<!-- Protected Requests -->
<http auto-config="true" use-expressions="true" authentication-manager-ref="authenticationManager">

    <intercept-url pattern="/login/**" access="permitAll" />
    <intercept-url pattern="/api/**" access="permitAll" />
    <intercept-url pattern="/**" access="isRememberMe() or isFullyAuthenticated()" />

    <form-login login-processing-url="/loginCheck"
        login-page="/login" 
        authentication-failure-url="/loginFailure" 
        default-target-url="/" 
        always-use-default-target="true" 
        password-parameter="password"
        username-parameter="username"/>

    <logout delete-cookies="JSESSIONID" logout-url="/logout" logout-success-url="/login" />
    <access-denied-handler error-page="/403" />

    <session-management invalid-session-url="/login" session-fixation-protection="newSession" />

    <headers>
        <frame-options/>
        <xss-protection/>
    </headers>

    <remember-me services-ref="rememberMeServices" />
</http> 

<authentication-manager alias="authenticationManager">
    <authentication-provider ref="mongoDashAuthenticationProvider" />
    <authentication-provider ref="rememberMeAuthenticationProvider" />
</authentication-manager>

<beans:bean id="rememberMeServices" class="org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices">
    <beans:constructor-arg value="${security.app.key}" />
    <beans:constructor-arg ref="userServiceImpl" />
    <beans:constructor-arg ref="mongoDashPersistentTokenRepository" />
    <beans:property name="tokenValiditySeconds" value="172800" />
    <beans:property name="parameter" value="remember" />
    <beans:property name="cookieName" value="REMEMBER_ME" />
</beans:bean>

<beans:bean id="rememberMeAuthenticationProvider" class="org.springframework.security.authentication.RememberMeAuthenticationProvider" >
    <beans:property name="key" value="${security.app.key}" />
</beans:bean>

<!-- Way better than SHA -->
<beans:bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder">
    <beans:constructor-arg name="strength" value="15" /> 
</beans:bean>

and this is my java configuration file for spring security:

@Configuration
@EnableWebMvcSecurity
@PropertySource("classpath:app.properties")
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Value("${security.app.key}")
String appKey;

@Autowired
UserService userService;

@Autowired
MongoDashPersistentTokenRepository mongoDashPersistentTokenRepository;

@Autowired
public void registerGlobalAuthentication(AuthenticationManagerBuilder auth) throws Exception {
    auth
        .eraseCredentials(true)
        .authenticationProvider(new MongoDashAuthenticationProvider())
        .authenticationProvider(rememberMeAuthenticationProvider());
        //.build();

    /**
    <authentication-manager alias="authenticationManager">
        <authentication-provider ref="mongoDashAuthenticationProvider" />
        <authentication-provider ref="rememberMeAuthenticationProvider" />
    </authentication-manager>
     */
}

@Override
public void configure(WebSecurity web) throws Exception {
    web
        .ignoring()
            .antMatchers("/assets/**")
            .antMatchers("/css/**")
            .antMatchers("/img/**")
            .antMatchers("/js/**")
            .antMatchers("/fonts/**");
    /**
    <http pattern="/assets/**" security="none" />
    <http pattern="" security="none" />
    <http pattern="" security="none" />
    <http pattern="" security="none" /> 
    **/
}

@Override
protected void configure(HttpSecurity http) throws Exception {
    http    

        .headers()
            .xssProtection()
            .frameOptions()
            .and()
        .authorizeRequests()
            .antMatchers("/login/**").permitAll()
            .antMatchers("/api/**").permitAll()
            .antMatchers("/**").access("isRememberMe() or isFullyAuthenticated()")
            .and()
        .formLogin()
            .loginProcessingUrl("/loginCheck")
            .loginPage("/login")
            //.failureUrl("/loginFailure")
            .defaultSuccessUrl("/", true)
            .passwordParameter("password")
            .usernameParameter("username")
            .permitAll()
            .and()
        .logout()
            .logoutSuccessUrl("/login")
            .logoutUrl("/logout")
            .deleteCookies("JSESSIONID")
            .and()
        .sessionManagement()
            .invalidSessionUrl("/login")
            .sessionFixation().newSession()
            .and()
        .exceptionHandling()
            .accessDeniedPage("/403")
            .and()
        .rememberMe()
            .rememberMeServices(rememberMeServices());


}

@Bean
public RememberMeAuthenticationProvider rememberMeAuthenticationProvider() {
    RememberMeAuthenticationProvider provider = new RememberMeAuthenticationProvider(appKey);
    return provider;

    /**
    <beans:bean id="rememberMeAuthenticationProvider" class="org.springframework.security.authentication.RememberMeAuthenticationProvider" >
        <beans:property name="key" value="${security.app.key}" />
    </beans:bean>
    **/
}

@Bean
public PersistentTokenBasedRememberMeServices rememberMeServices() {
    PersistentTokenBasedRememberMeServices rememberMeServices = new PersistentTokenBasedRememberMeServices(appKey, userService,
            mongoDashPersistentTokenRepository);
    rememberMeServices.setCookieName("REMEMBER_ME");
    rememberMeServices.setParameter("remember");
    rememberMeServices.setTokenValiditySeconds(172800);
    return rememberMeServices;

    /**
    <beans:bean id="rememberMeServices" class="org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices">
        <beans:constructor-arg value="${security.app.key}" />
        <beans:constructor-arg ref="userServiceImpl" />
        <beans:constructor-arg ref="mongoDashPersistentTokenRepository" />
        <beans:property name="tokenValiditySeconds" value="172800" />
        <beans:property name="parameter" value="remember" />
        <beans:property name="cookieName" value="REMEMBER_ME" />
    </beans:bean>
    **/
}

@Bean
public BCryptPasswordEncoder passwordEncoder() {

    BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(15);
    return passwordEncoder;

    /**
    <beans:bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder">
        <beans:constructor-arg name="strength" value="15" /> 
    </beans:bean>
     **/
}
}

And this is my AppConfig

@Configuration
@Import({SecurityConfig.class})
//@ImportResource("classpath:spring/security-context.xml")
@ComponentScan(basePackages = { "com.mongom" }, excludeFilters = {      @ComponentScan.Filter(Controller.class),
    @ComponentScan.Filter(Configuration.class) })
@PropertySource(value = { "classpath:app.properties" })
@EnableMBeanExport
@EnableAspectJAutoProxy
public class AppConfig {

@Bean
public MessageSource messageSource() {
    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    messageSource.setBasenames("validation", "messages");
    return messageSource;
}

@Bean
public PropertiesFactoryBean properties() {
    PropertiesFactoryBean ppc = new PropertiesFactoryBean();
    ppc.setLocations(new Resource[] { new ClassPathResource("app.properties") });
    ppc.setIgnoreResourceNotFound(false);
    return ppc;
}

 }

web.xml

<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

<display-name>MongoDASH</display-name>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

   <context-param>
        <param-name>contextClass</param-name>
    <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>com.mongom.spring.AppConfig</param-value>
</context-param>    

<servlet>
    <servlet-name>mongodash</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextClass</param-name>
        <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
    </init-param>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.mongom.spring.WebConfig</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>


<servlet-mapping>
    <servlet-name>mongodash</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

<!-- Spring Security -->

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

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

<env-entry>
    <description>JNDI logging context for this app</description>
    <env-entry-name>logback/contextName</env-entry-name>
    <env-entry-type>java.lang.String</env-entry-type>
    <env-entry-value>mongom</env-entry-value>
</env-entry>

<welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
</welcome-file-list>

<error-page>
    <error-code>400</error-code>
    <location>/404</location>
</error-page>

<error-page>
    <error-code>404</error-code>
    <location>/404</location>
</error-page>

<error-page>
    <error-code>403</error-code>
    <location>/403</location>
</error-page>

<error-page>
    <error-code>500</error-code>
    <location>/500</location>
</error-page>

Console output:

02/06 11:53:26.678 [http-bio-8080-exec-7] WARN  o.s.web.servlet.PageNotFound Request method 'POST' not supported
02/06 11:53:50.023 [http-bio-8080-exec-5] WARN  o.s.web.servlet.PageNotFound Request method 'POST' not supported
02/06 11:53:53.408 [http-bio-8080-exec-5] WARN  o.s.web.servlet.PageNotFound Request method 'POST' not supported
02/06 11:53:55.504 [http-bio-8080-exec-5] WARN  o.s.web.servlet.PageNotFound Request method 'POST' not supported

Login form:

<form class="form-signin" action="loginCheck" method="POST">
    <h2 class="form-signin-heading">sign in now</h2>
    <div class="login-wrap">
        <input type="text" name="username" class="form-control" placeholder="User ID" autofocus value="guest" />
        <input type="password" name="password" class="form-control" placeholder="Password" value="!Guest2014!" />
        <label class="checkbox">
            <input type="checkbox" name="remember" />Remember me
            <span class="pull-right">
                <a data-toggle="modal" href="#myModal"> Forgot Password?</a>
            </span>
        </label>
        <button class="btn btn-lg btn-login btn-block" type="submit">Sign in</button>
    </div>
 </form>

Spring Security logs:

02/06 15:57:34.202 [localhost-startStop-1] INFO  o.s.s.web.DefaultSecurityFilterChain Creating filter chain: Ant [pattern='/assets/**'], []
02/06 15:57:34.202 [localhost-startStop-1] INFO  o.s.s.web.DefaultSecurityFilterChain Creating filter chain: Ant [pattern='/css/**'], []
02/06 15:57:34.203 [localhost-startStop-1] INFO  o.s.s.web.DefaultSecurityFilterChain Creating filter chain: Ant [pattern='/img/**'], []
02/06 15:57:34.203 [localhost-startStop-1] INFO  o.s.s.web.DefaultSecurityFilterChain Creating filter chain: Ant [pattern='/js/**'], []
02/06 15:57:34.203 [localhost-startStop-1] INFO  o.s.s.web.DefaultSecurityFilterChain Creating filter chain: Ant [pattern='/fonts/**'], []
02/06 15:57:34.264 [localhost-startStop-1] INFO  o.s.s.web.DefaultSecurityFilterChain Creating filter chain: org.springframework.security.web.util.matcher.AnyRequestMatcher@1, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@28562791, org.springframework.security.web.context.SecurityContextPersistenceFilter@1f33b16a, org.springframework.security.web.header.HeaderWriterFilter@12504e0, org.springframework.security.web.csrf.CsrfFilter@7ff12373, org.springframework.security.web.authentication.logout.LogoutFilter@40e9e799, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@334362d9, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@892b7c2, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@d0da1d8, org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter@5eba06ff, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@303fb547, org.springframework.security.web.session.SessionManagementFilter@a5ae1e7, org.springframework.security.web.access.ExceptionTranslationFilter@13883d5f, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@52ecba8]

Spring security longs when posting login form:

    02/06 17:04:33.642 [http-bio-8080-exec-10] DEBUG o.s.s.w.u.m.AntPathRequestMatcher Checking match of request : '/logincheck'; against '/assets/**'
02/06 17:04:33.643 [http-bio-8080-exec-10] DEBUG o.s.s.w.u.m.AntPathRequestMatcher Checking match of request : '/logincheck'; against '/css/**'
02/06 17:04:33.644 [http-bio-8080-exec-10] DEBUG o.s.s.w.u.m.AntPathRequestMatcher Checking match of request : '/logincheck'; against '/img/**'
02/06 17:04:33.644 [http-bio-8080-exec-10] DEBUG o.s.s.w.u.m.AntPathRequestMatcher Checking match of request : '/logincheck'; against '/js/**'
02/06 17:04:33.645 [http-bio-8080-exec-10] DEBUG o.s.s.w.u.m.AntPathRequestMatcher Checking match of request : '/logincheck'; against '/fonts/**'
02/06 17:04:33.645 [http-bio-8080-exec-10] DEBUG o.s.security.web.FilterChainProxy /loginCheck at position 1 of 13 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
02/06 17:04:33.645 [http-bio-8080-exec-10] DEBUG o.s.security.web.FilterChainProxy /loginCheck at position 2 of 13 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
02/06 17:04:33.646 [http-bio-8080-exec-10] DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository HttpSession returned null object for SPRING_SECURITY_CONTEXT
02/06 17:04:33.700 [http-bio-8080-exec-10] DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository No SecurityContext was available from the HttpSession: org.apache.catalina.session.StandardSessionFacade@705ebc6e. A new one will be created.
02/06 17:04:33.701 [http-bio-8080-exec-10] DEBUG o.s.security.web.FilterChainProxy /loginCheck at position 3 of 13 in additional filter chain; firing Filter: 'HeaderWriterFilter'
02/06 17:04:33.701 [http-bio-8080-exec-10] DEBUG o.s.security.web.FilterChainProxy /loginCheck at position 4 of 13 in additional filter chain; firing Filter: 'CsrfFilter'
02/06 17:04:33.702 [http-bio-8080-exec-10] DEBUG o.s.security.web.csrf.CsrfFilter Invalid CSRF token found for http://localhost:8080/mongodash/loginCheck
02/06 17:04:33.703 [http-bio-8080-exec-10] DEBUG o.s.web.servlet.DispatcherServlet DispatcherServlet with name 'mongodash' processing POST request for [/mongodash/403]
02/06 17:04:33.704 [http-bio-8080-exec-10] DEBUG o.s.w.s.m.m.a.RequestMappingHandlerMapping Looking up handler method for path /403
02/06 17:04:33.719 [http-bio-8080-exec-10] DEBUG o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver Resolving exception from handler [null]: org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported
02/06 17:04:33.719 [http-bio-8080-exec-10] DEBUG o.s.w.s.m.a.ResponseStatusExceptionResolver Resolving exception from handler [null]: org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported
02/06 17:04:33.720 [http-bio-8080-exec-10] DEBUG o.s.w.s.m.s.DefaultHandlerExceptionResolver Resolving exception from handler [null]: org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported
02/06 17:04:33.720 [http-bio-8080-exec-10] WARN  o.s.web.servlet.PageNotFound Request method 'POST' not supported
02/06 17:04:33.721 [http-bio-8080-exec-10] DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
02/06 17:04:33.721 [http-bio-8080-exec-10] DEBUG o.s.web.servlet.DispatcherServlet Null ModelAndView returned to DispatcherServlet with name 'mongodash': assuming HandlerAdapter completed request handling
02/06 17:04:33.721 [http-bio-8080-exec-10] DEBUG o.s.web.servlet.DispatcherServlet Successfully completed request
02/06 17:04:33.722 [http-bio-8080-exec-10] DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
02/06 17:04:33.722 [http-bio-8080-exec-10] DEBUG o.s.s.w.c.SecurityContextPersistenceFilter SecurityContextHolder now cleared, as request processing completed

My question is: Am I missing anything here? I see that the application is working fine as I can see the login page and resources (js, css, img) being shown on that page, its just the login-processing-url that, when invoked with POST thru the login form, it doesnt work.

Thank you TL

edit #1: updated with login form and spring security logs

edit #2: updated with spring security logs when posting login information

like image 844
Thiago Avatar asked Feb 06 '14 15:02

Thiago


1 Answers

Based upon the logs you posted, the issue appears to be that you are not including the CSRF token in the login request.

DEBUG o.s.security.web.csrf.CsrfFilter Invalid CSRF token found for http://localhost:8080/mongodash/loginCheck

This did not happen with the XML based configuration, because CSRF is not on by default with XML configuration (to be passive). Since there is no CSRF token in the login request, Spring Security is forwarding to the /403 error page (this is what is configured as the access denied page in your Java Config). It appears the MVC controller that processes /403 does not allow for HTTP POST and is logging the error message you see.

To fix this, first I would ensure that /403 can processes other methods. This will help to troubleshoot any other similar issues you might have (i.e. at that point you should get a proper error message rather than seeing the error that the method is not supported).

Second, you have a choice:

Include the CSRF token in all POST, PUT, DELETE, etc forms. This will ensure you are protected against CSRF attacks.

UPDATE: If you are using @EnableWebMvcSecurity and Spring's JSP tag library or Thymeleaf 2.1+ to render your forms then the CSRF token is included automatically for you. See the Include CSRF Token section of the reference for details about automatic inclusion and for examples of how to include the token manually.

Alternatively, you can disable CSRF protection. This is NOT recommended, but can help be a stop gap if you want to upgrade to Spring Security's Java Configuration.

You can read more about CSRF protection in the CSRF section of the reference.

A few additional items:

Is there a reason you are explicitly configuring the headers? The configuration you have specified disables some of the default headers that will assist in protecting your application. The explicit configuration does not include the cache control header.

UPDATE: For example, you have the following:

http    
    .headers()
        .xssProtection()
        .frameOptions()
        .and()
    .authorizeRequests()

If you simply remove the headers declaration, all the default headers are added by default.

http    
    .authorizeRequests()

For the XML configuration you can include the <headers/> element with no children to get all the default headers. This explicit setup for XML is necessary to remain passive.

More information about the headers can be found in the Security HTTP Response Headers section of the reference.

There is quite a bit of configuration you can safely remove from your code since the Java Configuration will default to these values. For example, the username and password parameter configuration is unnecessary since this is the default value.

like image 61
Rob Winch Avatar answered Oct 14 '22 05:10

Rob Winch