Java and J2EE Tutorials, Jsp and Servlet Tutorials, Spring MVC, Solr, XML, JSON Examples, Hibernate & Struts 2 Hello World projects



Saturday, 18 October 2014

Securing Restful Web Services with Spring Security and OAuth2 (Spring Security + OAuth2 + Spring Rest)

OAuth (Open Authentication) is an open standard or kind of protocol that lets one site to share its content with some other site without sharing credentials.

The source code of this application can be found on Git Hub: Source Code

In this post we will discuss how to secure Restful Web Services using Spring security and OAuth2, we will use Spring Security to validate a user on server and OAuth to manage authentication tokens to be used in communication. After applying this implementation only authenticated users and applications will get a valid access token from OAuth and using that token the user can access authorized API’s on server.


The flow of application will go something like this:

1) User sends a GET request to server with five parameters: grant_type, username, password, client_id, client_secret; something like this:
http://localhost:8080/SpringRestSecurityOauth/oauth/token?grant_type=password&client_id=restapp&client_secret=restapp&username=beingjavaguys&password=spring@java


2) Server validates the user with help of spring security, and if the user is authenticated, OAuth generates a access token and send sends back to user in following format.

{
"access_token": "22cb0d50-5bb9-463d-8c4a-8ddd680f553f",
"token_type": "bearer",
"refresh_token": "7ac7940a-d29d-4a4c-9a47-25a2167c8c49",
"expires_in": 119
}
Here we got access_token for further communication with server or to get some protected resourses(API’s), it mentioned a expires_in time that indicates the validation time of the token and a refresh_token that is being used to get a new token when token is expired.


3) We access protected resources by passing this access token as a parameter, the request goes something like this:
http://localhost:8080/SpringRestSecurityOauth/api/users/?access_token=8c191a0f-ebe8-42cb-bc18-8e80f2c4238e

Here http://localhost:8080/SpringRestSecurityOauth is the server path, and /api/users/ Is an API URL that returns a list of users and is being protected to be accessed.


4) If the token is not expired and is a valid token, the requested resources will be returned.

5) In case the token is expired, user needs to get a new token using its refreshing token that was accepted in step(2). A new access token request after expiration looks something like this:
http://localhost:8080/SpringRestSecurityOauth/oauth/token?grant_type=refresh_token&client_id=restapp&client_secret=restapp&refresh_token=7ac7940a-d29d-4a4c-9a47-25a2167c8c49
And you will get a new access token along with a new refresh token.


Integrating Spring Security with OAuth2 to secure Restful Web Services.

In this section we will see how the application works and how to integrate Spring Security with OAuth2, so for now we have a simple Spring Rest structure that returns a list of users for authenticated user, we have a simple spring controller that’s taking the responsibility:

More on @ResponseBody : Here

\src\main\java\com\beingjavaguys\controllers\RestController.java
package com.beingjavaguys.controllers;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import com.beingjavaguys.models.User;
import com.beingjavaguys.services.DataService;

/**
 * @author Nagesh.Chauhan
 *
 */
@Controller
@RequestMapping("/api/users")
public class RestController {

 @Autowired
 DataService dataService;

 @RequestMapping(value = "/", method = RequestMethod.GET)
 @ResponseBody
 public List list() {
  return dataService.getUserList();

 }
}

The code is self explainer, we are having an URI (/api/users/) to be accessed by a GET request and it will return a list of users as a response.


We are using simple service structure to get data, for now the data is being returned in a harcoded way but it can be kind of dynamic of from database.

More on Spring Rest Web Services : Here

\src\main\java\com\beingjavaguys\services\DataService.java
package com.beingjavaguys.services;

import java.util.List;

import com.beingjavaguys.models.User;
/**
 * @author Nagesh.Chauhan
 *
 */
public interface DataService {
 public List getUserList();
}


\src\main\java\com\beingjavaguys\services\DataServiceImpl.java We have created three users and added them to a list, this list will be accessed to authenticated users only with the help of auhentication OAuth token.
package com.beingjavaguys.services;

import java.util.ArrayList;
import java.util.List;

import org.springframework.stereotype.Service;

import com.beingjavaguys.models.User;
/**
 * @author Nagesh.Chauhan
 *
 */
@Service
public class DataServiceImpl implements DataService {

