Spring Rest service security with OAUTH – XML


Lets discuss Spring Rest service security with OAuth using XML configuration


We have learned about securing Rest services and consuming secured Rest services using Spring Security in spring security rest service article.

In this article, we will learn about the same using OAuth instead of Spring security.

If you are completely new to OAuth then I would strongly recommend reading OAUTH overview article before going through this article.


Lets implement the same step by step

Create a new Maven Web project in eclipse (Refer Spring MVC Hello World project for the same)

Project structure


oauth_xml_config_proj_structure

Step 1

Update pom.xml with below dependencies

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
74
75
76
77
78
79
80
81
82
<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>SpringRestServiceSecurityOauth</groupId>
    <artifactId>SpringRestServiceSecurityOauth</artifactId>
    <packaging>war</packaging>
    <version>0.0.1-SNAPSHOT</version>
    <name>SpringRestServiceSecurityOauth Maven Webapp</name>
    <url>http://maven.apache.org</url>
    <properties>
        <org.springframework.version>4.2.0.RELEASE</org.springframework.version>
        <spring-security.version>3.2.7.RELEASE</spring-security.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
 
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <!-- Jackson JSON -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.8.5</version>
        </dependency>
 
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-core</artifactId>
            <version>${spring-security.version}</version>
        </dependency>
        <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>
        <!--  Spring OAUTH dependency -->
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.0.12.RELEASE</version>
        </dependency>
 
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.6</version>
</dependency>
 
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>4.3.5.RELEASE</version>
</dependency>
 
    </dependencies>
    <build>
        <finalName>SpringRestServiceSecurityOauth</finalName>
    </build>
