Spring Boot with WSO2 OAuth2 - logoutSuccessUrl is not working - spring-boot

We are implementing the service using Spring Boot (1.2.2) with WSO2 IS (5.x) as OAuth2 Identity provider.
As part of logout we wanted to run some cleaning activities and then redirect to login page. Following is the Spring Security Configuration class
#Configuration
#EnableWebMvcSecurity
public class SecurityConfiguration extends OAuth2SsoConfigurerAdapter {
private final Logger log = LoggerFactory.getLogger(this.getClass());
#Autowired
private OAuth2ClientContext oAuth2ClientContext;
#Autowired
private MmaLogoutHandler logoutHandler;
#Override
public void match(RequestMatchers matchers) {
matchers.anyRequest();
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logoff", "GET"))
.logoutSuccessUrl("/#/home")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID","remember-me")
.permitAll()
.and()
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class)
.addFilterBefore(customerAuthorizationFilter(), AbstractPreAuthenticatedProcessingFilter.class);
}
When the logout is called, the log shows that spring security has cleared the session, but it fails to redirect -
2015-09-01 14:23:05.983 DEBUG 12936 --- [nio-8443-exec-8] o.s.s.w.a.logout.LogoutFilter : Logging out user 'org.springframework.security.oauth2.provider.OAuth2Authentication#e6269aa6: Principal: mrpink_bd3d5b71-b212-11e4-ac24-22000b4791d2; Credentials: [PROTECTED]; Authenticated: true; Details: remoteAddress=127.0.0.1, sessionId=<SESSION>, tokenType=bearertokenValue=<TOKEN>; Granted Authorities: ROLE_USER' and transferring to logout destination
2015-09-01 14:23:05.984 DEBUG 12936 --- [nio-8443-exec-8] o.s.s.w.a.l.SecurityContextLogoutHandler : Invalidating session: B7B50DC106F50EDA84ACFEF229DE167B
2015-09-01 14:23:05.984 DEBUG 12936 --- [nio-8443-exec-8] .s.s.w.a.l.SimpleUrlLogoutSuccessHandler : Using default Url: /#/home
2015-09-01 14:23:05.984 DEBUG 12936 --- [nio-8443-exec-8] .s.s.w.a.l.SimpleUrlLogoutSuccessHandler : Response has already been committed. Unable to redirect to /#/home
2015-09-01 14:23:05.984 DEBUG 12936 --- [nio-8443-exec-8] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
As per log the redirection fails and the page is not actually redirected.
Is this due to OAuth2 or there is already default log out handler with Spring boot that is needed to be overridden?

Related

Upgrading to Spring-Security 5.7.0 SecurityContextPersistenceFilter not called

