This tutorial shows you how to Secure Spring Rest API Using Spring Security Oauth2 Example. OAuth2 is an authentication framework that allows third-party applications to grant limited access to a HTTP service, either on behalf of a resource owner or by allowing the third-party application to obtain access on its own behalf.
Table of contents:
1. OAuth2 Introduction
2. Configure Resource Server
3. Configure Authorization Server
4. Spring Security Configuration
5. Spring Rest Controller
6. Deploy Secure Spring Rest API Using Spring Security Oauth2 Example
Other interesting posts you may like
OAuth2 Introduction
OAuth2 defines four roles:
Resource Owner: One user or yourself or an entity capable of granting access to a protected resource.
Resource Server: One server that stores protected resources, this server supports access token for request and response.
Authorization Server: One server that distributes access tokens to client after successfully authentication the resource owner and obtaining authorization.
Client: An application making requests to protected resources on behalf of the owner. It can be a web app server, a mobile app, a client side application. In this example, we are using DHC REST Client like a client.
We don’t introduce OAuth2 deeply here. You can refer to the completely OAuth2 tutorial. We will focus on how to create Spring Rest API Using Spring Security Oauth2 Example step by step.
Configure Resource Server
Resource Server stores the protected resources that client needs. We configure resource server by creating the custom class of ResourceServerConfigurer. Here is the code snippet.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
package com.javabycode.security; 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 OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter { private static final String RESOURCE_ID = "SPRING_REST_API"; @Override public void configure(ResourceServerSecurityConfigurer resources) { resources.resourceId(RESOURCE_ID).stateless(false); } @Override public void configure(HttpSecurity http) throws Exception { http. anonymous().disable() .requestMatchers().antMatchers("/fruits/**") .and().authorizeRequests() .antMatchers("/fruits/**").access("hasRole('ADMIN')") .and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler()); } } |
Let’s dig deeper:
Following the above code, Resources are located on path /fruits/. @EnableResourceServer annotation eanbles a Spring Security filter that authenticates requests. These incoming requests have to include OAuth2 token.
Configure Authorization Server
Authorization server is responsible for verifying user credentials. And Authorization server will provide tokens (access-token or refresh-token) if the user credentials are valid. It also stores some other information such as registered client, access scopes and grant types. We configure the Authorization Server with the below code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
package com.javabycode.security; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; 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.approval.UserApprovalHandler; import org.springframework.security.oauth2.provider.token.TokenStore; @Configuration @EnableAuthorizationServer public class AuthServerOAuth2Config extends AuthorizationServerConfigurerAdapter { private static String REALM="EXAMPLE_REALM"; @Autowired private TokenStore tokenStore; @Autowired private UserApprovalHandler handler; @Autowired @Qualifier("authenticationManagerBean") private AuthenticationManager authManager; @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("trusted-client") .authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit") .authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT") .scopes("read", "write", "trust") .secret("secret") .accessTokenValiditySeconds(300).//invalid after 5 minutes. refreshTokenValiditySeconds(600);//refresh after 10 minutes. } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.tokenStore(tokenStore).userApprovalHandler(handler) .authenticationManager(authManager); } @Override public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { oauthServer.realm(REALM+"/client"); } } |
Let’s dig deeper:
@EnableAuthorizationServer annotation is used to enable an Authorization Server in the current application context. The configure(ClientDetailsServiceConfigurer clients) method configures the ClientDetailsService, e.g. declaring individual clients and their properties. The method configures the non-security features of the Authorization Server endpoints, like token store, token customizations, user approvals and grant types. The method configures the security of the Authorization Server, which means in practical terms the /oauth/token endpoint.
Spring Security Configuration
We configure spring secruity for the application by creating the WebSecurityConfigurerAdapter class that extends from the WebSecurityConfigurerAdapter class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
package com.javabycode.security; import org.springframework.beans.factory.annotation.Autowired; 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.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.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; @Configuration @EnableWebSecurity public class OAuth2SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private ClientDetailsService clientService; @Autowired public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("javabycode").password("123456").roles("USER") .and() .withUser("admin").password("admin123").roles("ADMIN"); } @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .anonymous().disable() .authorizeRequests() .antMatchers("/oauth/token").permitAll(); } @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @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(clientService)); handler.setClientDetailsService(clientService); return handler; } @Bean @Autowired public ApprovalStore approvalStore(TokenStore tokenStore) throws Exception { TokenApprovalStore store = new TokenApprovalStore(); store.setTokenStore(tokenStore); return store; } } |
The globalUserDetails(AuthenticationManagerBuilder auth) method configures resource owner ( javabycode and admin user). The configure(HttpSecurity http) method configures security for the endpoint /oauth/token. We get tokens (access token and refresh token) via the endpoint /oauth/token.
Spring Rest Controller
We reuse the rest controller named FruitController from the previous post Spring Security Basic Authentication Example. You can refer it on that post or look into in source code attachment. And we don’t mention it here.
Deploy Secure Spring Rest API Using Spring Security Oauth2 Example
After building the project by maven we deploy the file war on application server (Tomcat 8 for example). We are using DHC REST client to test rest api with oauth2.
Access the URL localhost:8080/spring-oauth2-example/fruits without any authorization. The message is 401 Unauthorized like below image:
Get tokens using HTTP POST on endpoint /oauth/token with grant_type=password and resource owner like the URL localhost:8080/spring-oauth2-example/oauth/token?grant_type=password&username=admin&password=admin123. One more, you have to set Header Authorization with client credential myRestClient/P@ssw0rd. You will receive both access token and refresh token. Here is the result screen:
Use the access token to request resources. Notices that the access token is valid for 2 minutes. Here is URL we you need localhost:8080/spring-oauth2-example/fruits?access_token=454b4db8-644e-4e72-bd8f-0b13f5c859fe and the screen displays like this:
This access token is invalid after 2 minutes. We run this localhost:8080/spring-oauth2-example/fruits?access_token=454b4db8-644e-4e72-bd8f-0b13f5c859fe again to see this case
We get new access token by submitting a post with refresh-token. The URL looks like this: http:/localhost:8080/spring-oauth2-example/oauth/token?grant_type=refresh_token&refresh_token=5168074a-fb24-46c2-bb43-786ebd01d00f
That’s all on the tutorial Secure Spring Rest API Using Spring Security Oauth2 Example.
Download complete source code, click link below
Spring-Rest-API-Using-Spring-Security-Oauth2-Example.zip (2408 downloads)
Believe me this is the best Spring Oauth2 step by step tutorial on the web
This is the best thing I’ve read so far about oauth2 (Spring).
Thank you very much!
Good post. I certainly appreciate this site. Continue the good work!
I deployed my spring rest api + spring security with oAuth2 in a tomcat 7.
I created a UsersRestController with a single endpoint GET /users that fetches all the users from the db.
I configured the security exactly how you did in your example:
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID = “my_rest_api”;
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID).stateless(false);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.
anonymous().disable()
.requestMatchers().antMatchers(“/users/**”)
.and()
.authorizeRequests().antMatchers(“/users/**”).access(“hasRole(‘ADMIN’)”)
.and()
.exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
}
}
The thing is that when I call GET on the /users the response is OK is the list of users. It should complain about security but I don’t understand why it works :). Do you have an idea?
This is from the log of the server:
INFO – questMappingHandlerMapping – Mapped “{[/users],methods=[GET],produces=[application/json]}” onto public ro.lvc.users.dto.UsersListDTO ro.lvc.users.controller.UsersController.getAllUsers()
INFO – questMappingHandlerAdapter – Looking for @ControllerAdvice: Root WebApplicationContext: startup date [Tue Feb 07 17:26:26 CET 2017]; root of context hierarchy
INFO – workEndpointHandlerMapping – Mapped “{[/oauth/authorize]}” onto public org.springframework.web.servlet.ModelAndView org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.authorize(java.util.Map,java.util.Map,org.springframework.web.bind.support.SessionStatus,java.security.Principal)
INFO – workEndpointHandlerMapping – Mapped “{[/oauth/authorize],methods=[POST],params=[user_oauth_approval]}” onto public org.springframework.web.servlet.View org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint.approveOrDeny(java.util.Map,java.util.Map,org.springframework.web.bind.support.SessionStatus,java.security.Principal)
INFO – workEndpointHandlerMapping – Mapped “{[/oauth/token],methods=[GET]}” onto public org.springframework.http.ResponseEntity org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.getAccessToken(java.security.Principal,java.util.Map) throws org.springframework.web.HttpRequestMethodNotSupportedException
INFO – workEndpointHandlerMapping – Mapped “{[/oauth/token],methods=[POST]}” onto public org.springframework.http.ResponseEntity org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken(java.security.Principal,java.util.Map) throws org.springframework.web.HttpRequestMethodNotSupportedException
INFO – workEndpointHandlerMapping – Mapped “{[/oauth/check_token]}” onto public java.util.Map org.springframework.security.oauth2.provider.endpoint.CheckTokenEndpoint.checkToken(java.lang.String)
INFO – workEndpointHandlerMapping – Mapped “{[/oauth/confirm_access]}” onto public org.springframework.web.servlet.ModelAndView org.springframework.security.oauth2.provider.endpoint.WhitelabelApprovalEndpoint.getAccessConfirmation(java.util.Map,javax.servlet.http.HttpServletRequest) throws java.lang.Exception
INFO – workEndpointHandlerMapping – Mapped “{[/oauth/error]}” onto public org.springframework.web.servlet.ModelAndView org.springframework.security.oauth2.provider.endpoint.WhitelabelErrorEndpoint.handleError(javax.servlet.http.HttpServletRequest)
INFO – DefaultSecurityFilterChain – Creating filter chain: OrRequestMatcher [requestMatchers=[Ant [pattern=’/oauth/token’], Ant [pattern=’/oauth/token_key’], Ant [pattern=’/oauth/check_token’]]], [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@60264e48, org.springframework.security.web.context.SecurityContextPersistenceFilter@4db429f, org.springframework.security.web.header.HeaderWriterFilter@7d8784cd, org.springframework.security.web.authentication.logout.LogoutFilter@8e40e61, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@645525e, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@322b33c3, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@39a9fc93, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@2009cdc5, org.springframework.security.web.session.SessionManagementFilter@33bb6d1e, org.springframework.security.web.access.ExceptionTranslationFilter@3340d40b, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@2530b288]
INFO – DefaultSecurityFilterChain – Creating filter chain: OrRequestMatcher [requestMatchers=[Ant [pattern=’/users/**’]]], [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@3ce96d6a, org.springframework.security.web.context.SecurityContextPersistenceFilter@22080b2a, org.springframework.security.web.header.HeaderWriterFilter@19359d1a, org.springframework.security.web.authentication.logout.LogoutFilter@35515a95, org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter@71ac4640, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@4dcfcb6c, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@78a75282, org.springframework.security.web.session.SessionManagementFilter@1f9a542f, org.springframework.security.web.access.ExceptionTranslationFilter@360b346b, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@44847baf]
INFO – DefaultSecurityFilterChain – Creating filter chain: org.springframework.security.web.util.matcher.AnyRequestMatcher@1, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@54dfc26c, org.springframework.security.web.context.SecurityContextPersistenceFilter@500ef978, org.springframework.security.web.header.HeaderWriterFilter@a020d0a, org.springframework.security.web.authentication.logout.LogoutFilter@584e61bb, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@4ab974a4, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@50c94aa4, org.springframework.security.web.session.SessionManagementFilter@7b7e6e28, org.springframework.security.web.access.ExceptionTranslationFilter@6e3e8267, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@4fdfce7a]
It seems your application is not enabled Authorization on server side. Let’s check all annotation in java class such as the post did
Hi did you fix it?
Thank you very much for this post.
@Configuration
@EnableResourceServer
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID = “SPRING_REST_API”;
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID).stateless(false);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.anonymous().disable()
.requestMatchers().antMatchers(“/fruits/**”)
.and().authorizeRequests()
.antMatchers(“/fruits/**”).access(“hasRole(‘ADMIN’)”)
.and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
}
}
Hello guys, may I ask where to put the “myRestClient/P@ssw0rd”.
Still can’t access
/fruits
and I’m always unauthorized.Thanks for the help guys.
You have to enable Header Authorization by click the label “set an authorization”, then put the “myRestClient/P@ssw0rd” into Authorization popup
I could not resist commenting. Perfectly written!
Hi nice tutorial. How to send the user credentials and tokens as request headers instead of url parameters?
Can u help me
You can try this with HttpClient like below:
String plainCredential = “your-username:your-password”;
String encoding = new String(Base64.encodeBase64(plainCredential.getBytes()));
HttpPost httppost = new HttpPost(“http://host:port/test/login”);
httppost.setHeader(“Authorization”, “Basic ” + encoding);
System.out.println(“executing request ” + httppost.getRequestLine());
HttpResponse response = httpclient.execute(httppost);
HttpEntity entity = response.getEntity();
i am getting 401 Unauthorized .
unable to use localhost:8080/spring-oauth2-example/oauth/token?grant_type=password&username=admin&password=admin123.i have to set Header Authorization
Try this with HttpClient like below
String plainCredential = “your-username:your-password”;
String encoding = new String(Base64.encodeBase64(plainCredential.getBytes()));
HttpPost httppost = new HttpPost(“http://host:port/test/login”);
httppost.setHeader(“Authorization”, “Basic ” + encoding);
System.out.println(“executing request ” + httppost.getRequestLine());
HttpResponse response = httpclient.execute(httppost);
HttpEntity entity = response.getEntity();
In which class i have to you
You must have a client. you can create the client using Spring RESTFul Client
i am getting Response Empty.why
Hey, great tutorial but in my case it is showing bad credentials
in which part of the code did access token generated.
+1 for excellent article.
I have one question. What is the purpose of following two methods that you have in here?
@Bean
@Autowired
public TokenStoreUserApprovalHandler userApprovalHandler(TokenStore tokenStore){
TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler();
handler.setTokenStore(tokenStore);
handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientService));
handler.setClientDetailsService(clientService);
return handler;
}
@Bean
@Autowired
public ApprovalStore approvalStore(TokenStore tokenStore) throws Exception {
TokenApprovalStore store = new TokenApprovalStore();
store.setTokenStore(tokenStore);
return store;
}
I don’t get why you would need it.
I followed everything, but i am getting Bad Credentials
this is the request
POST /oauth/token?grant_type=password&username=admin&password=admin123 HTTP/1.1
Host: localhost:9999
Authorization: Basic bXlSZXN0Q2xpZW50OlBAc3N3MHJk
Cache-Control: no-cache
Postman-Token: 972b66a8-e028-d40f-3d6f-71c9864e0def