Spring Boot Security OAuth2 Example(Bcrypt Encoder)

By Dhiraj Ray, 20 October,2017   | Last updated on: 22 October,2017
1335

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.

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.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 SCOPE_READ = "read"; static final String SCOPE_WRITE = "write"; 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) .scopes(SCOPE_READ, SCOPE_WRITE) .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"; } }

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

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.

generate-auth-token Accessing Resource Without Token unauthorised Accessing Resource With Token access-restricted-resource

Conclusion

I hope this article served you that you were looking for. If you have anything that you want to add or share then please share it below in the comment section.

Download the source

References

spring-security-oauth

an-introduction-to-oauth-2

authentication/

Google OAuth2

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

Is this page helpful to you? Please give us your feedback below. We would love to hear your thoughts on these articles, it will help us improve further our learning process.

Further Reading: