Spring Boot OAuth2隐式流+表单登录和请求方法'POST'不支持错误

ale*_*oid 2 spring spring-security spring-boot spring-security-oauth2 spring-oauth2

在我的Spring Boot应用程序中,我试图配置OAuth2隐式流。为此,我正在尝试配置自定义登录表单。

这是我的配置:

@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/login").setViewName("login");
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
    }

}

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private SocialAuthenticationSuccessHandler socialAuthenticationSuccessHandler;

    @Autowired
    private DBUserDetailsService userDetailsService;

    @Value("${social.postLogin.url}")
    private String postLoginUrl;

    @Override
    public void configure(WebSecurity web) throws Exception {
        // Spring Security ignores request to static resources such as CSS or JS
        // files.
        web.ignoring().antMatchers("/static/**");
    }


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

        // @formatter:off
        http.addFilterBefore(new CorsFilter(), ChannelProcessingFilter.class);

        // Set a custom successHandler on the SocialAuthenticationFilter
        final SpringSocialConfigurer socialConfigurer = new SpringSocialConfigurer();
        socialConfigurer.addObjectPostProcessor(new ObjectPostProcessor<SocialAuthenticationFilter>() {
            @Override
            public <O extends SocialAuthenticationFilter> O postProcess(O socialAuthenticationFilter) {
                socialAuthenticationFilter.setAuthenticationSuccessHandler(socialAuthenticationSuccessHandler);
                socialAuthenticationFilter.setPostLoginUrl(postLoginUrl);
                return socialAuthenticationFilter;
            }
        });

        http
            .csrf().disable()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
            .authorizeRequests()

            .antMatchers("/oauth/authorize").authenticated()
            //Anyone can access the urls
            .antMatchers("/auth/**").permitAll()
            .antMatchers("/actuator/health").permitAll()
            .antMatchers("/actuator/**").hasAuthority("PERMISSION_READ_ACTUATOR_DATA")
            .antMatchers("/login").permitAll()
            .anyRequest().authenticated()
        .and()
            .formLogin()
                .loginPage("/login")
                .loginProcessingUrl("/login")
                .usernameParameter("username")
                .passwordParameter("password")
                .permitAll()
        //Adds the SocialAuthenticationFilter to Spring Security's filter chain.
        .and()
            // apply the configuration from the socialConfigurer (adds the SocialAuthenticationFilter)
            .apply(socialConfigurer);
        // @formatter:on
    }

    /**
     * Configures the authentication manager bean which processes authentication
     * requests.
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }

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

}

@Configuration
public class OAuth2ServerConfig {

    private static final String RESOURCE_ID = "restservice";

    @Autowired
    private DBUserDetailsService userDetailsService;

    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter() {

            @Override
            public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
                DBUserDetails user = (DBUserDetails) authentication.getUserAuthentication().getPrincipal();
                final Map<String, Object> additionalInfo = new HashMap<>();
                additionalInfo.put("user_id", user.getUser().getId());
                ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
                OAuth2AccessToken enhancedToken = super.enhance(accessToken, authentication);
                return enhancedToken;
            }

        };

        converter.setSigningKey("123");

        DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
        DefaultUserAuthenticationConverter userTokenConverter = new DefaultUserAuthenticationConverter();
        userTokenConverter.setUserDetailsService(userDetailsService);
        accessTokenConverter.setUserTokenConverter(userTokenConverter);

        converter.setAccessTokenConverter(accessTokenConverter);

        return converter;
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Configuration
    @EnableAuthorizationServer
    protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

        @Autowired
        @Qualifier("authenticationManagerBean")
        private AuthenticationManager authenticationManager;

        @Autowired
        private TokenStore tokenStore;

        @Autowired
        private TokenEnhancer tokenEnhancer;

        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            // @formatter:off
            endpoints
                .tokenStore(tokenStore)
                .tokenEnhancer(tokenEnhancer)
                .authenticationManager(this.authenticationManager);
            // @formatter:on
        }

        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            // @formatter:off
            clients
                .inMemory()
                    .withClient("clientapp")
                        .authorizedGrantTypes("password","refresh_token")
                        //.authorizedGrantTypes("implicit")
                        .authorities("ROLE_CLIENT")
                        .scopes("read", "write")
                        .resourceIds(RESOURCE_ID)
                        .secret("123456")
                    .and()
                    .withClient("clientapp1")
                        .authorizedGrantTypes("implicit")
                        .scopes("read", "write")
                        .autoApprove(true);
            // @formatter:on
        }

    }

    @Configuration
    @EnableResourceServer
    protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

        @Autowired
        private ResourceServerTokenServices tokenService;

        @Override
        public void configure(ResourceServerSecurityConfigurer resources) {
            // @formatter:off
            resources           
                .resourceId(RESOURCE_ID)
                .tokenServices(tokenService);
            // @formatter:on
        }

        @Override
        public void configure(HttpSecurity http) throws Exception {
            // @formatter:off
            http
                .authorizeRequests()
                    .antMatchers("/profile/*").authenticated()
                    .and().csrf()
                    .disable().sessionManagement().sessionCreationPolicy(STATELESS);
            // @formatter:on
        }

    }

}
Run Code Online (Sandbox Code Playgroud)

login.html页面Thymeleaf模板:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
    <head>
        <title>Spring Security Example </title>
    </head>
    <body>
        <div th:if="${param.error}">
            Invalid username and password.
        </div>
        <div th:if="${param.logout}">
            You have been logged out.
        </div>
        <form th:action="@{/login}" method="post">
            <div><label> User Name : <input type="text" name="username"/> </label></div>
            <div><label> Password: <input type="password" name="password"/> </label></div>
            <div><input type="submit" value="Sign In"/></div>
        </form>
    </body>
</html>
Run Code Online (Sandbox Code Playgroud)

Maven工件版本:

<spring.boot.version>1.4.0.RELEASE</spring.boot.version>
<spring-security-core.version>4.1.3.RELEASE</spring-security-core.version>
<spring-security-oauth2.version>2.0.11.RELEASE</spring-security-oauth2.version>
Run Code Online (Sandbox Code Playgroud)

现在,当我尝试访问以下网址时:

http://localhost:8080/oauth/authorize?response_type=implicit&client_id=clientapp1
Run Code Online (Sandbox Code Playgroud)

我已成功重定向到的登录页面,http://localhost:8080/login但是当我输入用户名/密码并按“登录”按钮时,出现以下错误:

Whitelabel Error Page

This application has no explicit mapping for /error, so you are seeing this as a fallback.
Sat Sep 24 21:19:44 EEST 2016
There was an unexpected error (type=Method Not Allowed, status=405).
Request method 'POST' not supported
Run Code Online (Sandbox Code Playgroud)

我在做什么错以及如何解决此问题?

更新

在调试中,我可以看到以下输出:

DispatcherServlet with name 'dispatcherServlet' processing POST request for [/login]
2016-09-25 10:04:43 [http-nio-8080-exec-2] DEBUG o.s.w.s.m.m.a.RequestMappingHandlerMapping -
                Looking up handler method for path /login
2016-09-25 10:04:43 [http-nio-8080-exec-2] DEBUG o.s.w.s.m.m.a.RequestMappingHandlerMapping -
                Did not find handler method for [/login]
2016-09-25 10:04:43 [http-nio-8080-exec-2] DEBUG o.s.w.s.h.SimpleUrlHandlerMapping -
                Mapping [/login] to HandlerExecutionChain with handler [org.springframework.web.servlet.mvc.ParameterizableViewController@c85e70] and 1 interceptor
2016-09-25 10:04:43 [http-nio-8080-exec-2] DEBUG o.s.w.s.m.a.ResponseStatusExceptionResolver -
                Resolving exception from handler [org.springframework.web.servlet.mvc.ParameterizableViewController@c85e70]: org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported
2016-09-25 10:04:43 [http-nio-8080-exec-2] DEBUG o.s.w.s.m.s.DefaultHandlerExceptionResolver -
                Resolving exception from handler [org.springframework.web.servlet.mvc.ParameterizableViewController@c85e70]: org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported
2016-09-25 10:04:43 [http-nio-8080-exec-2] WARN  o.s.web.servlet.PageNotFound -
                Request method 'POST' not supported
2016-09-25 10:04:43 [http-nio-8080-exec-2] DEBUG o.s.s.w.h.writers.HstsHeaderWriter -
                Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@16580a4
2016-09-25 10:04:43 [http-nio-8080-exec-2] DEBUG o.s.web.servlet.DispatcherServlet -
                Null ModelAndView returned to DispatcherServlet with name 'dispatcherServlet': assuming HandlerAdapter completed request handling
2016-09-25 10:04:43 [http-nio-8080-exec-2] DEBUG o.s.web.servlet.DispatcherServlet -
                Successfully completed request
Run Code Online (Sandbox Code Playgroud)

另外,还有一个关于此问题的问题,没有提供答案,用户名passwordauthenticationfilter在osecurity2和formlogin中未在Spring Security中被调用

Tre*_*vor 5

今天我遇到了同样的问题,您的回答并没有帮助我,但我找到了解决问题的方法。

希望这可以帮助处于相同位置的其他人。

我的应用程序的问题在于,我正在使用基本身份验证来覆盖大多数Web应用程序受保护的资源,但我正在使用OAuth2,以保护允许人们连接的公共API。

因此,这导致我的应用程序public void configure(HttpSecurity http)在两个单独的配置文件中具有两个单独的方法。

解决方案是Order在配置类中添加注释。

因此,我使用Basic Auth的主要配置类已Order(1)分配给它,并且为它分配ResourceServerConfigurerAdapter了一个Order(2)注释。

例如:

@EnableWebSecurity
@Order(1)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{
  /// ... my normal configuration information
  protected void configure(HttpSecurity http) {
    // my basic auth config
  }
}

@Configuration
@Order(2)
public class OAuth2ServerConfig
{
  @Configuration
  @EnableResourceServer
  @Order(3)
  protected static class Oauth2ServerConfig extends ResourceServerConfigurerAdapter
  {
    @Override
    public void configure(HttpSecurity http) throws Exception
    {
      http.authorizeRequests()
      .antMatchers("/oauth/**").permitAll()
      .antMatchers("/api/v1/**").access("#oauth2.hasScope('read')");
    }
  }


  @Configuration
  @EnableAuthorizationServer
  protected static class AuthorizationServer extends AuthorizationServerConfigurerAdapter
  {
    // authorization server settings
  }
}
Run Code Online (Sandbox Code Playgroud)