Enterprise Java

Securing resources using Spring Security with OAuth

1. Introduction

In this tutorial, we will check out how we can use Spring Security with OAuth to secure the admin resources on the server based on the path pattern (/api/**). Another path pattern (/oauth/token) we have configured which will help configured authorization server generate the access token. Note that we will be using Password Grant Type in this demo application.

Before we move on with the implementation, let’s recap on the events involved with this grant type.

2. Resource Owner Password Credentials Grant Type

  • Used between trusted applications.
  • The user (Resource Owner) shares the credentials directly with the client application, which requests the Authorization Server to return the access token after successfully authenticating the user credentials and further authorizing the user to access limited resources on the server.

Useful Links

3. Implementation

Make sure the required pom entries are properly added to the pom.xml file.

pom.xml

<!-- Spring dependencies -->
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-core</artifactId>
	<version>${springframework.version}</version>
</dependency>
 
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-web</artifactId>
	<version>${springframework.version}</version>
</dependency>
 
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-webmvc</artifactId>
	<version>${springframework.version}</version>
</dependency>
 
<!-- Spring Security Dependencies -->
<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>
<dependency>
	<groupId>org.springframework.security.oauth</groupId>
	<artifactId>spring-security-oauth2</artifactId>
	<version>${spring-security.oauth.version}</version>
</dependency>

web.xml

Update the web.xml file to load the context files and configure the Spring Security filter, which will redirect the request for authentication and authorization before processing it.

<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_3_0.xsd"
                        version="3.0">
	
	<display-name>Archetype Created Web Application</display-name>
	
	<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>
 
    <!-- Loads context files -->
	<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>

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">
	
	<context:component-scan base-package="com.jcombat" />
	<mvc:annotation-driven />
 
	<bean
		class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix">
			<value>/WEB-INF/pages/</value>
		</property>
		<property name="suffix">
			<value>.jsp</value>
		</property>
	</bean>
</beans>

Since we will are using admin JSP files, we have configured the corresponding view resolver for it.

Now let’s configure the Spring Security OAuth in its context file.

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 ">
 
	<!-- 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" />
		<custom-filter ref="clientCredentialsTokenEndpointFilter"
			after="BASIC_AUTH_FILTER" />
		<access-denied-handler ref="oauthAccessDeniedHandler" />
	</http>
 
	<!-- URLs should be protected and what roles have access to them -->
	<!-- Can define more patterns based on the protected resources hosted on 
		the server -->
	<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" />
		<!-- Protect oauth clients with resource ids -->
		<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="demo/client" />
	</bean>
 
	<bean id="clientAuthenticationEntryPoint"
		class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
		<property name="realmName" value="demo/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 hard-coded username/password 
		combination. We can replace this with a user defined service to fetch user 
		credentials from DB instead -->
	<authentication-manager alias="authenticationManager"
		xmlns="http://www.springframework.org/schema/security">
		<authentication-provider>
			<user-service>
				<user name="admin" password="123" 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 defines the token store. We have currently used in-memory token 
		store but we can instead use a user defined one -->
	<bean id="tokenStore"
		class="org.springframework.security.oauth2.provider.token.InMemoryTokenStore" />
	<!-- If need to store tokens in DB 
	<bean id="tokenStore"
		class="org.springframework.security.oauth2.provider.token.store.JdbcTokenStore">
		<constructor-arg ref="jdbcTemplate" />
	</bean> -->
 
	<!-- 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>
 
	<!-- The server issuing access tokens to the client after successfully authenticating 
		the resource owner and obtaining authorization -->
	<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>
 
	<!-- Define protected resources hosted by the resource server -->
	<oauth:resource-server id="resourceServerFilter"
		resource-id="adminProfile" token-services-ref="tokenServices" />
 
	<!-- OAuth clients allowed to access the protected resources, can be something 
		like facebook, google if we are sharing any resource with them -->
	<oauth:client-details-service id="clientDetails">
		<oauth:client client-id="fbApp"
			authorized-grant-types="password,refresh_token"
			secret="fbApp" authorities="ROLE_APP" resource-ids="adminProfile" />
	</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>

We have configured /oauth/token URL for issuing access and refresh tokens and /api/** maps to the actual protected resources on the server. Hence to access any URL matching the pattern /api/**, a valid token needs to be passed along with the request.

Authentication Manager is the container where the authentication happens. In our case, the authentication manager checks –

  • If the user is authenticated.
  • If the user has requested for the correct client-id.
  • If the client-id is fine, is the user authorized to use it to access the admin profile on the server.

Refer to the below snippet –

<authentication-manager id="clientAuthenticationManager"
		xmlns="http://www.springframework.org/schema/security">
	<authentication-provider user-service-ref="clientDetailsUserService" />
</authentication-manager>
 
<bean id="clientDetailsUserService"
		class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
	<constructor-arg ref="clientDetails" />
</bean>
	
<!-- OAuth clients allowed to access the protected resources, can be something 
		like facebook, google if we are sharing any resource with them -->
<oauth:client-details-service id="clientDetails">
	<oauth:client client-id="fbApp"
		authorized-grant-types="password,refresh_token"
		secret="fbApp" authorities="ROLE_APP" resource-ids="adminProfile" />
</oauth:client-details-service>

Once the user is authenticated, the authorization server calls the tokenServices and issues the access token.

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

While specifying the clients, note the grant type we have specified, which is password.

<oauth:client-details-service id="clientDetails">
	<oauth:client client-id="fbApp"
		authorized-grant-types="password,refresh_token"
		secret="fbApp" authorities="ROLE_APP" resource-ids="adminProfile" />
</oauth:client-details-service>

Once the access token has been issued, we can access the protected resources on the server passing it along with every request. Let’s finally take a look at the Spring Controller we have written –

DemoController.java

package com.jcombat.controller;
 
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
 
@Controller
public class DemoController {
 
	@RequestMapping("/api/admin")
	public String getAdminPage() {
		return "/secured/admin";
	}
}

4. Running the application

To run the application, let’s start with requesting the access token from the authorization server –

http://localhost:8080/SpringSecurityOAuth/oauth/token?grant_type=password&client_id=fbApp&client_secret=fbApp&username=admin&password=123

{  
	"access_token":"5c0c1a28-9603-4818-9ebb-6014600c3de9",
	"token_type":"bearer",
	"refresh_token":"ada8a736-3082-4c3d-9cbf-f043ab8f415f",
	"expires_in":119
}

Once the access token is generated, we are ready to pass it along with every subsequent requests for the protected resources on the server.

http://localhost:8080/SpringSecurityOAuth/api/admin?access_token=5c0c1a28-9603-4818-9ebb-6014600c3de9

5. Download the code

Download the source code

Reference: Securing resources using Spring Security with OAuth from our JCG partner Abhimanyu Prasad at the jCombat blog.

Abhimanyu Prasad

Abhimanyu is a passionate tech blogger and senior programmer, who has an extensive end-to-end development experience with wide range of technologies. He is the founder and administrator at jCombat.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button