</project>
<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>SpringRestServiceSecurityOauth</groupId>
	<artifactId>SpringRestServiceSecurityOauth</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>SpringRestServiceSecurityOauth Maven Webapp</name>
	<url>http://maven.apache.org</url>
	<properties>
		<org.springframework.version>4.2.0.RELEASE</org.springframework.version>
		<spring-security.version>3.2.7.RELEASE</spring-security.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${org.springframework.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>${org.springframework.version}</version>
		</dependency>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${org.springframework.version}</version>
		</dependency>
		<!-- Jackson JSON -->
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>2.8.5</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-core</artifactId>
			<version>${spring-security.version}</version>
		</dependency>
		<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>
		<!--  Spring OAUTH dependency -->
		<dependency>
			<groupId>org.springframework.security.oauth</groupId>
			<artifactId>spring-security-oauth2</artifactId>
			<version>2.0.12.RELEASE</version>
		</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.6</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>4.3.5.RELEASE</version>
</dependency>

	</dependencies>
	<build>
		<finalName>SpringRestServiceSecurityOauth</finalName>
	</build>
</project>


We have added dependencies for Spring mvc ,spring security ,mysql,spring jdbc, Jackson and Junit in the above pom file.

Step 2

Update web.xml file with Dispatcher servlet and spring security filter

we have defined a dispatcher servlet in web.xml and mapped it by the URL pattern “/”

So just like any other servlet in web application,any request matching with the given pattern i.e “/” will be redirected to “Dispatcher servlet”.

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
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
    version="3.1">
  <display-name>Archetype Created Web Application</display-name>
  
   <servlet>
        <servlet-name>mvc-dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>
            /WEB-INF/spring-beans.xml,
            /WEB-INF/spring-security.xml
        </param-value>
        </init-param>
        <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>
 
    <!-- Loads Spring Security configuration file -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/spring-beans.xml,
            /WEB-INF/spring-security.xml
        </param-value>
    </context-param>
 
    <!-- Spring Security filter -->
    <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>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
    version="3.1">
  <display-name>Archetype Created Web Application</display-name>
  
   <servlet>
        <servlet-name>mvc-dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>
            /WEB-INF/spring-beans.xml,
            /WEB-INF/spring-security.xml
        </param-value>
        </init-param>
        <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>
 
    <!-- Loads Spring Security configuration file -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/spring-beans.xml,
            /WEB-INF/spring-security.xml
        </param-value>
    </context-param>
 
    <!-- Spring Security filter -->
    <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>


We have provided the spring configuration file name to create and load the spring beans while starting the server.

Also we have provided Spring security config file to load the security related configuration.

we have also added filter for spring security which will delegate the request for authentication before processing the request.

Step 3

Create a Spring beans config file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.2.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">
 
    <context:component-scan base-package="com.kb" />
    <mvc:annotation-driven />
     
</beans>
<?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:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.2.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">
 
    <context:component-scan base-package="com.kb" />
    <mvc:annotation-driven />
     
</beans>

Step 4

Create the spring security config file

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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
<?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 ">
 
 <!-- This is default url provided by spring to get the tokens(access and refresh) 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="/rest/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="/rest/api/**" access="ROLE_OAUTH_CLIENT" />
  <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="sample" />
 </bean>
 
 <bean id="clientAuthenticationEntryPoint"
  class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
  <property name="realmName" value="sample/oauthClient" />
  <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>
 
 
 <!-- Here we have hard-coded user name and password details. We can replace this with a user defined service to get users
  credentials from DB -->
 <authentication-manager alias="authenticationManager"
  xmlns="http://www.springframework.org/schema/security">
  <authentication-provider>
   <user-service id="userDetailsService">
    <user name="kb" password="kb@1234" authorities="ROLE_OAUTH_CLIENT" />
    <user name="raj" password="raj@1234" authorities="ROLE_OAUTH_CLIENT" />
   </user-service>
  </authentication-provider>
 </authentication-manager>
 
 <bean id="clientDetailsUserService"
  class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
  <constructor-arg ref="clientDetails" />
 </bean>
 
 
 <!--We have used JDBC tokenstore to store the tokens, we can use In Memory token store for development purpose -->
 <bean id="tokenStore"
  class="org.springframework.security.oauth2.provider.token.store.JdbcTokenStore">
  <constructor-arg ref="jdbcTemplate" />
  </bean>
  
  <bean id="jdbcTemplate"
     class="org.springframework.jdbc.datasource.DriverManagerDataSource">
      <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
      <property name="url" value="jdbc:mysql://localhost:3306/oauthdb"/>
   <property name="username" value="root"/>
   <property name="password" value="root"/>
  </bean>
 
 <!-- tokenServices bean for defining token based configurations, token validity etc -->
 <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="requestFactory"
        class="org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory">
        <constructor-arg name="clientDetailsService" ref="clientDetails" />
    </bean>
 
<bean id="userApprovalHandler" class="org.springframework.security.oauth2.provider.approval.TokenStoreUserApprovalHandler">
    <property name="tokenStore" ref="tokenStore"/>
    <property name="requestFactory" ref="requestFactory" />
</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="sample" token-services-ref="tokenServices" />
 
 <oauth:client-details-service id="clientDetails">
  <!-- client -->
  <oauth:client client-id="trusted client"
   authorized-grant-types="password,refresh_token,client_credentials"
   authorities="ROLE_OAUTH_CLIENT" scope="read,write,trust" secret="secret" />
 
  <oauth:client client-id="trusted client with secret"
   authorized-grant-types="password,authorization_code,refresh_token,implicit"
   secret="somesecret" authorities="ROLE_OAUTH_CLIENT" />
 
 </oauth:client-details-service>
 
 <sec:global-method-security
  pre-post-annotations="enabled" proxy-target-class="true">
  
  <sec:expression-handler ref="oauthExpressionHandler" />
 </sec:global-method-security>
 
 <oauth:expression-handler id="oauthExpressionHandler" />
 <oauth:web-expression-handler id="oauthWebExpressionHandler" />
</beans>
<?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 ">

 <!-- This is default url provided by spring to get the tokens(access and refresh) 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="/rest/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="/rest/api/**" access="ROLE_OAUTH_CLIENT" />
  <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="sample" />
 </bean>

 <bean id="clientAuthenticationEntryPoint"
  class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
  <property name="realmName" value="sample/oauthClient" />
  <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>


 <!-- Here we have hard-coded user name and password details. We can replace this with a user defined service to get users
  credentials from DB -->
 <authentication-manager alias="authenticationManager"
  xmlns="http://www.springframework.org/schema/security">
  <authentication-provider>
   <user-service id="userDetailsService">
    <user name="kb" password="kb@1234" authorities="ROLE_OAUTH_CLIENT" />
    <user name="raj" password="raj@1234" authorities="ROLE_OAUTH_CLIENT" />
   </user-service>
  </authentication-provider>
 </authentication-manager>

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


 <!--We have used JDBC tokenstore to store the tokens, we can use In Memory token store for development purpose -->
 <bean id="tokenStore"
  class="org.springframework.security.oauth2.provider.token.store.JdbcTokenStore">
  <constructor-arg ref="jdbcTemplate" />
  </bean>
  
  <bean id="jdbcTemplate"
     class="org.springframework.jdbc.datasource.DriverManagerDataSource">
      <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
      <property name="url" value="jdbc:mysql://localhost:3306/oauthdb"/>
   <property name="username" value="root"/>
   <property name="password" value="root"/>
  </bean>

 <!-- tokenServices bean for defining token based configurations, token validity etc -->
 <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="requestFactory"
		class="org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory">
		<constructor-arg name="clientDetailsService" ref="clientDetails" />
	</bean>

<bean id="userApprovalHandler" class="org.springframework.security.oauth2.provider.approval.TokenStoreUserApprovalHandler">
    <property name="tokenStore" ref="tokenStore"/>
    <property name="requestFactory" ref="requestFactory" />
</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="sample" token-services-ref="tokenServices" />

 <oauth:client-details-service id="clientDetails">
  <!-- client -->
  <oauth:client client-id="trusted client"
   authorized-grant-types="password,refresh_token,client_credentials"
   authorities="ROLE_OAUTH_CLIENT" scope="read,write,trust" secret="secret" />

  <oauth:client client-id="trusted client with secret"
   authorized-grant-types="password,authorization_code,refresh_token,implicit"
   secret="somesecret" authorities="ROLE_OAUTH_CLIENT" />

 </oauth:client-details-service>

 <sec:global-method-security
  pre-post-annotations="enabled" proxy-target-class="true">
  
  <sec:expression-handler ref="oauthExpressionHandler" />
 </sec:global-method-security>

 <oauth:expression-handler id="oauthExpressionHandler" />
 <oauth:web-expression-handler id="oauthWebExpressionHandler" />
</beans>

In the above spring security xml file we have defined OAuth related configuration.

We have configured /oauth/token url for retrieving access and refresh token and configured authentication manager as clientAuthenticationManager

clientAuthenticationManager is defined as below

1
2
3
4
 <authentication-manager id="clientAuthenticationManager"
  xmlns="http://www.springframework.org/schema/security">
  <authentication-provider user-service-ref="clientDetailsUserService" />
 </authentication-manager>
 <authentication-manager id="clientAuthenticationManager"
  xmlns="http://www.springframework.org/schema/security">
  <authentication-provider user-service-ref="clientDetailsUserService" />
 </authentication-manager>


This is pointing to clientDetailsUserService bean which is defined as below

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


This is pointing to the actual client details service which is defined as below

1
2
3
4
5
6
7
8
9
 <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: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>


Here we have injected the token service to manage the refresh and access tokens.
Also defined possible Grant types in it.

we have defined token service bean with token details as below

1
2
3
4
5
6
7
<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="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>

we have specified access token to be valid for 2 minutes.
We have also injected a tokenstore which will take care of storing the tokens.

we have defined tokenstore as JDBC token store as below

1
2
3
4
<bean id="tokenStore"
  class="org.springframework.security.oauth2.provider.token.store.JdbcTokenStore">
  <constructor-arg ref="jdbcTemplate" />
  </bean>
<bean id="tokenStore"
  class="org.springframework.security.oauth2.provider.token.store.JdbcTokenStore">
  <constructor-arg ref="jdbcTemplate" />
  </bean>

This will store the tokens in DB using JDBC and configuration for the JDBC is defined using JDBC template bean.

we have configured our rest service to be considered by OAuth using below tag

1
<intercept-url pattern="/rest/api/**" access="ROLE_OAUTH_CLIENT" />
<intercept-url pattern="/rest/api/**" access="ROLE_OAUTH_CLIENT" />

This specifies that any URL with the pattern preceded by /rest/api will be intercepted by OAuth and Client should have the Role called ROLE_OAUTH_CLIENT to proceed further.

we have also defined authentication provider as user service with set of users along with their credentials and roles defined in it.

1
2
3
4
5
6
 <authentication-provider>
   <user-service id="userDetailsService">
    <user name="kb" password="kb@1234" authorities="ROLE_OAUTH_CLIENT" />
    <user name="raj" password="raj@1234" authorities="ROLE_OAUTH_CLIENT" />
   </user-service>
  </authentication-provider>
 <authentication-provider>
   <user-service id="userDetailsService">
    <user name="kb" password="kb@1234" authorities="ROLE_OAUTH_CLIENT" />
    <user name="raj" password="raj@1234" authorities="ROLE_OAUTH_CLIENT" />
   </user-service>
  </authentication-provider>

we are granting access to Rest services only for those users whose Role is ROLE_OAUTH_CLIENT as configured above.

Step 5

Execute below sql scripts(executed in MYSQL) to support JDBC Token store

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
create table oauth_client_details (
  client_id VARCHAR(256) PRIMARY KEY,
  resource_ids VARCHAR(256),
  client_secret VARCHAR(256),
  scope VARCHAR(256),
  authorized_grant_types VARCHAR(256),
  web_server_redirect_uri VARCHAR(256),
  authorities VARCHAR(256),
  access_token_validity INTEGER,
  refresh_token_validity INTEGER,
  additional_information VARCHAR(4096),
  autoapprove VARCHAR(256)
);
 
 
create table oauth_client_token (
  token_id VARCHAR(256),
  token BLOB,
  authentication_id VARCHAR(256) PRIMARY KEY,
  user_name VARCHAR(256),
  client_id VARCHAR(256)
);
 
 
create table oauth_access_token (
  token_id VARCHAR(256),
  token BLOB,
  authentication_id VARCHAR(256),
  user_name VARCHAR(256),
  client_id VARCHAR(256),
  authentication BLOB,
  refresh_token VARCHAR(256)
);
 
create table oauth_refresh_token (
  token_id VARCHAR(256),
  token BLOB,
  authentication BLOB
);
 
create table oauth_code (
  code VARCHAR(256), authentication BLOB
);
 
create table oauth_approvals (
    userId VARCHAR(256),
    clientId VARCHAR(256),
    scope VARCHAR(256),
    status VARCHAR(10),
    expiresAt TIMESTAMP,
    lastModifiedAt TIMESTAMP
);
 
 
-- customized oauth_client_details table
create table ClientDetails (
  appId VARCHAR(256) PRIMARY KEY,
  resourceIds VARCHAR(256),
  appSecret VARCHAR(256),
  scope VARCHAR(256),
  grantTypes VARCHAR(256),
  redirectUrl VARCHAR(256),
  authorities VARCHAR(256),
  access_token_validity INTEGER,
  refresh_token_validity INTEGER,
  additionalInformation VARCHAR(4096),
  autoApproveScopes VARCHAR(256)
);
create table oauth_client_details (
  client_id VARCHAR(256) PRIMARY KEY,
  resource_ids VARCHAR(256),
  client_secret VARCHAR(256),
  scope VARCHAR(256),
  authorized_grant_types VARCHAR(256),
  web_server_redirect_uri VARCHAR(256),
  authorities VARCHAR(256),
  access_token_validity INTEGER,
  refresh_token_validity INTEGER,
  additional_information VARCHAR(4096),
  autoapprove VARCHAR(256)
);


create table oauth_client_token (
  token_id VARCHAR(256),
  token BLOB,
  authentication_id VARCHAR(256) PRIMARY KEY,
  user_name VARCHAR(256),
  client_id VARCHAR(256)
);


create table oauth_access_token (
  token_id VARCHAR(256),
  token BLOB,
  authentication_id VARCHAR(256),
  user_name VARCHAR(256),
  client_id VARCHAR(256),
  authentication BLOB,
  refresh_token VARCHAR(256)
);

create table oauth_refresh_token (
  token_id VARCHAR(256),
  token BLOB,
  authentication BLOB
);

create table oauth_code (
  code VARCHAR(256), authentication BLOB
);

create table oauth_approvals (
	userId VARCHAR(256),
	clientId VARCHAR(256),
	scope VARCHAR(256),
	status VARCHAR(10),
	expiresAt TIMESTAMP,
	lastModifiedAt TIMESTAMP
);


-- customized oauth_client_details table
create table ClientDetails (
  appId VARCHAR(256) PRIMARY KEY,
  resourceIds VARCHAR(256),
  appSecret VARCHAR(256),
  scope VARCHAR(256),
  grantTypes VARCHAR(256),
  redirectUrl VARCHAR(256),
  authorities VARCHAR(256),
  access_token_validity INTEGER,
  refresh_token_validity INTEGER,
  additionalInformation VARCHAR(4096),
  autoApproveScopes VARCHAR(256)
);

Step 6

Create rest service which we want to secure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.kb.rest.controllers;
 
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
 
import com.kb.rest.model.User;
 
@Controller
@RequestMapping("/rest/api")
public class RestController {
 
    @RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
    public @ResponseBody User getUserForId(@PathVariable ("id") int id) {
        User user = new User();
        user.setId(id);
        user.setName("John");
        user.setAge(45);
        return user;
    }
}
package com.kb.rest.controllers;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import com.kb.rest.model.User;

@Controller
@RequestMapping("/rest/api")
public class RestController {

	@RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
	public @ResponseBody User getUserForId(@PathVariable ("id") int id) {
		User user = new User();
		user.setId(id);
		user.setName("John");
		user.setAge(45);
		return user;
	}
}


we have exposed one method for retrieving the user based on id.

Step 7

Build and deploy the project

Step 8

Let’s access the rest service using Postman client

Access rest service without authentication info using below url

http://localhost:8080/SpringRestServiceSecurityOauth/rest/api/user/1

oauth_xml_config_no_auth_error

Select GET method

We can see that access is denied due to unauthorization as we are accessing the secured service without access token.

Let’s get the access token using below url

http://localhost:8080/SpringRestServiceSecurityOauth/oauth/token?grant_type=client_credentials

add user name and password in the header as shown below
Authrorization will be added to the header automatically

oauth_xml_config_get_access_token

We can see that access_token will expire in 119 seconds.

Now access the rest service using access token before it gets expired.

http://localhost:8080/SpringRestServiceSecurityOauth/rest/api/user/1?access_token=9409a4a0-0887-46f5-a36e-9b1de71699df

oauth_get_response_with_access_token

Note:
We will not get refresh token if the grant type is client_credentials and hence we are not getting refresh_token from OAuth.

About the Author

Karibasappa G C (KB)
Founder of javainsimpleway.com
I love Java and open source technologies and very much passionate about software development.
I like to share my knowledge with others especially on technology 🙂
I have given all the examples as simple as possible to understand for the beginners.
All the code posted on my blog is developed,compiled and tested in my development environment.
If you find any mistakes or bugs, Please drop an email to kb.knowledge.sharing@gmail.com

Connect with me on Facebook for more updates

Share this article on