2

Issues setting up spring-security-oauth2 (password grant type) for a traditional non-boot spring mvc app.

Able to retrieve access token (step 1).

Get a status 401 when using 'access token' to retrieve secured resource (see step 2).

Curl Commands:

  1. Get access token:

    curl -X POST --user clientapp:123456 http://localhost:8080/oauth/token -H "accept: application/json" -H "content-type: application/x-www-form-urlencoded" -d "grant_type=password&username=adolfo&password=123&scope=read_profile"

Response:

{"access_token":"50c7c311-c73a-4a32-bc7e-6801bc64bbe0","token_type":"bearer","expires_in":41545,"scope":"read_profile"}
  1. Retrieve secured data with access token

    curl -X GET http://localhost:8080/api/profile -H "authorization: Bearer 50c7c311-c73a-4a32-bc7e-6801bc64bbe0"

Response: Status 401

The request has not been applied because it lacks valid authentication credentials for the target resource.

Configurations

//Resource Server Config

 @Configuration
    @EnableResourceServer
    public class OAuth2ResourceServer extends ResourceServerConfigurerAdapter {

        @Override
        public void configure(HttpSecurity http) throws Exception {
            //@formatter:off
            http.authorizeRequests()
                    .anyRequest()
                    .authenticated()
                    .and()
                    .requestMatchers()
                    .antMatchers("/api/**");
            //@formatter:on
        }

    }

Authorization Server Config

 @Configuration
    @EnableAuthorizationServer
    public class OAuth2AuthorizationServer extends AuthorizationServerConfigurerAdapter {


        @Autowired
        private AuthenticationManager authenticationManager;

        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints.authenticationManager(authenticationManager);
        }


        @Override
        public void configure(ClientDetailsServiceConfigurer clients)
                throws Exception {
            clients.inMemory()
                    .withClient("clientapp")
                    .secret("123456")
                    //.autoApprove(true)
                    .redirectUris("http://localhost:9000/callback")
                    .authorizedGrantTypes("password")
                    .scopes("read_profile", "read_contacts");
        }


        @Override
        public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
            oauthServer.passwordEncoder(NoOpPasswordEncoder.getInstance());
        }
    }

Security Config

Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .csrf().disable()
            .authorizeRequests()
            .antMatchers("/api/**").permitAll()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .usernameParameter("username")
            .passwordParameter("password")
            .loginPage("/signin")
            .loginProcessingUrl("/authenticate")
            .defaultSuccessUrl("/")
            .failureUrl("/signin-error")
            .permitAll();

}

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


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().passwordEncoder(NoOpPasswordEncoder.getInstance())
                //.passwordEncoder(passwordEncoder())
                .withUser("adolfo")
                .password("123")
                .roles("USER");
    }

}

//Controller

@Controller
public class UserController {

    @RequestMapping("/api/profile")
    public ResponseEntity<UserProfile> profile() {
        String username = (String) SecurityContextHolder.getContext()
                .getAuthentication().getPrincipal();
        String email = username + "@mailinator.com";
        UserProfile profile = new UserProfile();
        profile.setName(username);
        profile.setEmail(email);
        return ResponseEntity.ok(profile);
    }

Init Class

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

     @Override
     protected Class<?>[] getRootConfigClasses() {
     return new Class<?>[]{ SecurityConfig.class, OAuth2AuthorizationServer.class, OAuth2ResourceServer.class,};
     }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[]{DispatcherConfig.class};
    }
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    @Override
    protected Filter[] getServletFilters() {
        DelegatingFilterProxy delegatingFilterProxy =  new DelegatingFilterProxy("springSecurityFilterChain");
        HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
        return  new Filter[] {delegatingFilterProxy,hiddenHttpMethodFilter};
    }

}

//Logs

