In my Spring Boot application I'm trying to configure OAuth2 implicit flow. For this purpose I'm trying to configure custom login form.
This is my config:
@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
}
}
}
login.html page Thymeleaf template:
<!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>
Maven artifact versions:
<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>
Right now, when I'm trying to access following url:
http://localhost:8080/oauth/authorize?response_type=implicit&client_id=clientapp1
I'm successfully redirected to my login page at http://localhost:8080/login
but when I enter username/password and press "Sign in" button I'm getting following error:
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
What am I doing wrong and how to fix this issue ?
UPDATED
In debug I can see the following output:
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
Also, there is another question about this issue with no answer provided usernamepasswordauthenticationfilter not getting invoked in spring security with oauth2 and formlogin
It was my issue with a wrong configuration of ResourceServer.
With a following configuration everything is working fine:
@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
.antMatcher("/api/**" )
.authorizeRequests().anyRequest().authenticated()
.and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(STATELESS);
// @formatter:on
}
}