Setup - diging/oauth-tokens GitHub Wiki

Setting your project up with Spring OAuth2 and oauth-tokens

After adding the Spring OAuth2 libraries to your project, there are a couple of required configuration. To avoid issues with classes in different contexts, it's easiest to scan the root package of your project in your root context.

CSRF exemptions

To exempt your API endpoints from requiring CSRF tokens, add the following RequestMatcher:

public class CsrfSecurityRequestMatcher implements RequestMatcher {
    private Pattern allowedMethods = Pattern.compile("^(GET|HEAD|TRACE|OPTIONS)$");
    private RegexRequestMatcher unprotectedMatcher = new RegexRequestMatcher("/api/v1/.*", null);

    @Override
    public boolean matches(HttpServletRequest request) {
        if (allowedMethods.matcher(request.getMethod()).matches()) {
            return false;
        }

        return !unprotectedMatcher.matches(request);
    }
}

Security Context

There are three security configurations necessary:

@EnableWebSecurity
public class SecurityContext extends WebSecurityConfigurerAdapter {
    
    @Override
    public void configure(WebSecurity web) throws Exception {
        web
        // Spring Security ignores request to static resources such as CSS or JS
        // files.
        .ignoring().antMatchers("/static/**");
    }
    
    @Configuration
    @EnableAuthorizationServer
    @PropertySource("classpath:/config.properties")
    public static class OAuth2Configuration extends AuthorizationServerConfigurerAdapter {

        @Value("${_oauth_token_validity}")
        private int oauthTokenValidity;

        @Autowired
        private AuthenticationManager authenticationManager;
        
        @Autowired 
        private OAuthClientRepository clientRepo;
        
        @Autowired
        private BCryptPasswordEncoder bCryptPasswordEncoder;
        
        @Autowired
        @Qualifier("OAuthClientDetailsService")
        private ClientDetailsService clientDetailsService;
        
        @Bean
        public TokenStore tokenStore() {
            return new InMemoryTokenStore();
        }

        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable().authorizeRequests().antMatchers("/oauth/token").permitAll().anyRequest()
                    .authenticated();
        }

        @Override
        public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {
            configurer.withClientDetails(clientDetailsService);
        }
        
        @Bean
        public IOAuthClientManager oauthClientManager() {
            return new OAuthClientManager(clientRepo, bCryptPasswordEncoder, oauthTokenValidity);
        }

        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints
                    .pathMapping("/oauth/authorize", "/api/v1/oauth/authorize")
                    .pathMapping("/oauth/check_token", "/api/v1/oauth/check_token")
                    .pathMapping("/oauth/confirm_access", "/api/v1/oauth/confirm_access")
                    .pathMapping("/oauth/error", "/api/v1/oauth/error")
                    .pathMapping("/oauth/token", "/api/v1/oauth/token").tokenStore(tokenStore())
                    .authenticationManager(authenticationManager);
        }

        @Override
        public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
            oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')")
                    .checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')").allowFormAuthenticationForClients();
        }

    }

    @Configuration
    @Order(1)
    public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().requireCsrfProtectionMatcher(new CsrfSecurityRequestMatcher()).and().formLogin()
                    // Configures the logout function
                    .and().logout().deleteCookies("JSESSIONID").and()
                    .exceptionHandling().accessDeniedPage("/403")
                    .and().requestMatchers().requestMatchers(new NegatedRequestMatcher(
                                new OrRequestMatcher(
                                        new AntPathRequestMatcher("/api/**")
                                )
                        ))
                    // Configures url based authorization
                    .and()
                    .authorizeRequests()
                    // Anyone can access the urls
                    .antMatchers("/", "/resources/**",
                           "/register").permitAll()
                    // The rest of the our application is protected.
                    .antMatchers("/users/**", "/admin/**").hasRole("ADMIN")
                    .anyRequest().hasRole("USER");
        }

        public AuthenticationFailureHandler customAuthenticationFailureHandler(String defaultFailureUrl) {
            return new CustomAuthenticationFailureHandler(defaultFailureUrl);
        }
        
    }

    @Configuration
    @EnableResourceServer
    @Order(2)
    public static class ResourceServerConfigurer extends ResourceServerConfigurerAdapter {
        // configure
        private static final String RESOURCE_ID = "my_rest_api";
        
        @Autowired
        private TokenStore tokenStore;

        @Override
        public void configure(ResourceServerSecurityConfigurer resources) {
            resources.tokenStore(tokenStore).resourceId(RESOURCE_ID).stateless(false);
        }

        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.csrf().disable().authorizeRequests().antMatchers("/api/**").authenticated().and()
                .exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
        }

    }

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(4);
    }

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

}