2018-08-13 15:50:29,333 DEBUG [http-nio-8080-exec-10] o.s.s.w.u.m.OrRequestMatcher [OrRequestMatcher.java:65] Trying to match using Ant [pattern='/oauth/token']
2018-08-13 15:50:29,334 DEBUG [http-nio-8080-exec-10] o.s.s.w.u.m.AntPathRequestMatcher [AntPathRequestMatcher.java:157] Checking match of request : '/api/profile'; against '/oauth/token'
2018-08-13 15:50:29,334 DEBUG [http-nio-8080-exec-10] o.s.s.w.u.m.OrRequestMatcher [OrRequestMatcher.java:65] Trying to match using Ant [pattern='/oauth/token_key']
2018-08-13 15:50:29,334 DEBUG [http-nio-8080-exec-10] o.s.s.w.u.m.AntPathRequestMatcher [AntPathRequestMatcher.java:157] Checking match of request : '/api/profile'; against '/oauth/token_key'
2018-08-13 15:50:29,334 DEBUG [http-nio-8080-exec-10] o.s.s.w.u.m.OrRequestMatcher [OrRequestMatcher.java:65] Trying to match using Ant [pattern='/oauth/check_token']
2018-08-13 15:50:29,334 DEBUG [http-nio-8080-exec-10] o.s.s.w.u.m.AntPathRequestMatcher [AntPathRequestMatcher.java:157] Checking match of request : '/api/profile'; against '/oauth/check_token'
2018-08-13 15:50:29,334 DEBUG [http-nio-8080-exec-10] o.s.s.w.u.m.OrRequestMatcher [OrRequestMatcher.java:72] No matches found
2018-08-13 15:50:29,334 DEBUG [http-nio-8080-exec-10] o.s.s.w.u.m.OrRequestMatcher [OrRequestMatcher.java:65] Trying to match using Ant [pattern='/api/**']
2018-08-13 15:50:29,335 DEBUG [http-nio-8080-exec-10] o.s.s.w.u.m.AntPathRequestMatcher [AntPathRequestMatcher.java:157] Checking match of request : '/api/profile'; against '/api/**'
2018-08-13 15:50:29,335 DEBUG [http-nio-8080-exec-10] o.s.s.w.u.m.OrRequestMatcher [OrRequestMatcher.java:68] matched
2018-08-13 15:50:29,335 DEBUG [http-nio-8080-exec-10] o.s.s.w.FilterChainProxy [FilterChainProxy.java:328] /api/profile at position 1 of 11 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2018-08-13 15:50:29,335 DEBUG [http-nio-8080-exec-10] o.s.s.w.FilterChainProxy [FilterChainProxy.java:328] /api/profile at position 2 of 11 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2018-08-13 15:50:29,335 DEBUG [http-nio-8080-exec-10] o.s.s.w.FilterChainProxy [FilterChainProxy.java:328] /api/profile at position 3 of 11 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2018-08-13 15:50:29,335 DEBUG [http-nio-8080-exec-10] o.s.s.w.FilterChainProxy [FilterChainProxy.java:328] /api/profile at position 4 of 11 in additional filter chain; firing Filter: 'LogoutFilter'
2018-08-13 15:50:29,335 DEBUG [http-nio-8080-exec-10] o.s.s.w.u.m.OrRequestMatcher [OrRequestMatcher.java:65] Trying to match using Ant [pattern='/logout', GET]
2018-08-13 15:50:29,335 DEBUG [http-nio-8080-exec-10] o.s.s.w.u.m.AntPathRequestMatcher [AntPathRequestMatcher.java:157] Checking match of request : '/api/profile'; against '/logout'
2018-08-13 15:50:29,336 DEBUG [http-nio-8080-exec-10] o.s.s.w.u.m.OrRequestMatcher [OrRequestMatcher.java:65] Trying to match using Ant [pattern='/logout', POST]
2018-08-13 15:50:29,336 DEBUG [http-nio-8080-exec-10] o.s.s.w.u.m.AntPathRequestMatcher [AntPathRequestMatcher.java:137] Request 'GET /api/profile' doesn't match 'POST /logout
2018-08-13 15:50:29,336 DEBUG [http-nio-8080-exec-10] o.s.s.w.u.m.OrRequestMatcher [OrRequestMatcher.java:65] Trying to match using Ant [pattern='/logout', PUT]
2018-08-13 15:50:29,336 DEBUG [http-nio-8080-exec-10] o.s.s.w.u.m.AntPathRequestMatcher [AntPathRequestMatcher.java:137] Request 'GET /api/profile' doesn't match 'PUT /logout
2018-08-13 15:50:29,336 DEBUG [http-nio-8080-exec-10] o.s.s.w.u.m.OrRequestMatcher [OrRequestMatcher.java:65] Trying to match using Ant [pattern='/logout', DELETE]
2018-08-13 15:50:29,336 DEBUG [http-nio-8080-exec-10] o.s.s.w.u.m.AntPathRequestMatcher [AntPathRequestMatcher.java:137] Request 'GET /api/profile' doesn't match 'DELETE /logout
2018-08-13 15:50:29,337 DEBUG [http-nio-8080-exec-10] o.s.s.w.u.m.OrRequestMatcher [OrRequestMatcher.java:72] No matches found
2018-08-13 15:50:29,337 DEBUG [http-nio-8080-exec-10] o.s.s.w.FilterChainProxy [FilterChainProxy.java:328] /api/profile at position 5 of 11 in additional filter chain; firing Filter: 'OAuth2AuthenticationProcessingFilter'
2018-08-13 15:50:29,347 DEBUG [http-nio-8080-exec-10] o.s.s.o.p.a.OAuth2AuthenticationProcessingFilter [OAuth2AuthenticationProcessingFilter.java:165] Authentication request failed: error="invalid_token", error_description="Invalid access token: d86d8105-9edf-44dd-94b6-36542cade80f"
2018-08-13 15:50:29,365 DEBUG [http-nio-8080-exec-10] o.s.s.w.h.w.HstsHeaderWriter [HstsHeaderWriter.java:129] Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@5337f7a
2018-08-13 15:50:29,365 DEBUG [http-nio-8080-exec-10] o.s.s.o.p.e.DefaultOAuth2ExceptionRenderer [DefaultOAuth2ExceptionRenderer.java:101] Written [error="invalid_token", error_description="Invalid access token: d86d8105-9edf-44dd-94b6-36542cade80f"] as "application/json;charset=UTF-8" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@734d0eb7]
2018-08-13 15:50:29,366 DEBUG [http-nio-8080-exec-10] o.s.s.w.c.SecurityContextPersistenceFilter [SecurityContextPersistenceFilter.java:119] SecurityContextHolder now cleared, as request processing completed

