Spring Security - 调用login-processing-url时出现错误405

Thi*_*ago 5 spring spring-mvc spring-security

我有一个sprinc核心+ spring mvc +安全启动并运行,使用XML配置,这些是我目前在我的pom.xml中使用的版本:

<spring.version>4.0.1.RELEASE</spring.version>
<spring.security.version>3.2.0.RELEASE</spring.security.version>
Run Code Online (Sandbox Code Playgroud)

所以我决定继续将我的所有xml配置移动到基于java的配置.会发生什么是Spring安全的login-processing-url停止工作,现在给我一个错误405,说不允许POST.例如,这是我的spring security xml文件:

<?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>
Run Code Online (Sandbox Code Playgroud)

这是我的spring安全性配置文件:

@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>
     **/
}
}
Run Code Online (Sandbox Code Playgroud)

这是我的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;
}

 }
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

控制台输出:

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
Run Code Online (Sandbox Code Playgroud)

登录表格:

<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>
Run Code Online (Sandbox Code Playgroud)

Spring安全日志:

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]
Run Code Online (Sandbox Code Playgroud)

发布登录表单时Spring安全性很长:

    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
Run Code Online (Sandbox Code Playgroud)

我的问题是:我在这里错过了什么吗?我看到应用程序工作正常,因为我可以看到登录页面和资源(js,css,img)显示在该页面上,它只是login-processing-url,当通过POST通过登录表单调用时,它不起作用.

谢谢TL

编辑#1:使用登录表单和spring安全日志进行更新

编辑#2:在发布登录信息时使用spring安全日志进行更新

Rob*_*nch 8

根据您发布的日志,问题似乎是您没有在登录请求中包含CSRF令牌.

DEBUG o.s.security.web.csrf.CsrfFilter Invalid CSRF token found for http://localhost:8080/mongodash/loginCheck
Run Code Online (Sandbox Code Playgroud)

基于XML的配置不会发生这种情况,因为默认情况下,CSRF未启用XML配置(被动).由于登录请求中没有CSRF令牌,因此Spring Security将转发到/ 403错误页面(这是在Java Config中配置为拒绝访问页面的内容).似乎处理/ 403的MVC控制器不允许HTTP POST并记录您看到的错误消息.

要解决这个问题,首先我要确保/ 403可以处理其他方法.这将有助于解决您可能遇到的任何其他类似问题(即,此时您应该获得正确的错误消息,而不是看到不支持该方法的错误).

其次,你有一个选择:

在所有POST,PUT,DELETE等表单中包含CSRF令牌.这将确保您免受CSRF攻击.

更新:如果您正在使用@EnableWebMvcSecuritySpring的JSP标记库或Thymeleaf 2.1+来呈现表单,那么将自动包含CSRF令牌.有关自动包含的详细信息以及如何手动包含令牌的示例,请参阅参考的" 包括CSRF令牌"部分.

或者,您可以禁用CSRF保护.不建议这样做,但如果要升级到Spring Security的Java配置,可以帮助缩小差距.

您可以在参考文献CSRF部分中阅读有关CSRF保护的更多信息.

一些额外的项目:

您是否有明确配置标头的原因?您指定的配置会禁用一些有助于保护您的应用程序的默认标头.显式配置不包括缓存控制头.

更新:例如,您有以下内容:

http    
    .headers()
        .xssProtection()
        .frameOptions()
        .and()
    .authorizeRequests()
Run Code Online (Sandbox Code Playgroud)

如果您只是删除标头声明,则默认情况下会添加所有默认标头.

http    
    .authorizeRequests()
Run Code Online (Sandbox Code Playgroud)

对于XML配置,您可以包含<headers/>没有子项的元素来获取所有默认标头.这种显式的XML设置对于保持被动是必要的.

有关标头的更多信息,请参阅参考资料的" 安全HTTP响应标头"部分.

您可以安全地从代码中删除相当多的配置,因为Java配置将默认为这些值.例如,用户名和密码参数配置是不必要的,因为这是默认值.