Pages

Secure your REST API using Spring Security Oauth2

This tutorial shows how to secure your REST API using spring security oauth2. If you don't know anything about oauth2 please followup the below tutorials,

Hold on, if you really don't know the basic concepts of spring please look at it first.

OK, we are fine now. No more prerequisites.

Not like other tutorials this implementation is using custom JDBCTokenStore to store client id/secrets and user authentication details in MySQL DB.
Source code : https://github.com/rajithd/spring-security-oauth2-rest

Here I'm going to implement 3-legged oauth flow and please check the below diagram in order to understand this.




Lets start,

Step 1
Create database as oauth and then use import.sql file to create tables and add some seed data.
Before running make sure to change the db properties located in resources folder. (https://github.com/rajithd/spring-security-oauth2-rest/blob/master/src/main/resources/database.properties)

 create database oauth;  
 use oauth;  
 source <path/to/resorce/folder>/import.sql;  

Step 2
Build the project using maven

 mvn clean install -DskipTests  

Step 3
copy the war file into tomcat/webapps then start the tomcat

Fetch request_token

 http://localhost:8080/oauth2/oauth/token?grant_type=password&client_id=rajith-client-id&client_secret=12345&username=rajith&password=password  

Response
 {  
  access_token: "f833a754-0d6c-4595-92c8-99b202ea6dd4"  
  token_type: "bearer"  
  refresh_token: "967068eb-13d1-4d18-8dd8-b89b2124d5d6"  
  expires_in: 4  
  scope: "read trust write"  
 }  

Fetch access_token by submitting request_token


 http://localhost:8080/oauth2/oauth/token?grant_type=refresh_token&client_id=rajith-client-id&refresh_token=967068eb-13d1-4d18-8dd8-b89b2124d5d6&client_secret=12345  

Response

 {  
 access_token: "d78dd4c7-41c3-443d-a85e-3716ceefc66f"  
 token_type: "bearer"  
 refresh_token: "967068eb-13d1-4d18-8dd8-b89b2124d5d6"  
 expires_in: 4  
 scope: "read trust write"  
 }  

Accessing protected resource


 http://localhost:8080/oauth2/test/ateam?access_token=d78dd4c7-41c3-443d-a85e-3716ceefc66f  

You can use Authorization header to send the access token for protected resources.

 Authorization Bearer d78dd4c7-41c3-443d-a85e-3716ceefc66f  


Main configuration file is located in under webapps (spring-security.xml)

 <?xml version="1.0" encoding="UTF-8" ?>  
 <beans xmlns="http://www.springframework.org/schema/beans"  
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
     xmlns:oauth="http://www.springframework.org/schema/security/oauth2"  
     xmlns:sec="http://www.springframework.org/schema/security"  
     xsi:schemaLocation="http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-1.0.xsd  
     http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd  
     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">  
   <http pattern="/oauth/token" create-session="stateless" authentication-manager-ref="clientAuthenticationManager"  
      xmlns="http://www.springframework.org/schema/security">  
     <intercept-url pattern="/oauth/token" access="IS_AUTHENTICATED_FULLY"/>  
     <anonymous enabled="false"/>  
     <http-basic entry-point-ref="clientAuthenticationEntryPoint"/>  
     <!-- include this only if you need to authenticate clients via request parameters -->  
     <custom-filter ref="clientCredentialsTokenEndpointFilter" after="BASIC_AUTH_FILTER"/>  
     <access-denied-handler ref="oauthAccessDeniedHandler"/>  
   </http>  
   <!-- The OAuth2 protected resources are separated out into their own block so we can deal with authorization and error handling  
     separately. This isn't mandatory, but it makes it easier to control the behaviour. -->  
   <http pattern="/test/*" create-session="never" entry-point-ref="oauthAuthenticationEntryPoint"  
      access-decision-manager-ref="accessDecisionManager" xmlns="http://www.springframework.org/schema/security">  
     <anonymous enabled="false"/>  
     <intercept-url pattern="/test/*" access="ROLE_USER"/>  
     <custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER"/>  
     <access-denied-handler ref="oauthAccessDeniedHandler"/>  
   </http>  
   <bean id="oauthAuthenticationEntryPoint"  
      class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">  
     <property name="realmName" value="test"/>  
   </bean>  
   <bean id="clientAuthenticationEntryPoint"  
      class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">  
     <property name="realmName" value="test/client"/>  
     <property name="typeName" value="Basic"/>  
   </bean>  
   <bean id="oauthAccessDeniedHandler"  
      class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler"/>  
   <bean id="clientCredentialsTokenEndpointFilter"  
      class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">  
     <property name="authenticationManager" ref="clientAuthenticationManager"/>  
   </bean>  
   <bean id="accessDecisionManager" class="org.springframework.security.access.vote.UnanimousBased"  
      xmlns="http://www.springframework.org/schema/beans">  
     <constructor-arg>  
       <list>  
         <bean class="org.springframework.security.oauth2.provider.vote.ScopeVoter"/>  
         <bean class="org.springframework.security.access.vote.RoleVoter"/>  
         <bean class="org.springframework.security.access.vote.AuthenticatedVoter"/>  
       </list>  
     </constructor-arg>  
   </bean>  
   <authentication-manager id="clientAuthenticationManager" xmlns="http://www.springframework.org/schema/security">  
     <authentication-provider user-service-ref="clientDetailsUserService"/>  
   </authentication-manager>  
   <bean id="passwordEncoder"  
      class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder">  
     <constructor-arg name="strength" value="11"/>  
   </bean>  
   <authentication-manager alias="authenticationManager" xmlns="http://www.springframework.org/schema/security">  
     <authentication-provider user-service-ref="userService">  
       <password-encoder ref="passwordEncoder"/>  
     </authentication-provider>  
   </authentication-manager>  
   <bean id="clientDetailsUserService"  
      class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">  
     <constructor-arg ref="clientDetails"/>  
   </bean>  
   <!-- Used for the persistenceof tokens (currently an in memory implementation) -->  
   <bean id="tokenStore" class="org.springframework.security.oauth2.provider.token.store.JdbcTokenStore">  
     <constructor-arg ref="dataSource"/>  
   </bean>  
   <bean id="tokenServices" class="org.springframework.security.oauth2.provider.token.DefaultTokenServices">  
     <property name="tokenStore" ref="tokenStore"/>  
     <property name="supportRefreshToken" value="true"/>  
     <property name="clientDetailsService" ref="clientDetails"/>  
   </bean>  
   <bean id="oAuth2RequestFactory"  
      class="org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory">  
     <constructor-arg ref="clientDetails"/>  
   </bean>  
   <bean id="userApprovalHandler"  
      class="org.springframework.security.oauth2.provider.approval.TokenStoreUserApprovalHandler">  
     <property name="tokenStore" ref="tokenStore"/>  
     <property name="requestFactory" ref="oAuth2RequestFactory"/>  
   </bean>  
   <!-- authorization-server aka AuthorizationServerTokenServices is an interface that defines everything necessary for token management -->  
   <oauth:authorization-server client-details-service-ref="clientDetails" token-services-ref="tokenServices"  
                 user-approval-handler-ref="userApprovalHandler">  
     <oauth:authorization-code/>  
     <oauth:implicit/>  
     <oauth:refresh-token/>  
     <oauth:client-credentials/>  
     <oauth:password/>  
   </oauth:authorization-server>  
   <oauth:resource-server id="resourceServerFilter" resource-id="test" token-services-ref="tokenServices"/>  
   <bean id="clientDetails"  
      class="org.ateam.common.service.ClientService">  
   </bean>  
   <sec:global-method-security pre-post-annotations="enabled" proxy-target-class="true">  
     <!--you could also wire in the expression handler up at the layer of the http filters. See https://jira.springsource.org/browse/SEC-1452 -->  
     <sec:expression-handler ref="oauthExpressionHandler"/>  
   </sec:global-method-security>  
   <oauth:expression-handler id="oauthExpressionHandler"/>  
   <oauth:web-expression-handler id="oauthWebExpressionHandler"/>  
 </beans>  

Clone the repo and change it accordingly for your needs. Happy coding!

26 comments:

  1. what is difference between first and second access_tokens?

    ReplyDelete
  2. Hi sir,

    Thank you for your post.

    Can you help me to create Spring Java config of OAuth2 server?

    ReplyDelete
  3. hi, thanks for the great blog!
    i wanna know how to set the expires_in for longer than 5 seconds? I've modified the data but still everytime i restart the server, it got back to 5 seconds ?

    ReplyDelete
    Replies
    1. Simplest way is to change value of "hibernate.hbm2ddl.auto" create to update

      Delete
  4. When try to get data from the server, show the follow message:
    {
    "error": "access_denied"
    "error_description": "Access is denied"
    }
    All the configuration is ok

    ReplyDelete
    Replies
    1. Hi Ronald I am also facing same issue. Please let me know if you could solve it.

      Delete
    2. Hi Ronald I am also facing same issue. Please let me know if you could solve it.

      Delete
    3. This comment has been removed by the author.

      Delete
  5. Im also facing the same issue..

    ReplyDelete
  6. facing same issue...also confused with first and second access token.

    ReplyDelete
  7. This comment has been removed by the author.

    ReplyDelete
  8. This comment has been removed by the author.

    ReplyDelete
  9. Thanks for a great post!! Im also getting access denied... Did any figure out why it is happening. The implementation generates and stores tokens correctly but no access?

    ReplyDelete
  10. I implemented in the exact way you have mentioned but unfortunately getting bad credential password not matching issue showing.

    ReplyDelete
  11. I implemented in the exact way you have mentioned but unfortunately getting bad credential password not matching issue showing.

    ReplyDelete
  12. When try to get data from the server, show the follow message:
    {
    "error": "access_denied"
    "error_description": "Access is denied"
    }
    All the configuration is ok

    ReplyDelete
  13. When try to get data from the server, show the follow message:
    {
    "error": "access_denied"
    "error_description": "Access is denied"
    }
    All the configuration is ok

    ReplyDelete
  14. Also getting the below error

    {
    "error": "access_denied",
    "error_description": "Access is denied"
    }

    ReplyDelete
  15. When try to get data from the server, show the follow message:
    {
    "error": "access_denied"
    "error_description": "Access is denied"
    }

    ReplyDelete
  16. Got this error with valid access token.

    {
    "error": "access_denied",
    "error_description": "Access is denied"
    }

    Any solution ??

    ReplyDelete
  17. I am also getting any found solution plz help....

    {
    "error": "access_denied",
    "error_description": "Access is denied"
    }

    ReplyDelete
  18. Remove alias from .
    Use <authentication-manager id="authenticationManager"

    ReplyDelete