See the entire log here

10
  • Your configuration seems to be okay and you should be able to call controller method with valid access token. To find out what goes wrong try to debug OAuth2AuthenticationProcessingFilter. Finally you will see the reason of getting of 401 status code. Commented Aug 13, 2018 at 9:49
  • @briarheart removed '@Order' from SecurityConfig, now i get 'invalid_token' Commented Aug 13, 2018 at 14:58
  • Perfect! Issue new token and try again. Commented Aug 13, 2018 at 15:18
  • Repeated step 1 and 2 above but same invalid token response. Commented Aug 13, 2018 at 15:21
  • Is there any additional information in response (description for example) or in server logs? Commented Aug 13, 2018 at 15:33

1 Answer 1

0

I don't see the OAuth2AuthenticationProcessingFilter in your stacktrace. I think it is because of the @Order(2) annotation that is placed on the SecurityConfig class. Your web security configuration clashes with Spring's internal configuration (org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfiguration). Try to remove @Order annotation from the SecurityConfig class.

EDIT

Another reason of getting 401 response is using different instances of org.springframework.security.oauth2.provider.token.TokenStore: one for storing of newly created tokens and another for authentication. It may be caused by the wrong configuration of servlet initializer when implementation of org.springframework.web.servlet.config.annotation.WebMvcConfigurer references (through ComponentScan annotation for example) to other configurations that are already declared as root configuration classes. It leads to creation of two application contexts that are clash with each other.

Let's suppose you have three configuration classes:

  1. ServletConfig that implements org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  2. AuthorizationServerConfig;
  3. ResourceServerConfig.

There are two ways to configure your servlet initializer. The first one is to return ServletConfig class from getServletConfigClasses method:

public class MyServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] {AuthorizationServerConfig.class, AuthorizationServerConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] {ServletConfig.class};
    }
}

By choosing this way you should make sure you do not include classes from root configuration into servlet configuration (do not run component scan from ServletConfig class against package where root configuration classes are located).

The second way is to move ServletConfig to root configuration classes returning null from getServletConfigClasses:

public class MyServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] {ServletConfig.class, AuthorizationServerConfig.class, AuthorizationServerConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return null;
    }
}

It is perfectly fine to choose the second way as long as you do not need multiple dispatcher servlets.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.