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!