With WebSecurityConfigurerAdapter being deprecated in Spring-security 5.7.0 we are trying to migrate to the newer way to configuring securityFilterChain but in doing so i noticed in spring debug log that the SecurityContextPersistenceFilter isnt invoked. As a result when testing controllers with a requestPostProcessor the authentication set within the requestPostProcessor doesnt get applied to the HttpSession when the request is being authenticated.
Logs post the version upgrade
[main] DEBUG org.springframework.security.web.context.HttpSessionSecurityContextRepository - Created HttpSession as SecurityContext is non-default
[main] DEBUG org.springframework.security.web.context.HttpSessionSecurityContextRepository - Stored SecurityContextImpl [Authentication=TestAuthenticationToken [Principal=ApiUser [Username=USER, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[placeholder]], Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[]]] to HttpSession [org.springframework.mock.web.MockHttpSession#153d14e3]
[main] DEBUG org.springframework.test.web.servlet.TestDispatcherServlet - POST "/v1/api_path”, parameters={}
[main] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped to com.xyz.Controller#controllerMethod(String, List)
Logs before the version upgrade
HttpSession as SecurityContext is non-default
[main] DEBUG org.springframework.security.web.context.HttpSessionSecurityContextRepository - Stored SecurityContextImpl [Authentication=TestAuthenticationToken [Principal=ApiUser [Username=USERNAME, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[placeholder]], Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[]]] to HttpSession [org.springframework.mock.web.MockHttpSession#d641499]
[main] DEBUG org.springframework.security.web.FilterChainProxy - Securing POST /v1/api_path
[main] DEBUG org.springframework.security.web.context.HttpSessionSecurityContextRepository - Retrieved SecurityContextImpl [Authentication=TestAuthenticationToken [Principal=ApiUser [Username=USERNAME, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[placeholder]], Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[]]]
[main] DEBUG org.springframework.security.web.context.SecurityContextPersistenceFilter - Set SecurityContextHolder to SecurityContextImpl [Authentication=TestAuthenticationToken [Principal=ApiUser [Username=USERNAME, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[placeholder]], Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[]]]
[main] DEBUG org.springframework.security.web.access.intercept.FilterSecurityInterceptor - Authorized filter invocation [POST /v1/api_path] with attributes [authenticated]
[main] DEBUG org.springframework.security.web.FilterChainProxy - Secured POST /v1/api_path
[main] DEBUG org.springframework.test.web.servlet.TestDispatcherServlet - POST "/v1/api”_path, parameters={}
[main] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping - Mapped to com.Controller#ControllerMethod(String, List)
Here's the code changes made to the SecurityConfig
NEW CODE
/**
* Configure in memory authentication with the default username/password.
* #return InMemoryUserDetailsManager {#link InMemoryUserDetailsManager}
*/
#Bean
public InMemoryUserDetailsManager configureAuthentication() {
final UserDetails userDetails = new User(DEFAULT_USERNAME, DEFAULT_PASSWORD, authorities(DEFAULT_ROLES));
return new InMemoryUserDetailsManager(userDetails);
}
/**
* Security Filter chain for Http requests.
* #param http HttpSecurity
* #return SecurityFilterChain for Http requests
*/
#Bean
public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception {
http.authorizeRequests(auth ->
auth.anyRequest().authenticated())
.httpBasic()
.and()
.csrf().disable();
return http.build();
}
/**
* Set the default ignore everything on the security context.
* #return WebSecurityCustomizer - used to customize WebSecurity
*/
#Bean
public WebSecurityCustomizer ignoringCustomizer() {
return web -> web.ignoring().antMatchers("/**");
}
OLD CODE
#Autowired
public void configureGlobal(final AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser(DEFAULT_USERNAME)
.password(DEFAULT_PASSWORD)
.roles(DEFAULT_ROLES.toArray(new String[0]));
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic()
.and()
.csrf().disable();
}
/**
* Set the default ignore everything on the security context.
*
* #param web {#link WebSecurity}.
*/
protected static void setIgnoreEverything(final WebSecurity web) {
web.ignoring().antMatchers("/**");
}
Realized in debugging that in the legacy code there were duplicate security filter chains but the order of their execution had been flipped and so the securityfilterchain with antpatter /** was being executed first and hence bypassing the securityContextpersistencefilter.
Solution was to remove the /** antpattern since it wasnt serving any purpose.

Spring with two security configurations - failed API login redirects to form login page. How to change?

I have a Spring Boot application with two security configurations (two WebSecurityConfigurerAdapters), one for a REST API with "/api/**" endpoints, and one for a web front-end at all other endpoints. The security configuration is here on Github and here's some relevant parts of it:
#Configuration
#Order(1)
public static class APISecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
JWTAuthenticationFilter jwtAuthenticationFilter = new JWTAuthenticationFilter(authenticationManager());
jwtAuthenticationFilter.setFilterProcessesUrl("/api/login");
jwtAuthenticationFilter.setPostOnly(true);
http.antMatcher("/api/**")
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilter(jwtAuthenticationFilter)
.addFilter(new JWTAuthorizationFilter(authenticationManager()));
}
}
#Configuration
public static class FrontEndSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginPage("/login").permitAll()
.defaultSuccessUrl("/")
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/?logout")
.and()
.authorizeRequests()
.mvcMatchers("/").permitAll()
.mvcMatchers("/home").authenticated()
.anyRequest().denyAll()
.and();
}
}
The JWTAuthenticationFilter is a custom subclass of UsernamePasswordAuthenticationFilter that processes sign-in attempts to the REST API (by HTTP POST with a JSON body to /api/login) and returns a JWT token in the "Authorization" header if successful.
So here's the issue: failed login attempts to /api/login (either with bad credentials or missing JSON body) are redirecting to the HTML login form /api/login. Non-authenticated requests to other "/api/**" endpoints result in a simple JSON response such as:
{
"timestamp": "2019-11-22T21:03:07.892+0000",
"status": 403,
"error": "Forbidden",
"message": "Access Denied",
"path": "/api/v1/agency"
}
{
"timestamp": "2019-11-22T21:04:46.663+0000",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/api/v1/badlink"
}
Attempts to access other protected URLs (not starting with "/api/") by a non-authenticated user redirect to the login form /login, which is the desired behavior. But I don't want API calls to /api/login to redirect to that form!
How can I code the correct behavior for failed API logins? Is it a question of adding a new handler for that filter? Or maybe adding an exclusion to some behavior I've already defined?
More detail on the exception and handling:
I looked at the logs, and the exception being thrown for either bad credentials or malformed JSON is a subclass of org.springframework.security.authentication.AuthenticationException. The logs show, for example:
webapp_1 | 2019-11-25 15:30:16.048 DEBUG 1 --- [nio-8080-exec-5] n.j.w.g.config.JWTAuthenticationFilter : Authentication request failed: org.springframework.security.authentication.BadCredentialsException: Bad credentials
(...stack trace...)
webapp_1 | 2019-11-25 15:30:16.049 DEBUG 1 --- [nio-8080-exec-5] n.j.w.g.config.JWTAuthenticationFilter : Updated SecurityContextHolder to contain null Authentication
webapp_1 | 2019-11-25 15:30:16.049 DEBUG 1 --- [nio-8080-exec-5] n.j.w.g.config.JWTAuthenticationFilter : Delegating to authentication failure handler org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler#7f9648b6
webapp_1 | 2019-11-25 15:30:16.133 DEBUG 1 --- [nio-8080-exec-6] n.j.webapps.granite.home.HomeController : Accessing /login page.
When I access another URL, for example one that doesn't exist such as /api/x, it looks very different. It's pretty verbose but it looks like the server is trying to redirect to /error and is not finding that to be an authorized URL. Interestingly if I try this in a web browser I get the error formatted with my custom error page (error.html), but if I access it with Postman I just get a JSON message. A sample of the logs:
webapp_1 | 2019-11-25 16:07:22.157 DEBUG 1 --- [nio-8080-exec-6] o.s.s.w.a.ExceptionTranslationFilter : Access is denied (user is anonymous); redirecting to authentication entry point
webapp_1 |
webapp_1 | org.springframework.security.access.AccessDeniedException: Access is denied
...
webapp_1 | 2019-11-25 16:07:22.174 DEBUG 1 --- [nio-8080-exec-6] o.s.s.w.a.ExceptionTranslationFilter : Calling Authentication entry point.
webapp_1 | 2019-11-25 16:07:22.175 DEBUG 1 --- [nio-8080-exec-6] o.s.s.w.a.Http403ForbiddenEntryPoint : Pre-authenticated entry point called. Rejecting access
...
webapp_1 | 2019-11-25 16:07:22.211 DEBUG 1 --- [nio-8080-exec-6] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/error'; against '/api/**'
...
webapp_1 | 2019-11-25 16:07:22.214 DEBUG 1 --- [nio-8080-exec-6] o.s.security.web.FilterChainProxy : /error reached end of additional filter chain; proceeding with original chain
webapp_1 | 2019-11-25 16:07:22.226 DEBUG 1 --- [nio-8080-exec-6] o.s.web.servlet.DispatcherServlet : "ERROR" dispatch for GET "/error", parameters={}
webapp_1 | 2019-11-25 16:07:22.230 DEBUG 1 --- [nio-8080-exec-6] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
webapp_1 | 2019-11-25 16:07:22.564 DEBUG 1 --- [nio-8080-exec-6] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/json', given [*/*] and supported [application/json, application/*+json, application/json, application/*+json]
webapp_1 | 2019-11-25 16:07:22.577 DEBUG 1 --- [nio-8080-exec-6] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [{timestamp=Mon Nov 25 16:07:22 GMT 2019, status=403, error=Forbidden, message=Access Denied, path=/a (truncated)...]
webapp_1 | 2019-11-25 16:07:22.903 DEBUG 1 --- [nio-8080-exec-6] w.c.HttpSessionSecurityContextRepository : SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
webapp_1 | 2019-11-25 16:07:22.905 DEBUG 1 --- [nio-8080-exec-6] o.s.web.servlet.DispatcherServlet : Exiting from "ERROR" dispatch, status 403
So it looks like what I maybe need to do is to configure the "authentication failure handler" for the REST API to go to "/error" instead of going to "/login", but only for endpoints under /api/**.
I added an #Override of unsuccessfulAuthentication() to my Authentication filter
The grandparent class (AbstractAuthenticationProcessingFilter) has a method for unsuccessful authentications (i.e. AuthenticationException) which delegates to an authentication failure handler class. I could have created my own custom authentication failure handler, but instead decided to simply override the unsuccessfulAuthentication method with some code that sends back a response with a 401 status and a JSON error message:
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
// TODO: enrich/improve error messages
response.setStatus(response.SC_UNAUTHORIZED);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
response.getWriter().write("{\"error\": \"authentication error?\"}");
}
...and added a custom AuthenticationEntryPoint to make the other errors match
This doesn't have the exact form of the error messages I had seen at other endpoints, so I also created a custom AuthenticationEntryPoint (the class that handles unauthorized requests to protected endpoints) and it does basically the same thing. My implementation:
public class RESTAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
// TODO: enrich/improve error messages
response.setStatus(response.SC_UNAUTHORIZED);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
response.getWriter().write("{\"error\": \"unauthorized?\"}");
}
}
Now the security configuration for the REST endpoints looks like this (note the addition of ".exceptionHandling().authenticationEntryPoint()":
#Configuration
#Order(1)
public static class APISecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
JWTAuthenticationFilter jwtAuthenticationFilter = new JWTAuthenticationFilter(authenticationManager());
jwtAuthenticationFilter.setFilterProcessesUrl("/api/login");
http.antMatcher("/api/**")
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(new RESTAuthenticationEntryPoint())
.and()
.addFilter(jwtAuthenticationFilter)
.addFilter(new JWTAuthorizationFilter(authenticationManager()));
}
}
I'll need to work on my error messages so they're more informative and secure.
And this doesn't exactly answer my original question, which was how to get those default-style JSON responses I liked, but it does allow me to customize the errors from all types of access failures independently of the web configuration. (Endpoints outside of /api/** still work with the web form login.)
Full code as of the current commit: github

Spring boot client fails to register Spring boot admin (version 2.x)

I am running spring boot application with basic authentication enabled and spring boot admin with UI security enabled .
My spring boot client fails to register to the spring boot admin server . Below are the logs :
2018-08-23 15:17:09.676 DEBUG 4992 --- [gistrationTask1] o.s.web.client.RestTemplate : Created POST request for "http://localhost:9001/instances"
2018-08-23 15:17:09.699 DEBUG 4992 --- [gistrationTask1] o.s.web.client.RestTemplate : Setting request Accept header to [application/json, application/*+json]
2018-08-23 15:17:09.724 DEBUG 4992 --- [gistrationTask1] o.s.web.client.RestTemplate : Writing [Application(name=spring-boot-application, managementUrl=http://localhost:8001/relay/actuator, healthUrl=http://localhost:8001/relay/actuator/health, serviceUrl=http://localhost:8001/relay)] as "application/json" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter#12c0c0b3]
2018-08-23 15:17:09.864 DEBUG 4992 --- [gistrationTask1] s.n.www.protocol.http.HttpURLConnection : sun.net.www.MessageHeader#5953ec3b8 pairs: {POST /instances HTTP/1.1: null}{Accept: application/json}{Content-Type: application/json}{Authorization: Basic YWRtaW46YWRtaW4=}{User-Agent: Java/1.8.0_161}{Host: localhost:9001}{Connection: keep-alive}{Content-Length: 287}
2018-08-23 15:17:10.124 DEBUG 4992 --- [gistrationTask1] s.n.www.protocol.http.HttpURLConnection : sun.net.www.MessageHeader#126c0bea10 pairs: {null: HTTP/1.1 403}{Cache-Control: no-cache, no-store, max-age=0, must-revalidate}{Pragma: no-cache}{Expires: 0}{X-Content-Type-Options: nosniff}{X-Frame-Options: DENY}{X-XSS-Protection: 1 ; mode=block}{Content-Type: text/plain}{Transfer-Encoding: chunked}{Date: Thu, 23 Aug 2018 09:47:10 GMT}
2018-08-23 15:17:10.128 DEBUG 4992 --- [gistrationTask1] o.s.web.client.RestTemplate : POST request for "http://localhost:9001/instances" resulted in 403 (null); invoking error handler
2018-08-23 15:17:10.138 WARN 4992 --- [gistrationTask1] d.c.b.a.c.r.ApplicationRegistrator : Failed to register application as Application(name=spring-boot-application, managementUrl=http://localhost:8001/relay/actuator, healthUrl=http://localhost:8001/relay/actuator/health, serviceUrl=http://localhost:8001/relay) at spring-boot-admin ([http://localhost:9001/instances]): 403 null. Further attempts are logged on DEBUG level
Below are my webSecurity config class from admin server
#SpringBootApplication
#EnableAdminServer
#EnableAutoConfiguration
#Configuration
public class SpringStarterAdminApplication {
public static void main(String[] args) {
SpringApplication.run(SpringStarterAdminApplication.class, args);
}
// Added for spring boot security login ui for admin
public static class SecuritySecureConfig extends WebSecurityConfigurerAdapter {
private final String adminContextPath;
public SecuritySecureConfig(AdminServerProperties adminServerProperties) {
this.adminContextPath = adminServerProperties.getContextPath();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
successHandler.setTargetUrlParameter("redirectTo");
successHandler.setDefaultTargetUrl(adminContextPath + "/");
http.authorizeRequests()
.antMatchers(adminContextPath + "/assets/**").permitAll()
.antMatchers(adminContextPath + "/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage(adminContextPath + "/login").successHandler(successHandler).and()
.logout().logoutUrl(adminContextPath + "/logout").and()
.httpBasic().and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.ignoringAntMatchers(
"/instances",
"/actuator/**"
);
// #formatter:on
}
}
Not sure what is preventing the client to register to admin Server .
The inner configuration class should be annotated with #Configuration too.
It's even better if you refactor it into a separate class.

Migration to Spring Boot 2 from 1.5.7 - Request method POST not supported - csrf already disabled

We've migrated our software from spring boot 1.5.7 to spring boot 2.
We're using JSF by including joinfaces-parent in our pom.xml.
At the startup, all works perfectly, but login call does not work:
Request method 'POST' not supported
It is probably a Spring Security issue? CSRF is already disabled.
Here's our SecurityConfig file:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
#Override
protected void configure(HttpSecurity http) {
try {
http.csrf().disable().authorizeRequests()
.antMatchers("/javax.faces.resource/**", Page.LOGIN.getUrlForSecurityContext())
.permitAll()
.and()
........
// *** login configuration
.formLogin()
.loginPage(Page.LOGIN.getUrlForSecurityContext()).permitAll()
.failureUrl(Page.LOGIN.getUrlForSecurityContext() + "?error=true")
.usernameParameter("username")
.passwordParameter("password")
.successHandler(authenticationSuccessHandler)
.and()
...........
// #formatter:on
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
.......
}
The login request does not arrives to our backend.
I found out that this error is generated from the dispatcher.forward function, called from xhtml. Here the function:
public void login() throws ServletException, IOException {
final ExternalContext context = FacesContext.getCurrentInstance().getExternalContext();
final RequestDispatcher dispatcher = ((ServletRequest) context.getRequest()).getRequestDispatcher("/login");
dispatcher.forward((ServletRequest) context.getRequest(), (ServletResponse) context.getResponse());
FacesContext.getCurrentInstance().responseComplete();
}
Here more logs when the error message happens:
[io.undertow.servlet] (default task-3) Initializing Spring FrameworkServlet 'dispatcherServlet'
16:02:20,926 INFO [org.springframework.web.servlet.DispatcherServlet] (default task-3) FrameworkServlet 'dispatcherServlet': initialization started
16:02:20,938 INFO [org.springframework.web.servlet.DispatcherServlet] (default task-3) FrameworkServlet 'dispatcherServlet': initialization completed in 12 ms
16:02:20,949 WARN [org.springframework.web.servlet.PageNotFound] (default task-3) Request method 'POST' not supported
16:02:20,973 ERROR [org.springframework.boot.web.servlet.support.ErrorPageFilter] (default task-3) Cannot forward to error page for request [/login] as the response has already been committed. As a result, the response may have the wrong status code. If your application is running on WebSphere Application Server you may be able to resolve this problem by setting com.ibm.ws.webcontainer.invokeFlushAfterService to false
Thanks in advice!
Spring Security configuration looks ok for me. There is something wrong with your login controller. I suppose your login method is called in response to POST request from the client. Then it tries to forward this POST to render login page and finally throws an exception. Obviously it should be GET request instead of POST.

Spring Boot: Oauth2: Access is denied (user is anonymous); redirecting to authentication entry point

I am trying to use spring boot oauth2 to accomplish stateless authentication and authorisation. However, I am struggling to it working.
Here is my code:
#EnableAutoConfiguration
#ComponentScan
//#EnableEurekaClient
//#EnableZuulProxy
#Configuration
public class AuthServiceApp {
public static void main(String[] args) {
SpringApplication.run(AuthServiceApp.class, args);
}
}
Authorisation Config:
#Configuration
#EnableAuthorizationServer
public class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager auth;
#Autowired
private DataSource dataSource;
#Autowired
private CustomUserDetailsService userDetailService;
#Autowired
private ClientDetailsService clientDetailsService;
#Bean
public JdbcTokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
#Bean
protected AuthorizationCodeServices authorizationCodeServices() {
return new JdbcAuthorizationCodeServices(dataSource);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// #OFF
endpoints
.authorizationCodeServices(authorizationCodeServices())
.authenticationManager(auth)
.userDetailsService(userDetailService)
.tokenStore(tokenStore());
// #ON
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// #OFF
clients.jdbc(dataSource)
.withClient("client")
.secret("secret")
.authorizedGrantTypes("password","refresh_token", "client_credentials")
.authorities("USER")
.scopes("read", "write")
.autoApprove(true)
.accessTokenValiditySeconds(60)
.refreshTokenValiditySeconds(300);
// #ON
}
}
Resource Server Config:
#Configuration
#EnableResourceServer
#EnableGlobalMethodSecurity(prePostEnabled = true)
class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Autowired
private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
#Autowired
private CustomLogoutSuccessHandler customLogoutSuccessHandler;
#Override
public void configure(HttpSecurity http) throws Exception {
// #OFF
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling()
.authenticationEntryPoint(customAuthenticationEntryPoint)
.and()
.logout()
.logoutUrl("/oauth/logout")
.logoutSuccessHandler(customLogoutSuccessHandler)
.and()
.csrf()
// .requireCsrfProtectionMatcher(new AntPathRequestMatcher("/oauth/authorize"))
.disable()
.headers()
.frameOptions().disable()
.and()
.authorizeRequests()
.antMatchers("/identity/**").authenticated();
// #ON
}
}
#Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// #OFF
http
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().permitAll();
// #ON
}
}
Controller:
#RestController
#RequestMapping("/")
public class AuthController {
#PreAuthorize("#oauth2.hasScope('read')")
#GetMapping("/user")
public Principal getUser(Principal user) {
return user;
}
}
I can get the access token using POSTMAN. I am using the same access token in the header to get the user details as http://localhost:8082/identity/user before it gets expired. However, I get login page html response with following log on console:
2017-05-24 22:55:16.070 DEBUG 16899 --- [nio-8082-exec-9] o.s.s.w.a.AnonymousAuthenticationFilter : Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken#9055c2bc: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 301C6EDD36372CF9C553FCFCD4AA47E3; Granted Authorities: ROLE_ANONYMOUS'
2017-05-24 22:55:16.070 DEBUG 16899 --- [nio-8082-exec-9] o.s.security.web.FilterChainProxy : /user at position 10 of 12 in additional filter chain; firing Filter: 'SessionManagementFilter'
2017-05-24 22:55:16.070 DEBUG 16899 --- [nio-8082-exec-9] o.s.security.web.FilterChainProxy : /user at position 11 of 12 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
2017-05-24 22:55:16.070 DEBUG 16899 --- [nio-8082-exec-9] o.s.security.web.FilterChainProxy : /user at position 12 of 12 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
2017-05-24 22:55:16.070 DEBUG 16899 --- [nio-8082-exec-9] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/user'; against '/login'
2017-05-24 22:55:16.070 DEBUG 16899 --- [nio-8082-exec-9] o.s.s.w.a.i.FilterSecurityInterceptor : Secure object: FilterInvocation: URL: /user; Attributes: [authenticated]
2017-05-24 22:55:16.071 DEBUG 16899 --- [nio-8082-exec-9] o.s.s.w.a.i.FilterSecurityInterceptor : Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken#9055c2bc: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 301C6EDD36372CF9C553FCFCD4AA47E3; Granted Authorities: ROLE_ANONYMOUS
2017-05-24 22:55:16.071 DEBUG 16899 --- [nio-8082-exec-9] o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.web.access.expression.WebExpressionVoter#55b4f25d, returned: -1
2017-05-24 22:55:16.071 DEBUG 16899 --- [nio-8082-exec-9] o.s.s.w.a.ExceptionTranslationFilter : Access is denied (user is anonymous); redirecting to authentication entry point
org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:84) ~[spring-security-core-4.2.2.RELEASE.jar:4.2.2.RELEASE]
But it seems like I have been authenticated when making first call to get the access token to oauth/token:
2017-05-24 22:54:35.966 DEBUG 16899 --- [nio-8082-exec-6] o.s.s.w.a.i.FilterSecurityInterceptor : Secure object: FilterInvocation: URL: /oauth/token; Attributes: [fullyAuthenticated]
2017-05-24 22:54:35.966 DEBUG 16899 --- [nio-8082-exec-6] o.s.s.w.a.i.FilterSecurityInterceptor : Previously Authenticated: org.springframework.security.authentication.UsernamePasswordAuthenticationToken#50c8f5e8: Principal: org.springframework.security.core.userdetails.User#af12f3cb: Username: client; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#21a2c: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 2F070B741A55BD1E47933621D9127780; Granted Authorities: USER
2017-05-24 22:54:35.966 DEBUG 16899 --- [nio-8082-exec-6] o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.web.access.expression.WebExpressionVoter#61f8721f, returned: 1
2017-05-24 22:54:35.966 DEBUG 16899 --- [nio-8082-exec-6] o.s.s.w.a.i.FilterSecurityInterceptor : Authorization successful
2017-05-24 22:54:35.966 DEBUG 16899 --- [nio-8082-exec-6] o.s.s.w.a.i.FilterSecurityInterceptor : RunAsManager did not change Authentication object
2017-05-24 22:54:35.966 DEBUG 16899 --- [nio-8082-exec-6] o.s.security.web.FilterChainProxy : /oauth/token reached end of additional filter chain; proceeding with original chain
2017-05-24 22:54:35.967 DEBUG 16899 --- [nio-8082-exec-6] .s.o.p.e.FrameworkEndpointHandlerMapping : Looking up handler method for path /oauth/token
2017-05-24 22:54:35.968 DEBUG 16899 --- [nio-8082-exec-6] .s.o.p.e.FrameworkEndpointHandlerMapping : Returning handler method [public org.springframework.http.ResponseEntity<org.springframework.security.oauth2.common.OAuth2AccessToken> org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken(java.security.Principal,java.util.Map<java.lang.String, java.lang.String>) throws org.springframework.web.HttpRequestMethodNotSupportedException]
2017-05-24 22:54:35.975 DEBUG 16899 --- [nio-8082-exec-6] .o.p.p.ResourceOwnerPasswordTokenGranter : Getting access token for: client
2017-05-24 22:54:35.975 DEBUG 16899 --- [nio-8082-exec-6] o.s.s.authentication.ProviderManager : Authentication attempt using org.springframework.security.authentication.dao.DaoAuthenticationProvider
Hibernate: select user0_.id as id1_1_, user0_.enabled as enabled2_1_, user0_.name as name3_1_, user0_.password as password4_1_, user0_.username as username5_1_ from user user0_ where user0_.username=?
Hibernate: select roles0_.user_id as user_id1_2_0_, roles0_.role_id as role_id2_2_0_, role1_.id as id1_0_1_, role1_.role as role2_0_1_ from user_role roles0_ inner join role role1_ on roles0_.role_id=role1_.id where roles0_.user_id=?
2017-05-24 22:54:36.125 INFO 16899 --- [nio-8082-exec-6] o.s.s.o.p.token.store.JdbcTokenStore : Failed to find access token for token 180c2528-b712-4088-9cce-71e9cc7ccb94
2017-05-24 22:54:36.232 DEBUG 16899 --- [nio-8082-exec-6] o.s.s.w.a.ExceptionTranslationFilter : Chain processed normally
2017-05-24 22:54:36.232 DEBUG 16899 --- [nio-8082-exec-6] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
May be I am configuring something wrong. What am I missing here?
I had a similar issue and found that the OAuth2AuthenticationProcessingFilter was not getting invoked by the filter chain and because of which, user was not getting authenticated and hence treated as anonymous.
Am using Spring-boot 1.5.3 version and I added below line in application.yml to fix the ordering.
security.oauth2.resource.filter-order=3
There must be a log statement present which shows that its getting invoked
DEBUG 34386 --- [nio-8082-exec-1] o.s.security.web.FilterChainProxy : /foo at position 5 of 11 in additional filter chain; firing Filter: 'OAuth2AuthenticationProcessingFilter'
Reference - https://github.com/spring-projects/spring-security-oauth/issues/993
To use
.authorizedGrantTypes("password","refresh_token", "client_credentials")
you need to open access to
/auth/token
in security configuration of your Authorization Server
#Override
protected void configure(HttpSecurity http) throws Exception {
code...
http
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/oauth/token").permitAll()
.anyRequest().authenticated()
code...
}
I was facing the same problem and managed to fix it by explicitly disabling some spring autoconfiguration:
#EnableAutoConfiguration(exclude = [OAuth2AutoConfiguration::class,
SecurityAutoConfiguration::class, SecurityFilterAutoConfiguration::class])
Also check if you are using multiple OAuth2 filters coming from multiple auth-related dependices/libraries.
In my case I was using
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.2.4.RELEASE</version>
</dependency>
Along with another custom OAuth2 library used in my organization, which caused the spring library to attempt to authenticate before the custom (correct) one, and with no credentials fed to it of course throwing this exact exception.
My service calls worked once I removed the Spring OAuth2 dependency.

Resources