Introduction
OAuth2 is an open standard for authorization. OAuth provides client applications a 'secure delegated access' to server resources on behalf of a resource owner.Designed specifically to work with HTTP, OAuth2 essentially allows access token to be issued to third-party clients by an authorization server, with the approval of the resource owner, or end-user. The client then uses the access token to access the protected resources hosted by the resource server. OAuth 2 provides authorization flows for web and desktop applications, and mobile devices.
[Please Refer: http://en.wikipedia.org/wiki/OAuth to know more about OAuth/OAuth2]
OAuth2 Flow
OAuth defines four roles:
- Resource Owner -The resource owner is the user who authorizes an application to access their account. The application's access to the user's account is limited to the "scope" of the authorization granted (e.g. read or write access).
- Client -The client is the application that wants to access the user's account
- Resource Server -The resource server hosts the protected user accounts
- Authorization Server -The authorization server verifies the identity of the user then issues access tokens to the application.
The entire flow works like below -
Many articles are available for OAuth2, describing the flow and concepts we can refer them so better we jump into code.
Code Example
Spring has added OAuth2 support with spring security. Spring Security OAuth2 support was available with xml based configuration. Java Config support for Spring security OAuth2 has been added recent past. Lets see the code we need to write to enable OAuth2 Java Config support for our spring projects.
Authorization Server Code:
package com.test.security.config;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
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.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
DataSource dataSource;
@Autowired
private AuthenticationManager authenticationManager;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource); // If you want to maintain client details is database
//Section below for in-memory clients
/*clients.inMemory().withClient("<client_id>")
.resourceIds(<resource_id>)
.authorizedGrantTypes("authorization_code", "implicit")
.authorities("<roles>")
.scopes("read", "write")
.secret("secret")
.and()
.withClient("<client_id>")
.resourceIds(<resource_id>)
.authorizedGrantTypes("authorization_code", "implicit")
.authorities("<roles>")
.scopes("read", "write")
.secret("secret")
.redirectUris(<redirect_url>)
.and()
.withClient("<client_id>")
.resourceIds(<resource_id>)
.authorizedGrantTypes("authorization_code", "client_credentials")
.authorities("ROLE_CLIENT")
.scopes("read", "trust")
.redirectUris("http://anywhere?key=value")
.and()
.withClient("my-trusted-client")
.authorizedGrantTypes("password","refresh_token")
.authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
.scopes("read", "write", "trust")
.accessTokenValiditySeconds(60)
.refreshTokenValiditySeconds(600)
.and()
.withClient("my-trusted-client-with-secret")
.authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
.authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
.scopes("read", "write", "trust")
.secret("somesecret")
.and()
.withClient("my-less-trusted-client")
.authorizedGrantTypes("authorization_code", "implicit")
.authorities("ROLE_CLIENT")
.scopes("read", "write", "trust")
.and()
.withClient("my-less-trusted-autoapprove-client")
.authorizedGrantTypes("implicit")
.authorities("ROLE_CLIENT")
.scopes("read", "write", "trust")
.autoApprove(true);*/
}
@Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource); // access and refresh tokens will be maintain in database
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore()).authenticationManager(authenticationManager);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.allowFormAuthenticationForClients();
}
}
Resource Server Code:
package com.test.security.config;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
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.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
@Configuration
@EnableResourceServer
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
private static final String HU_REST_RESOURCE_ID = "rest_api";
@Autowired
@Qualifier("dataSource")
DataSource dataSource;
@Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(HU_REST_RESOURCE_ID).stateless(false);
}
@Override
public void configure(HttpSecurity http) throws Exception {
//define URL patterns to enable OAuth2 security
http.
requestMatchers().antMatchers("/api/v1/**").and().
authorizeRequests().antMatchers("/api/v1/**").access("#oauth2.hasScope('read') or (!#oauth2.isOAuth() and hasRole('ROLE_USER'))");
}
}
Authentication Config Code:
package com.test.security.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter;
import com.test.aouth2.service.OAuthUserDetailService;
import com.test.config.security.util.CommonPasswordEncoder;
@Configuration
public class GlobalAuthenticationConfig extends GlobalAuthenticationConfigurerAdapter {
@Autowired
private OAuthUserDetailService oAuthUserDetailService; // This Class extends UserDetails Service
//and overrides loadUserByUsername()
@Autowired
private CommonPasswordEncoder commonPasswordEncoder; //password Encoder
@Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(oAuthUserDetailService).passwordEncoder(commonPasswordEncoder);
}
}
Password Encoder Code:
package com.test.config.security.util;
import org.springframework.security.crypto.bcrypt.BCrypt;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
@Component
public class CommonPasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence rawPassword) {
String rawpswd = (String) rawPassword;
return BCrypt.hashpw(rawpswd, BCrypt.gensalt());
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
String rawpasswd = (String) rawPassword;
boolean status = BCrypt.checkpw(rawpasswd, encodedPassword);
return status;
}
}
@Component
public class OAuthUserDetailService implements UserDetailsService {
@Autowired
private UserService userService; // Repository methods are here
@Override
public UserDetails loadUserByUsername(String userName)
throws UsernameNotFoundException {
User user = userService.findByUserName(userName);
if(user == null){
throw new UsernameNotFoundException("UserName "+userName+" not found");
}
return new OAuthUser(user);
}
}
public class OAuthUser extends User implements UserDetails {
//Implementation details
}
Testing
Lets assume that we have written a REST service like below -
@RestController
@RequestMapping({ "/api/v1/", "" })
public class HomeController {
@RequestMapping(value = "/secured/hello", method = {RequestMethod.POST,RequestMethod.GET})
public String hello() {
return "Hello World !!";
}
}
Access Token
To get the access token sent a request like below from your terminal
$ curl <client_id>:@host:port/oauth/token -d grant_type=password -d username=<db_user_name> -d password=<db_user_password>
It will give you a response like
{"access_token”:”<ACCESS_TOKEN>”,”token_type":"bearer","expires_in":2592000,"refresh_token”:”<REFRESH_TOKEN>”,”scope":"read" ……}
Access Secured Resource
curl -H "Authorization:Bearer <ACCESS_TOKEN>“ http://localhost:8080/secured/hello
It will print Hello World !! in the terminal
Hi Badal, Thanks for the nice post.! Could you please send me the source code of this example ? My mail id is pkc.mvit@gmail.com.
ReplyDeleteThanks in advance..!
Hi,
DeleteThanks a lot you can find code here: -
https://github.com/badalb/spring-security-oauth2-javaconfig
Regards,
Badal
Thanks a lot for this great post. Could you please send me the source code in my email : edonzogaj1@gmail.com , Thanks a lot
ReplyDeleteHi,
DeleteThanks a lot you can find code here: -
https://github.com/badalb/spring-security-oauth2-javaconfig
Regards,
Badal
Thanks a lot for this great post. Could you please send me the source code in my email : edonzogaj1@gmail.com , Thanks a lot
ReplyDeleteHi,
DeleteThanks a lot you can find code here: -
https://github.com/badalb/spring-security-oauth2-javaconfig
Regards,
Badal
Great article , could you please send me the source code to " alaayameen2012@gmail.com
ReplyDelete, thanks
Hi,
DeleteThanks a lot you can find code here: -
https://github.com/badalb/spring-security-oauth2-javaconfig
Regards,
Badal
Thanks a lot for this great post. Could you please send me the source code in my email : arunn472@gmail.com , Thanks a lot
ReplyDeleteHi,
DeleteThanks a lot you can find code here: -
https://github.com/badalb/spring-security-oauth2-javaconfig
Regards,
Badal
Hi
ReplyDeleteThanks for this post.
I'm trying to use this source code, but an error occurred while executing
a command to get the token:
{"timestamp": 1445293690462, "status": 401, "error": "Unauthorized", "message": "In AuthenticationProvider found fororg.springframework.security.authentication.UsernamePasswordAuthenticationToken","path":"/oauth/token"}* Connection # 0 to host localhost left intact
Could you help me please ?
Hi,
DeletePlease refer my github code here:
https://github.com/badalb/spring-security-oauth2-javaconfig
If you face any difficulties please do let me know.
Regards,
Badal
HELLO,
ReplyDeleteI have created three of project in eclipse. I added all dependency to POM.XML. I fixed all errors. There are no any compile time error, but when i run project i am getting below errors. If you can help then it will be very helpful to me.
Error -
Exception in thread "background-preinit" java.lang.NoClassDefFoundError: org/springframework/boot/autoconfigure/BackgroundPreinitializer$MBeanFactoryInitializer
at org.springframework.boot.autoconfigure.BackgroundPreinitializer$1.run(BackgroundPreinitializer.java:61)
at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.ClassNotFoundException: Illegal access: this web application instance has been stopped already. Could not load [org.springframework.boot.autoconfigure.BackgroundPreinitializer$MBeanFactoryInitializer]. The following stack trace is thrown for debugging purposes as well as to attempt to terminate the thread which caused the illegal access.
at org.apache.catalina.loader.WebappClassLoaderBase.checkStateForClassLoading(WebappClassLoaderBase.java:1343)
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1206)
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1167)
... 2 more
Caused by: java.lang.IllegalStateException: Illegal access: this web application instance has been stopped already. Could not load [org.springframework.boot.autoconfigure.BackgroundPreinitializer$MBeanFactoryInitializer]. The following stack trace is thrown for debugging purposes as well as to attempt to terminate the thread which caused the illegal access.
at org.apache.catalina.loader.WebappClassLoaderBase.checkStateForResourceLoading(WebappClassLoaderBase.java:1353)
at org.apache.catalina.loader.WebappClassLoaderBase.checkStateForClassLoading(WebappClassLoaderBase.java:1341)
... 4 more
Hi sir,
ReplyDeleteThanks for you post.
I'm try create OAuth2 server(Tokens & data stored on database).
I'm already complete OAuth2 data services, can you help me to complete OAuth2 server Spring Java Configuration?
Thank you in advance.
Best regards,
Ghislain MAMAT
Hi Badal,
ReplyDeleteWe are facing a strange situation here. We are able to get the token by using oauth/token url in windows machine. But the same thing is throwing an error in ubuntu (linux) machine saying POST request not supported. Could you please help me in this.
Hi,
DeleteThanks for posting the issue you are facing. Could you please paste the log you are getting?
Regards,
Badal
Hi Badal,
ReplyDeleteI have an application that has RestFul web services (resources) which are protected using spring OAuth 2.0. Now I need to configure OAuth so as to allow only certain clients (client ID) to access certain web services (resources). Could some one please guide me on how this can be done? Thank you in advance.
We are able to get the token by using oauth/token url . But the token generated and the token which is inserted in DB both are different.
ReplyDeletePlease refer https://github.com/badalb/spring-security-oauth2-javaconfig . Clone the repo and try to run locally. It will work
DeleteGreat post !! I am glad to find your impressive way of writing the post.Thanks for sharing
ReplyDeletebest website development services | best seo services