Spring Boot Security OAuth2 Example(Bcrypt Encoder)

By Dhiraj Ray, 20 October,2017   | Last updated on: 18 January,2018 11050

In this post we will be discussing about securing REST APIs using Spring Boot Security OAuth2 with an example.We will be implementing AuthorizationServer, ResourceServer and some REST API for different crud operations and test these APIs using Postman.Here we will be using mysql database to read user credentials instead of in-memory authentication.Also, to ease our ORM solution, we will be using spring-data and BCryptPasswordEncoder for password encoding.

What is OAuth

OAuth is simply a secure authorization protocol that deals with the authorization of third party application to access the user data without exposing their password. eg. (Login with fb, gPlus, twitter in many websites..) all work under this protocol.The Protocol becomes easier when you know the involved parties. Basically there are three parties involved: oAuth Provider, oAuth Client and Owner.Here, oAuth Provider provides the auth token such as Facebook, twitter. Similarly, oAuth Client are the the applications which want access of the credentials on behalf of owner and owner is the user which has account on oAuth providers such as facebook and twitter.

What is OAuth2

OAuth 2 is an authorization framework that enables applications to obtain limited access to user accounts on an HTTP service, such as Facebook, GitHub, and DigitalOcean. It works by delegating user authentication to the service that hosts the user account, and authorizing third-party applications to access the user account. OAuth 2 provides authorization flows for web and desktop applications, and mobile devices.

OAuth2 Roles

OAuth2 provides 4 different roles.

Resource Owner: User

Client: Application

Resource Server: API

Authorization Server: API

OAuth2 Grant Types

Following are the 4 different grant types defined by OAuth2

Authorization Code: used with server-side Applications

Implicit: used with Mobile Apps or Web Applications (applications that run on the user's device)

Resource Owner Password Credentials: used with trusted Applications, such as those owned by the service itself

Client Credentials: used with Applications API access

Project Structure

Following is the project structure of our Spring Boot Security OAuth2 implementation.

oauth2-project-strct

Maven Dependencies

pom.xml

Following are the required dependencies.

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.8.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</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.boot</groupId> <artifactId>spring-security-oauth2</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> </dependency> </dependencies>

OAuth2 Authorization Server Config

This class extends AuthorizationServerConfigurerAdapter and is responsible for generating tokens specific to a client.Suppose, if a user wants to login to devglan.comvia facebook then facebook auth server will be generating tokens for Devglan.In this case, Devglan becomes the client which will be requesting for authorization code on behalf of user from facebook - the authorization server.Following is a similar implementation that facebook will be using.

Here, we are using in-memory credentials with client_id as devglan-client and CLIENT_SECRET as devglan-secret.

@EnableAuthorizationServer: Enables an authorization server

AuthorizationServerConfig.java
package com.devglan.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 org.springframework.security.oauth2.provider.approval.UserApprovalHandler; import org.springframework.security.oauth2.provider.token.TokenStore; @Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { static final String CLIEN_ID = "devglan-client"; static final String CLIENT_SECRET = "devglan-secret"; static final String GRANT_TYPE = "password"; static final String AUTHORIZATION_CODE = "authorization_code"; static final String REFRESH_TOKEN = "refresh_token"; static final String IMPLICIT = "implicit"; static final String SCOPE_READ = "read"; static final String SCOPE_WRITE = "write"; static final String TRUST = "trust"; static final int ACCESS_TOKEN_VALIDITY_SECONDS = 1*60*60; static final int FREFRESH_TOKEN_VALIDITY_SECONDS = 6*60*60; @Autowired private TokenStore tokenStore; @Autowired private UserApprovalHandler userApprovalHandler; @Autowired private AuthenticationManager authenticationManager; @Override public void configure(ClientDetailsServiceConfigurer configurer) throws Exception { configurer .inMemory() .withClient(CLIEN_ID) .secret(CLIENT_SECRET) .authorizedGrantTypes(GRANT_TYPE_PASSWORD, AUTHORIZATION_CODE, REFRESH_TOKEN, IMPLICIT ) .scopes(SCOPE_READ, SCOPE_WRITE, TRUST) .accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS). refreshTokenValiditySeconds(FREFRESH_TOKEN_VALIDITY_SECONDS); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.tokenStore(tokenStore).userApprovalHandler(userApprovalHandler) .authenticationManager(authenticationManager); } }

OAuth2 Resource Server Config

Resource in our context is the REST API which we have exposed for the crud operation.To access these resources, client must be authenticated.In real-time scenarios, whenever an user tries to access these resources, the user will be asked to provide his authenticity and once the user is authorized then he will be allowed to access these protected resources.

@EnableResourceServer: Enables a resource server

ResourceServerConfig.java
package com.devglan.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; import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler; @Configuration @EnableResourceServer public class ResourceServerConfig extends ResourceServerConfigurerAdapter { private static final String RESOURCE_ID = "resource_id"; @Override public void configure(ResourceServerSecurityConfigurer resources) { resources.resourceId(RESOURCE_ID).stateless(false); } @Override public void configure(HttpSecurity http) throws Exception { http. anonymous().disable() .authorizeRequests() .antMatchers("/users/**").authenticated() .and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler()); } }

Security Config

This class extends WebSecurityConfigurerAdapter and provides usual spring security configuration.Here, we are using bcrypt encoder to encode our passwords. You can try this online Bcrypt Tool to encode and match bcrypt passwords.Following configuration basically bootstraps the authorization server and resource server.

@EnableWebSecurity : Enables spring security web security support.

@EnableGlobalMethodSecurity: Support to have method level access control such as @PreAuthorize @PostAuthorize

SecurityConfig.java
package com.devglan.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; 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.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.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.approval.ApprovalStore; import org.springframework.security.oauth2.provider.approval.TokenApprovalStore; import org.springframework.security.oauth2.provider.approval.TokenStoreUserApprovalHandler; import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; import javax.annotation.Resource; @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Resource(name = "userService") private UserDetailsService userDetailsService; @Autowired private ClientDetailsService clientDetailsService; @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Autowired public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService) .passwordEncoder(encoder()); } @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .anonymous().disable() .authorizeRequests() .antMatchers("/api-docs/**").permitAll(); } @Bean public TokenStore tokenStore() { return new InMemoryTokenStore(); } @Bean @Autowired public TokenStoreUserApprovalHandler userApprovalHandler(TokenStore tokenStore){ TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler(); handler.setTokenStore(tokenStore); handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientDetailsService)); handler.setClientDetailsService(clientDetailsService); return handler; } @Bean @Autowired public ApprovalStore approvalStore(TokenStore tokenStore) throws Exception { TokenApprovalStore store = new TokenApprovalStore(); store.setTokenStore(tokenStore); return store; } @Bean public BCryptPasswordEncoder encoder(){ return new BCryptPasswordEncoder(); } @Bean public FilterRegistrationBean corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedOrigin("*"); config.addAllowedHeader("*"); config.addAllowedMethod("*"); source.registerCorsConfiguration("/**", config); FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source)); bean.setOrder(0); return bean; } }

Rest APIs

Following are the very basic REST APIs that we have exposed for testing purpose.

UserController.java
@RestController @RequestMapping("/users") public class UserController { @Autowired private UserService userService; @RequestMapping(value="/user", method = RequestMethod.GET) public List listUser(){ return userService.findAll(); } @RequestMapping(value = "/user", method = RequestMethod.POST) public User create(@RequestBody User user){ return userService.save(user); } @RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE) public String delete(@PathVariable(value = "id") Long id){ userService.delete(id); return "success"; } }

Now let us define the Userservice that is responsible for fetching user details from the database.Following is the implementation that spring will be using to validate user.

UserServiceImpl.java

@Service(value = "userService") public class UserServiceImpl implements UserDetailsService, UserService { @Autowired private UserDao userDao; public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException { User user = userDao.findByUsername(userId); if(user == null){ throw new UsernameNotFoundException("Invalid username or password."); } return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), getAuthority()); } private List getAuthority() { return Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN")); } public List findAll() { List list = new ArrayList<>(); userDao.findAll().iterator().forEachRemaining(list::add); return list; } }

Default Scripts

Following are the insert statements that are inserted when application starts.

INSERT INTO User (id, username, password, salary, age) VALUES (1, 'Alex123', '$2a$04$I9Q2sDc4QGGg5WNTLmsz0.fvGv3OjoZyj81PrSFyGOqMphqfS2qKu', 3456, 33); INSERT INTO User (id, username, password, salary, age) VALUES (2, 'Tom234', '$2a$04$PCIX2hYrve38M7eOcqAbCO9UqjYg7gfFNpKsinAxh99nms9e.8HwK', 7823, 23); INSERT INTO User (id, username, password, salary, age) VALUES (3, 'Adam', '$2a$04$I9Q2sDc4QGGg5WNTLmsz0.fvGv3OjoZyj81PrSFyGOqMphqfS2qKu', 4234, 45);

Testing the Application

Run Application.java as a java application.We will be using postman to test the OAuth2 implementation.

Generate AuthToken:In the header we have username and password as Alex123 and password respectively as Authorization header.As per Oauth2 specification, Access token request should use application/x-www-form-urlencoded.Following is the setup.

generate-oauth2-token

Once you make the request you will get following result.It has access token as well as refresh token.

oauth2-token-result Accessing Resource Without Token unauthorised Accessing Resource With Token access-restricted-resource Using refresh token to refresh the token

Usually, the token expiry time is very less in case of oAuth2 and you can use following API to refresh token once it is expired.

oauth2-refresh-token

Common Errors

I can see in the comment section that there are 2 errors which most of the readers have encountered.Hence,adding this section that ideally should help readers.

Full authentication is required to access this resource
{ "timestamp": 1513747665246, "status": 401, "error": "Unauthorized", "message": "Full authentication is required to access this resource", "path": "/oauth/token" }

It causes in case you have missed to add username/password under authorization section of postman.Under this section, select Type as Basic Auth and provide the credentials as devglan-client and devglan-secret and then make the request to url - http://localhost:8080/oauth/token to get the auth token.Following is the screenshot.

Full authentication is required to access this resource There is no client authentication. Try adding an appropriate authentication filter.
{ "error": "unauthorized", "error_description": "There is no client authentication. Try adding an appropriate authentication filter." }

In this case check your auth url.It should be - http://localhost:8080/oauth/token instead of http://localhost:8080/oauth/token/

Missing grant type.
{ "error": "invalid_request", "error_description": "Missing grant type" }

In this case you have missed adding grant_type in the request.Try adding it as password

Conclusion

In this tutorial we learned about securing REST API with OAUTH2 with implementation of resouce server and authorisation server. If you have anything that you want to add or share then please share it below in the comment section.You can download the source from github.

Further Reading on Spring Security

1. Angular Jwt Authentication

2. Spring Boot Jwt Auth

3. Spring Boot Security Rest Basic Authentication

4. Spring Boot Security Custom Form Login Example

5. Spring Boot Security Hibernate Login Example

6. Spring Jms Activemq Integration Example

7. Spring Mvc Angularjs Integration Example

Suggest more topics in suggestion section or write your own article and share with your colleagues.