 @Override
 public List getUserList() {
  
  // preparing user list with few hard coded values
  List userList = new ArrayList();
  
  userList.add(new User(1, "user_a", "user_a@example.com", "9898989898"));
  userList.add(new User(2, "user_b", "user_b@example.com", "9767989898"));
  userList.add(new User(3, "user_c", "user_c@example.com", "9898459898"));
  
  return userList;
 }

}



Dependencies required to integrate Spring MVC , Spring Security and OAuth2

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.beingjavaguys.sample</groupId>
 <artifactId>SpringRestSecurityOauth</artifactId>
 <packaging>war</packaging>
 <version>1.0-SNAPSHOT</version>
 <name>SpringRestSecurityOauth Maven Webapp</name>
 <url>http://maven.apache.org</url>
 <!-- @author Nagesh.Chauhan(neel4soft@gmail.com) -->
 <properties>
  <spring.version>4.0.7.RELEASE</spring.version>
  <log4j.version>1.2.17</log4j.version>
  <jdk.version>1.7</jdk.version>
  <context.path>SpringRestSecurityOauth</context.path>
  <spring.security.version>3.2.5.RELEASE</spring.security.version>
 </properties>
 <build>
  <finalName>${pom.artifactId}</finalName>
  <plugins>
   <plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
     <source>${jdk.version}</source>
     <target>${jdk.version}</target>
    </configuration>
   </plugin>
  </plugins>
 </build>
 <dependencies>

  <dependency>
   <groupId>org.apache.commons</groupId>
   <artifactId>commons-io</artifactId>
   <version>1.3.2</version>
  </dependency>

  <!-- log4j -->
  <dependency>
   <groupId>log4j</groupId>
   <artifactId>log4j</artifactId>
   <version>${log4j.version}</version>
  </dependency>


  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-web</artifactId>
   <version>${spring.version}</version>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-webmvc</artifactId>
   <version>${spring.version}</version>
  </dependency>

  <!-- Spring Security -->
  <dependency>
   <groupId>org.springframework.security</groupId>
   <artifactId>spring-security-web</artifactId>
   <version>${spring.security.version}</version>
  </dependency>
  <dependency>
   <groupId>org.springframework.security</groupId>
   <artifactId>spring-security-config</artifactId>
   <version>${spring.security.version}</version>
  </dependency>
  <dependency>
   <groupId>org.springframework.security.oauth</groupId>
   <artifactId>spring-security-oauth2</artifactId>
   <version>1.0.0.RELEASE</version>
  </dependency>
  <dependency>
   <groupId>com.google.code.gson</groupId>
   <artifactId>gson</artifactId>
   <version>2.2.2</version>
  </dependency>
  <dependency>
   <groupId>org.codehaus.jackson</groupId>
   <artifactId>jackson-mapper-asl</artifactId>
   <version>1.9.10</version>
  </dependency>
  <dependency>
   <groupId>commons-httpclient</groupId>
   <artifactId>commons-httpclient</artifactId>
   <version>3.1</version>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context-support</artifactId>
   <version>${spring.version}</version>
  </dependency>
  <dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>javax.servlet-api</artifactId>
   <version>3.0.1</version>
   <scope>provided</scope>
  </dependency>
 </dependencies>
</project>



Spring security OAuth configuration

Now this is where the actual thing is happening, we need to configure a number of thing in this security configuration file:
\src\main\webapp\WEB-INF\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:context="http://www.springframework.org/schema/context"
 xmlns:sec="http://www.springframework.org/schema/security" xmlns:mvc="http://www.springframework.org/schema/mvc"
 xsi:schemaLocation="http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd
  http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.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-4.1.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd ">

 <!-- @author Nagesh.Chauhan(neel4soft@gmail.com) -->
 <!-- This is default url to get a token from OAuth -->
 <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>

 <!-- This is where we tells spring security what URL should be protected 
  and what roles have access to them -->
 <http pattern="/api/**" 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="/api/**" access="ROLE_APP" />
  <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>


 <!-- This is simple authentication manager, with a hardcoded user/password 
  combination. We can replace this with a user defined service to get few users 
  credentials from DB -->
 <authentication-manager alias="authenticationManager"
  xmlns="http://www.springframework.org/schema/security">
  <authentication-provider>
   <user-service>
    <user name="beingjavaguys" password="spring@java" authorities="ROLE_APP" />
   </user-service>
  </authentication-provider>
 </authentication-manager>

 <bean id="clientDetailsUserService"
  class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
  <constructor-arg ref="clientDetails" />
 </bean>


 <!-- This defined token store, we have used inmemory tokenstore for now 
  but this can be changed to a user defined one -->
 <bean id="tokenStore"
  class="org.springframework.security.oauth2.provider.token.InMemoryTokenStore" />

 <!-- This is where we defined token based configurations, token validity 
  and other things -->
 <bean id="tokenServices"
  class="org.springframework.security.oauth2.provider.token.DefaultTokenServices">
  <property name="tokenStore" ref="tokenStore" />
  <property name="supportRefreshToken" value="true" />
  <property name="accessTokenValiditySeconds" value="120" />
  <property name="clientDetailsService" ref="clientDetails" />
 </bean>

 <bean id="userApprovalHandler"
  class="org.springframework.security.oauth2.provider.approval.TokenServicesUserApprovalHandler">
  <property name="tokenServices" ref="tokenServices" />
 </bean>

 <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" />

 <oauth:client-details-service id="clientDetails">
  <!-- client -->
  <oauth:client client-id="restapp"
   authorized-grant-types="authorization_code,client_credentials"
   authorities="ROLE_APP" scope="read,write,trust" secret="secret" />

  <oauth:client client-id="restapp"
   authorized-grant-types="password,authorization_code,refresh_token,implicit"
   secret="restapp" authorities="ROLE_APP" />

 </oauth:client-details-service>

 <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>



Spring configuration file

This is nothing more that a very simple spring configuration file, it just loads Spring web app context in the container, we can configure spring related thing here:
\src\main\webapp\WEB-INF\mvc-dispatcher-servlet.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:context="http://www.springframework.org/schema/context"
 xmlns:util="http://www.springframework.org/schema/util" xmlns:mvc="http://www.springframework.org/schema/mvc"
 xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">
 <!-- @author Nagesh.Chauhan(neel4soft@gmail.com) -->
 <context:component-scan base-package="com.beingjavaguys" />
 <mvc:annotation-driven />
</beans>


Deployment descriptor

This is simple web.xml file that lets the container know that all upcoming requests will be handled by spring itself, we have passed location of configuration files and added a spring security filter over here. This filter is intercepting all requests to be authenticated by security context. \src\main\webapp\WEB-INF\web.xml
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
       http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
 version="2.5">

 <display-name>Sample Spring Maven Project</display-name>
 <!-- @author Nagesh.Chauhan(neel4soft@gmail.com) -->

 <servlet>
  <servlet-name>mvc-dispatcher</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
 </servlet>
 <servlet-mapping>
  <servlet-name>mvc-dispatcher</servlet-name>
  <url-pattern>/</url-pattern>
 </servlet-mapping>
 <listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>

 <context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>  
            /WEB-INF/mvc-dispatcher-servlet.xml,  
            /WEB-INF/spring-security.xml
        </param-value>
 </context-param>

 <!-- Spring Security -->

 <filter>
  <filter-name>springSecurityFilterChain</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
 </filter>

 <filter-mapping>
  <filter-name>springSecurityFilterChain</filter-name>
  <url-pattern>/*</url-pattern>
 </filter-mapping>

</web-app>


Domain classes

Here is a single domain class, whose objects are going to be returned in json format:
\src\main\java\com\beingjavaguys\models\User.java
package com.beingjavaguys.models;
/**
 * @author Nagesh.Chauhan
 *
 */
public class User {
 private int id;
 private String name;
 private String email;
 private String phone;

 public User() {
  super();
  // TODO Auto-generated constructor stub
 }

 public User(int id, String name, String email, String phone) {
  super();
  this.id = id;
  this.name = name;
  this.email = email;
  this.phone = phone;
 }

 public int getId() {
  return id;
 }

 public void setId(int id) {
  this.id = id;
 }

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

 public String getEmail() {
  return email;
 }

 public void setEmail(String email) {
  this.email = email;
 }

 public String getPhone() {
  return phone;
 }

 public void setPhone(String phone) {
  this.phone = phone;
 }

}

This is all about, securing restful web services with spring security and oauth2, in this article we look into how to configure spring security with oauth2 to use a token based authentication mechanism. In upcoming articles we will see more about security in java EE applications.

Download complete Project : Git Hub









Thanks for reading !
Being Java Guys Team

Get Spring Security and OAuth2 Integration Source Code from Git Hub






18 comments:

  1. Step 1 is rather insecure: GET and URL parameters - not a good idea.
    I know this is not the focus of the article but at least provide a note then about it if you like.
    Thanks

    ReplyDelete
    Replies
    1. + HTTPS should be used of course

      Delete
    2. Using Get and passing credentials in the URL is not secure!!!!!
      Credentials will stick in the browser history.

      Delete
    3. We can use POST as well.
      "/oauth/authorize" url is mapped to a servlet which accepts POST also. See the below server log -

      [2/5/15 14:10:53:088 IST] 00000433 FrameworkEndp I org.springframework.web.servlet.handler.AbstractHandlerMethodMapping registerHandlerMethod Mapped "{[/oauth/authorize],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" 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)
      [2/5/15 14:10:53:090 IST] 00000433 FrameworkEndp I org.springframework.web.servlet.handler.AbstractHandlerMethodMapping registerHandlerMethod Mapped "{[/oauth/authorize],methods=[POST],params=[user_oauth_approval],headers=[],consumes=[],produces=[],custom=[]}" 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)

      Delete
  2. This example has oauth consumer and oauth provider in one application. How can I split it two applications. In other work token provider and rest api server are in different server or application.

    ReplyDelete
  3. When I create a token I get a JSON response in the following format:



    value: "30120630-af4e-48df-86c8-df735526e533",
    expiration: 1421056974779,
    tokenType: "bearer",
    refreshToken: {
    value: "1a74d29a-f715-4590-b8a6-fb22901ca15a",
    expiration: 1423648854779
    },
    scope: [ ],
    additionalInformation: { },
    expired: false,
    expiresIn: 79
    }

    Your format is different:

    {
    "access_token": "22cb0d50-5bb9-463d-8c4a-8ddd680f553f",
    "token_type": "bearer",
    "refresh_token": "7ac7940a-d29d-4a4c-9a47-25a2167c8c49",
    "expires_in": 119
    }

    Any idea why this is?
    I need my response to be in the same format as yours as my OAuth2 client consumer is built that way...

    Thanks

    ReplyDelete
  4. Hello,
    Could you please provided xml configurations for Mongo (NoSql database) and for latest dependency


    org.springframework.security.oauth
    spring-security-oauth2
    2.0.6.RELEASE


    Your provided Spring OAuth2 xml configuration not working for the above two adaptations.

    ReplyDelete
  5. can any please reply me whre token is stored in spring secuirity

    ReplyDelete
  6. I've used your example to create my own project, but I always receive a error message: "An Authentication object was not found in the SecurityContext".

    Do you have any ideas about the problem related to this error message?

    ReplyDelete
  7. this is working fine.. but when i use same configuration in my project then i get access token but after passing it to secure url... it shows an error
    {
    "error": "invalid_token",
    "error_description": "Invalid access token: 2f4da12c-1c9b-45b7-b997-a7115b953d40"
    }

    why my access token is invalid?

    ReplyDelete
  8. Please help me with below 2 problems -


    1- I want to maintain the User credentials in the Database.
    2- I want to pass the user credentials as a header.


    Is passing the credentials with URL different from passing it with header when SSL is used?

    ReplyDelete
  9. Hello, You can post username and password into headers in POST request using Postman client( for Chrome) and RestClient (for Mozilla), I'm able to send it successfully and getting response back to...

    ReplyDelete
  10. Hello - Did you got any response or idea or suggestion on this? I am anxiously waiting for reply..

    ReplyDelete
  11. Hello Nagesh,


    It seems that you're not replying to any of the inquiries below which is bad. May I suggest you to please update your code to use Spring Securities latest version (2.0.7.RELEASE)? It would be worth for all your followers.


    If we try to change it to latest maven versions causes the error. Please help us.


    Thanks,
    Stacy

    ReplyDelete
  12. Hello... anyone can i ask u question about the json format?

    The complete question is in here.

    http://stackoverflow.com/questions/29045919/different-oauth2-access-token-json-format-java

    ReplyDelete
  13. hi,

    firstly thanks for the code. when i added second login http url and second authentication manager for it, login response returns that "No Authentication object found in Security Context". is there a way to succeed that 2 different login url with 2 different authentication manager

    thanks

    ReplyDelete
  14. Nice Question. I am facing the same problem. Do you have the solution now? If yes please share

    ReplyDelete
  15. i couldnt find a clean solution, after that i decided create a new application for second login mechanism.. now, i have 2 application, one with real app and first authentication mechanism, and second app is for just second authentication..

    ReplyDelete

Like Us on Facebook


Like Us On Google+



Contact

Email: neel4soft@gmail.com
Skype: neel4soft