Spring Security - OAuth2

This guide walks you through the steps for creating Restful API with Spring using Oauth2 to manage authorization.

Example

The application is pretty simple, it's just fetch some users from database using Spring Data. Each endpoints' security is controlled by Spring Security based on the OAuth2 scope that logged users have.

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>spring-swagger-oauth2</groupId>
    <artifactId>spring-swagger-oauth2</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <swagger.version>2.9.2</swagger.version>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.1.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>${swagger.version}</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>${swagger.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.5.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

All files below will be executed by Spring during start up. It should be in the Java's resource folder of the project.

application.properties

# LOGGING LEVEL
logging.level.org.springframework.web = DEBUG

# DB PROPERTIES:
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=12345678

# HIBERNATE CONFIGURATION:
hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
hibernate.show_sql=true
hibernate.hbm2ddl.auto=update

data.sql

insert into users (user_id, username, password, first_name, last_name, admin) values (1, 'tiago', '$2a$10$15CmMwDjGYlYsTN8Y/.1S.770GqiKtIaHa.lLoI1AnYe4Lp1Ie8C6', 'Tiago', 'Souza', false);
insert into users (user_id, username, password, first_name, last_name, admin) values (2, 'ziggy', '$2a$10$15CmMwDjGYlYsTN8Y/.1S.770GqiKtIaHa.lLoI1AnYe4Lp1Ie8C6', 'Ziggy', 'Zagg', false);
insert into users (user_id, username, password, first_name, last_name, admin) values (3, 'bapi', '$2a$10$15CmMwDjGYlYsTN8Y/.1S.770GqiKtIaHa.lLoI1AnYe4Lp1Ie8C6', 'Bapi', 'Any', false);
insert into users (user_id, username, password, first_name, last_name, admin) values (4, 'john', '$2a$10$15CmMwDjGYlYsTN8Y/.1S.770GqiKtIaHa.lLoI1AnYe4Lp1Ie8C6', 'John', 'Duo', false);
insert into users (user_id, username, password, first_name, last_name, admin) values (5, 'peter', '$2a$10$15CmMwDjGYlYsTN8Y/.1S.770GqiKtIaHa.lLoI1AnYe4Lp1Ie8C6', 'Peter', 'Saint', false);
insert into users (user_id, username, password, first_name, last_name, admin) values (6, 'admin', '$2a$10$JQOfG5Tqnf97SbGcKsalz.XpDQbXi1APOf2SHPVW27bWNioi9nI8y', 'Super', 'Admin', true);

schema.sql

CREATE TABLE users (
	USER_ID INT PRIMARY KEY AUTO_INCREMENT NOT NULL,
	USERNAME VARCHAR(40) NOT NULL,
	PASSWORD VARCHAR(255) NOT NULL,
	FIRST_NAME VARCHAR(40) NOT NULL,
	LAST_NAME VARCHAR(40) NOT NULL,
	ADMIN BOOLEAN
);

Now we have our endpoints implemented and documented with Swagger.

UserApi.java

package io.tiago.oauth.api.v1;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.tiago.oauth.entity.User;
import io.tiago.oauth.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Api(value = "User API V1")
@RequestMapping(value = "/v1/api")
public class UserApi {

    @Autowired
    private UserService userService;

    @ApiOperation(value = "Simple text", notes = "This endpoint returns simple message - without authentication", response = User.class)
    @ApiResponses(value = {@ApiResponse(code = 200, message = "", response = User[].class), @ApiResponse(code = 500, message = "Exception", response = Exception.class)})
    @GetMapping(value = "/get", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<User> findAll() throws Exception {
        return new ResponseEntity<>(userService.findAll().stream().findAny().get(), HttpStatus.OK);
    }
}

This endpoint (v2) contains Spring's annotation @PreAuthorize("#oauth2.hasScope('read')") which means only users with read scopes can read it.

package io.tiago.oauth.api.v2;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.tiago.oauth.entity.User;
import io.tiago.oauth.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController("UserControllerV2")
@Api(value = "User API V2 Description")
@RequestMapping(value = "/v2/api")
public class UserApi {

    @Autowired
    private UserService userService;

    @ApiOperation(value = "List all users", notes = "List all users", response = User.class)
    @ApiResponses(value = {@ApiResponse(code = 200, message = "", response = User[].class), @ApiResponse(code = 500, message = "Some app arror", response = Exception.class)})
    @RequestMapping(value = "/list", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    @PreAuthorize("#oauth2.hasScope('read')")
    public ResponseEntity<List<User>> list() throws Exception {
        List<User> users = userService.findAll();
        return new ResponseEntity<>(users, HttpStatus.OK);
    }
}

The endpoint (v3) contains Spring's annotation @PreAuthorize("#oauth2.hasScope('write')") which means only users with write scopes can write to the database.

package io.tiago.oauth.api.v3;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.tiago.oauth.entity.User;
import io.tiago.oauth.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController("UserControllerV3")
@Api(value = "User API V3 Description")
@RequestMapping(value = "/v3/api", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public class UserApi {

    @Autowired
    private UserService userService;

    @ApiOperation(value = "Add a user", notes = "This method add some user to database", response = User.class)
    @ApiResponses(value = {@ApiResponse(code = 201, message = "User created successfully", response = User.class), @ApiResponse(code = 500, message = "Some error on add a new user", response = Exception.class)})
    @RequestMapping(value = "/add", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    @PreAuthorize("#oauth2.hasScope('write')")
    public ResponseEntity<User> add(@RequestBody User user) throws Exception {
        userService.add(user);
        return new ResponseEntity<>(HttpStatus.CREATED);
    }
}

JPA entity represeting our database table.

package io.tiago.oauth.entity;

import org.hibernate.validator.constraints.NotEmpty;

import javax.persistence.*;
import java.io.Serializable;

@Entity
@Table(name = "users")
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue
    @Column(name = "USER_ID")
    private Integer id;

    @Column(name = "USERNAME")
    @NotEmpty(message = "Username can not be empty")
    private String username;

    @Column(name = "PASSWORD")
    @NotEmpty(message = "Password can not be empty")
    private String password;

    @Column(name = "FIRST_NAME")
    @NotEmpty(message = "Firstname can not be empty")
    private String firstName;

    @Column(name = "LAST_NAME")
    @NotEmpty(message = "Lastname can not be empty")
    private String lastName;

    @Column(name = "ADMIN")
    private boolean admin;

    public User() {}

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public boolean isAdmin() {
        return admin;
    }

    public void setAdmin(boolean admin) {
        this.admin = admin;
    }
}

UserRepository.java is our Spring Data repository.

package io.tiago.oauth.repository;

import io.tiago.oauth.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Integer> {

    User findByUsername(String username);

}

UserCredentials.java

package io.tiago.oauth.service;

import io.tiago.oauth.entity.User;
import io.tiago.oauth.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
public class UserCredentials implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        User user = userRepository.findByUsername(username);

        if (user == null) {
            throw new UsernameNotFoundException(String.format("User with the username %s doesn't exist", username));
        }

        List<GrantedAuthority> authorities = new ArrayList<>();

        if (user.isAdmin()) {
            authorities = AuthorityUtils.createAuthorityList("ROLE_ADMIN");
        }

        UserDetails userDetails = new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities);

        return userDetails;
    }
}

UserService.java

package io.tiago.oauth.service;

import io.tiago.oauth.entity.User;
import io.tiago.oauth.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.util.List;
import java.util.Optional;

@Service
public class UserService {

	@Autowired
	private UserRepository userRepository;
	
	@Transactional
	public void add(User u) {
		userRepository.save(u);		
	}
		
	public List<User> findAll() {
		return userRepository.findAll();
	}

	@Transactional
	public void delete(Integer uid) {
		userRepository.deleteById(uid);
	}

	public Optional<User> findOne(Integer uid) {
		return userRepository.findById(uid);
	}
}

Constants.java

package io.tiago.oauth.config;

public class Constants {

    public static final String CLIENT_ID = "souzaClientID";

    public static final String CLIENT_SECRET = "souzaSecret";

    public static final String V1_PKG = "io.tiago.oauth.api.v1";

    public static final String V2_PKG = "io.tiago.oauth.api.v2";

    public static final String V3_PKG = "io.tiago.oauth.api.v3";

    public static final String SCAN_PKG = "io.tiago.oauth.*";

    public static final String SWAGGER_API_DESCRIPTION = "This is a Restful API to demonstrate Spring Security OAuth2. " +
            "This example use authorization code as grant type."
            + "<br/> Click here https://metiago.github.io to log in.";
}
package io.tiago.oauth.config;

import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.provider.expression.OAuth2ExpressionParser;
import org.springframework.security.oauth2.provider.expression.OAuth2SecurityExpressionMethods;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }

    @Override
    @Bean
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    @Override
    @Order(Ordered.HIGHEST_PRECEDENCE)
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/v1/**", "/swagger-ui/**", "/api-docs/**").permitAll().and().formLogin();
        http.csrf().disable();
    }

    @EnableGlobalMethodSecurity(prePostEnabled = true)
    static class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
        @Override
        protected MethodSecurityExpressionHandler createExpressionHandler() {
            return new OAuth2MethodSecurityExpressionHandler();
        }
    }

    private static class OAuth2MethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {
        public OAuth2MethodSecurityExpressionHandler() {
            setExpressionParser(new OAuth2ExpressionParser(getExpressionParser()));
        }

        @Override
        public StandardEvaluationContext createEvaluationContextInternal(Authentication authentication, MethodInvocation mi) {
            final StandardEvaluationContext ec = super.createEvaluationContextInternal(authentication, mi);
            final OAuth2SecurityExpressionMethods methods = new OAuth2SecurityExpressionMethods(authentication);
            ec.setVariable("oauth", methods);
            ec.setVariable("oauth2", methods);
            return ec;
        }
    }
}

ResourceServerConfig.java

package io.tiago.oauth.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.resourceId("Souza-OAuth2").stateless(true);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.requestMatchers().antMatchers("/v2/**")
                .and().authorizeRequests().antMatchers("/list").access("#oauth2.hasScope('read')");
        http.requestMatchers().antMatchers("/v3/**")
                .and().authorizeRequests().antMatchers("/add").access("#oauth2.hasScope('write')");
    }
}

AuthorizationServerConfig.java

package io.tiago.oauth.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;

import static io.tiago.oauth.config.Constants.CLIENT_ID;
import static io.tiago.oauth.config.Constants.CLIENT_SECRET;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    private final String[] REDIRECT_URI = new String[]{"http://localhost:8080/home",
                                                       "https://oauth.pstmn.io/v1/callback",
                                                       "http://localhost:8080/webjars/springfox-swagger-ui/oauth2-redirect.html"};

    @Autowired
    private AuthenticationManager authenticationManager;

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

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

        clients.inMemory().withClient(CLIENT_ID)
                .secret("{noop}"+CLIENT_SECRET)
                .authorizedGrantTypes("password", "authorization_code", "refresh_token")
                .scopes("read", "write")
                .resourceIds("Souza-OAuth2")
                .redirectUris(REDIRECT_URI)
                .refreshTokenValiditySeconds(360)
                .accessTokenValiditySeconds(360);
    }
}

SwaggerConfig.java

package io.tiago.oauth.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger.web.SecurityConfiguration;
import springfox.documentation.swagger.web.SecurityConfigurationBuilder;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static io.tiago.oauth.config.Constants.CLIENT_ID;
import static io.tiago.oauth.config.Constants.CLIENT_SECRET;

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket swaggerSettingsEndpointV1() {

        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("Users_v1")
                .select()
                .apis(RequestHandlerSelectors.basePackage(Constants.V1_PKG))
                .paths(PathSelectors.any())
                .build()
                .apiInfo(apiInfo());
    }

    @Bean
    public Docket swaggerSettingsEndpointV2() {

        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("Users_V2")
                .select()
                .apis(RequestHandlerSelectors.basePackage(Constants.V2_PKG))
                .paths(PathSelectors.any())
                .build()
                .securitySchemes(Collections.singletonList(securitySchema()))
                .securityContexts(Collections.singletonList(securityContext())).pathMapping("/")
                .apiInfo(apiInfo());
    }

    @Bean
    public Docket swaggerSettingsEndpointV3() {

        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("Users_V3")
                .select()
                .apis(RequestHandlerSelectors.basePackage(Constants.V3_PKG))
                .paths(PathSelectors.any())
                .build()
                .securitySchemes(Collections.singletonList(securitySchema()))
                .securityContexts(Collections.singletonList(securityContext())).pathMapping("/")
                .apiInfo(apiInfo());
    }

    private ApiInfo apiInfo() {
        Contact contact = new Contact("Mr. Souza", "https://metiago.github.io", "mesouza@gmail.com");
        ApiInfo apiInfo = new ApiInfo("ZBX2", Constants.SWAGGER_API_DESCRIPTION,
                "0.1.0", "Terms of service",
                contact,
                "Apache 2.0",
                "https://www.apache.org/licenses/LICENSE-2.0", Collections.emptyList());
        return apiInfo;
    }

    @Bean
    public SecurityConfiguration security() {
        return SecurityConfigurationBuilder.builder()
                .clientId(CLIENT_ID)
                .clientSecret(CLIENT_SECRET)
                .useBasicAuthenticationWithAccessCodeGrant(true)
                .build();
    }

    private SecurityContext securityContext() {
        return SecurityContext.builder()
                .securityReferences(defaultAuth())
                .forPaths(PathSelectors.regex("/v2.*"))
                .forPaths(PathSelectors.regex("/v3.*"))
                .build();
    }

    private OAuth securitySchema() {
        List<AuthorizationScope> authorizationScopeList = new ArrayList();
        authorizationScopeList.add(new AuthorizationScope("read", "read all"));
        authorizationScopeList.add(new AuthorizationScope("write", "write all"));
        List<GrantType> grantTypes = new ArrayList();
        GrantType creGrant = new AuthorizationCodeGrant(new TokenRequestEndpoint("/oauth/authorize", CLIENT_ID, CLIENT_SECRET), new TokenEndpoint("/oauth/token", "my_token"));
        grantTypes.add(creGrant);
        return new OAuth("oauth2schema", authorizationScopeList, grantTypes);
    }

    private List<SecurityReference> defaultAuth() {
        final AuthorizationScope[] authorizationScopes = new AuthorizationScope[2];
        authorizationScopes[0] = new AuthorizationScope("read", "read all");
        authorizationScopes[1] = new AuthorizationScope("write", "write all");
        return Collections.singletonList(new SecurityReference("oauth2schema", authorizationScopes));
    }
}

AppConfig.java

package io.tiago.oauth.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@EnableTransactionManagement
@ComponentScan({Constants.SCAN_PKG})
@EnableJpaRepositories(Constants.SCAN_PKG)
public class AppConfig implements WebMvcConfigurer {

    private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
            "classpath:/META-INF/resources/", "classpath:/resources/",
            "classpath:/static/", "classpath:/public/"};

    @Bean
    public WebMvcConfigurer corsConfigurer() {

        return new WebMvcConfigurer() {

            @Override
            public void addCorsMappings(CorsRegistry registry) {

                registry.addMapping("/**")
                        .allowedOrigins("*")
                        .allowedMethods("POST", "PUT", "DELETE", "GET", "OPTIONS")
                        .allowedHeaders("content-type", "Authorization")
                        .exposedHeaders("content-type", "Authorization")
                        .allowCredentials(false).maxAge(3600);
            }
        };
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {

        if (!registry.hasMappingForPattern("/webjars/**")) {
            registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
        }

        if (!registry.hasMappingForPattern("/**")) {
            registry.addResourceHandler("/**").addResourceLocations(CLASSPATH_RESOURCE_LOCATIONS);
        }
    }
}

Server.java

package io.tiago.oauth;

import io.tiago.oauth.config.AppConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.context.annotation.Import;

@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
@Import({AppConfig.class})
public class Server {

    public static void main(String[] args) {
        SpringApplication.run(Server.class, args);
    }
}