Reputation: 19250
I'm using Spring 4.3.8.RELEASE
and spring-boot 1.5.3.RELEASE
. I want to serve oauth access tokens to applications with the proper credentials. I'm using the org.springframework.security.oauth2.provider.token.store.JdbcTokenStore
class to do this. However, I'm noticing that each time I connect with a client to my server that I have set up with OAuth, the server repeatedly returns the same access token, even after server restarts. My OAuth server configuration is below
<?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" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
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.1.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.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="/oauth/(users|clients)/.*" request-matcher="regex"
create-session="stateless" entry-point-ref="oauthAuthenticationEntryPoint"
use-expressions="true" xmlns="http://www.springframework.org/schema/security">
<anonymous enabled="false" />
<intercept-url pattern="/oauth/users/([^/].*?)/tokens/.*"
access="#oauth2.clientHasRole('ROLE_CLIENT') and (hasRole('ROLE_USER') or #oauth2.isClient()) and #oauth2.hasScope('write')"
method="DELETE" />
<intercept-url pattern="/oauth/users/.*"
access="#oauth2.clientHasRole('ROLE_CLIENT') and (hasRole('ROLE_USER') or #oauth2.isClient()) and #oauth2.hasScope('read')"
method="GET" />
<intercept-url pattern="/oauth/clients/.*"
access="#oauth2.clientHasRole('ROLE_CLIENT') and #oauth2.isClient() and #oauth2.hasScope('read')"
method="GET" />
<custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
<access-denied-handler ref="oauthAccessDeniedHandler" />
<expression-handler ref="oauthWebExpressionHandler" />
</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="/me/**" 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="/me" access="ROLE_USER,SCOPE_READ" />
<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="sparklr2" />
</bean>
<bean id="clientAuthenticationEntryPoint"
class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
<property name="realmName" value="sparklr2/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>
<authentication-manager alias="authenticationManager"
xmlns="http://www.springframework.org/schema/security">
<authentication-provider>
<user-service id="userDetailsService">
<user name="marissa" password="koala" authorities="ROLE_USER" />
<user name="paul" password="emu" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
<bean id="clientDetailsUserService"
class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
<constructor-arg ref="clientDetails" />
</bean>
<bean id="tokenStore" class="org.springframework.security.oauth2.provider.token.store.JdbcTokenStore">
<constructor-arg ref="dataSource" />
<property name="authenticationKeyGenerator">
<bean class="org.springframework.security.oauth2.UniqueAuthenticationKeyGenerator" />
</property>
</bean>
<bean id="tokenServices"
class="org.springframework.security.oauth2.provider.token.DefaultTokenServices">
<property name="tokenStore" ref="tokenStore" />
<property name="tokenEnhancer" ref="tokenEnhancer" />
<property name="supportRefreshToken" value="true" />
<property name="clientDetailsService" ref="clientDetails" />
</bean>
<bean id="tokenEnhancer"
class="org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter" />
<bean id="requestFactory"
class="org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory">
<constructor-arg name="clientDetailsService" ref="clientDetails" />
</bean>
<bean id="approvalStore"
class="org.springframework.security.oauth2.provider.approval.TokenApprovalStore">
<property name="tokenStore" ref="tokenStore" />
</bean>
<oauth:authorization-server
client-details-service-ref="clientDetails" token-services-ref="tokenServices">
<oauth:client-credentials />
</oauth:authorization-server>
<oauth:resource-server id="resourceServerFilter" entry-point-ref="entry"
resource-id="myclientAssignment" token-services-ref="tokenServices" />
<bean id="entry" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<constructor-arg value="/assignment" />
</bean>
<context:property-placeholder location="classpath:application.properties"/>
<oauth:client-details-service id="clientDetails">
<oauth:client client-id="${myclient.client.id}"
authorized-grant-types="client_credentials" authorities="ROLE_CLIENT"
access-token-validity="30"
scope="read,write" secret="${myclient.client.secret}" />
</oauth:client-details-service>
<mvc:default-servlet-handler />
<oauth:expression-handler id="oauthExpressionHandler" />
<oauth:web-expression-handler id="oauthWebExpressionHandler" />
<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="/**"
access="IS_AUTHENTICATED_FULLY"/>
<custom-filter ref="resourceServerFilter"
before="PRE_AUTH_FILTER" />
<access-denied-handler ref="oauthAccessDeniedHandler" />
</http>
</beans>
Here's the shell script I'm using to connect to my local server to retrieve the token ...
#!/bin/bash
ret=$(curl http://localhost:8080/myproject/oauth/token \
-u "myclientid:mysecret" \
-d "grant_type=client_credentials")
echo $ret > /tmp/out
cat /tmp/out
access_token=$( sed -e 's/^.*"access_token":"\([^"]*\)".*$/\1/' /tmp/out )
echo $access_token
Edit:
Per Hans' request, here is an example access token
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwiZXhwIjoxNDk2ODQ4NDAzLCJhdXRob3JpdGllcyI6WyJST0xFX0NMSUVOVCJdLCJqdGkiOiI4OGMxZjkzZC0wNmRhLTRmYTAtOTM1OS0yZWMxYzU5MWJlMGIiLCJjbGllbnRfaWQiOiJ6aW5jbGVhcm5pbmcifQ.Pf-rjPDj0ZhrNOYuhA0tK8lPLLCzlkqUuFFjb48xskA
and here is a second access token
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwiZXhwIjoxNDk2OTUzNTg0LCJhdXRob3JpdGllcyI6WyJST0xFX0NMSUVOVCJdLCJqdGkiOiIxM2I5M2M4Ni05MmIwLTQyY2UtYjFkNS1lZjRiNmZhNzJkMzgiLCJjbGllbnRfaWQiOiJ6aW5jbGVhcm5pbmcifQ.GfSHA_JcQg2WHYCI81lunMFIhxdX6REc4goshB2Lck0
Upvotes: 2
Views: 9321
Reputation: 1569
I know it is very late to post an answer to this question but here is what I think should be considered.
When configuring an Authorization server, one of the parameters is the accessTokenValiditySeconds
.
the point here is that it is your solution:
Here is an example of configuring the Authorization server.
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient(ParamsConfig.TRUSTED_CLIENT_ID)
.secret(passwordEncoder.encode(ParamsConfig.TRUSTED_CLIENT_SECRET))
.accessTokenValiditySeconds(10)
.scopes(ParamsConfig.OAUTH_SECURITY_SCOPE)
.authorizedGrantTypes(ParamsConfig.OAUTH_AUTHORIZATION_GRANT_TYPE)
.authorities(ParamsConfig.OAUTH_SECURITY_AUTHORITIES)
.resourceIds(ParamsConfig.OAUTH_SECURITY_RESOURCE_ID);
}
See the accessTokenValiditySeconds
that describes two things:
1 - till what time this access token will be valid.
2 - till what time you will be getting the same Access token if you request for an access token.
Note: This configuration is in Java not xml.
In your case, it should be access-token-validity
.
Upvotes: 0
Reputation: 659
In your configuration you use a JdbcTokenStore :
So, you have to check the table oauth_client_details : you must have a row with a column access_token_validity where you can change in live the duration in seconds.
Another solution is to delete that row and redeploy your application. Your configuration will create the row again with the value you specified in your configuration and that you were speaking about in the conversation.
Upvotes: 0
Reputation: 75964
This is sample regular access token which when issued will have 30
seconds expiration for your configuration.
You should see the info message (Failed to find access token for token) at the time a new token is issued.
{
"access_token": "e057a4f3-9872-4b97-803e-938ad6ef32db",
"token_type": "bearer",
"refresh_token": "f9a7ea9a-0fd1-45a9-b8ae-de87eaf61787",
"expires_in": 30,
"scope": "read write"
}
This is expected behavior as spring looks into database to verify if the access token for the authentication is already issued.
Subsequent requests until the expiry of token (30s
) will not show the message. This message will show up again after old access token is expired and when new token is issued and it continues.
The below line is where that info message is displayed from
Refresh token is set to expire in 30 days by default. The similar log message will appear again when the refresh token expires.
It should work similar for JWT access token too.
This is the sequences of log messages when a new token is issued when you run the application in DEBUG
logging level.
2017-06-06 14:06:51.339 DEBUG 28732 --- [nio-9191-exec-1] o.s.s.o.p.token.store.JdbcTokenStore : Failed to find access token for authentication org.springframework.security.oauth2.provider.OAuth2Authentication@8e69d9c0: Principal: org.springframework.security.core.userdetails.User@586034f: Username: admin; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN,ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_ADMIN, ROLE_USER
2017-06-06 14:06:51.346 DEBUG 28732 --- [nio-9191-exec-1] o.s.jdbc.core.JdbcTemplate : Executing prepared SQL query
2017-06-06 14:06:51.346 DEBUG 28732 --- [nio-9191-exec-1] o.s.jdbc.core.JdbcTemplate : Executing prepared SQL statement [select token_id, token from oauth_access_token where token_id = ?]
2017-06-06 14:06:51.347 INFO 28732 --- [nio-9191-exec-1] o.s.s.o.p.token.store.JdbcTokenStore : Failed to find access token for token e057a4f3-9872-4b97-803e-938ad6ef32db
2017-06-06 14:06:51.442 DEBUG 28732 --- [nio-9191-exec-1] o.s.jdbc.core.JdbcTemplate : Executing prepared SQL update
2017-06-06 14:06:51.443 DEBUG 28732 --- [nio-9191-exec-1] o.s.jdbc.core.JdbcTemplate : Executing prepared SQL statement [insert into oauth_access_token (token_id, token, authentication_id, user_name, client_id, authentication, refresh_token) values (?, ?, ?, ?, ?, ?, ?)]
2017-06-06 14:06:51.450 DEBUG 28732 --- [nio-9191-exec-1] o.s.jdbc.core.JdbcTemplate : SQL update affected 1 rows
2017-06-06 14:06:51.452 DEBUG 28732 --- [nio-9191-exec-1] o.s.jdbc.core.JdbcTemplate : Executing prepared SQL update
2017-06-06 14:06:51.452 DEBUG 28732 --- [nio-9191-exec-1] o.s.jdbc.core.JdbcTemplate : Executing prepared SQL statement [insert into oauth_refresh_token (token_id, token, authentication) values (?, ?, ?)]
2017-06-06 14:06:51.455 DEBUG 28732 --- [nio-9191-exec-1] o.s.jdbc.core.JdbcTemplate : SQL update affected 1 rows
Upvotes: 4
Reputation: 29168
Why i am getting same access token for multiple login request?
Ashwani has given answer like below in this post
Access Tokens have fixed Life-Time.
Until that period has not expires, access token remains the same. During that period if you request access token you will receive the same string but will have reset expiration. It would only happen if Location, Password etc are exactly same as previous request. Otherwise new token generated. Sometimes Salesforce resets the expiration time for last expired token and return in response.
There is no way to expire those tokens directly, so instead, the tokens are issued with a short expiration time so that the application is forced to continually refresh them, giving the service a chance to revoke an application’s access if needed.
@Dave, As you have told that, there is no expiry field, so it is non-expiring access token
.
Non-expiring access tokens are the easiest method for developers. If you choose this option, it is important to consider the trade-offs you are making.
It isn’t practical to use self-encoded tokens if you want to be able to revoke them arbitrarily. As such, you’ll need to store these tokens in some sort of database, so they can be deleted or marked as invalid as needed.
Note that even if the service intends on issuing non-expiring access tokens for normal use, you’ll still need to provide a mechanism to expire them under exceptional circumstances, such as if the user explicitly wants to revoke an application’s access, or if a user account is deleted.
Non-expiring access tokens are much easier for developers testing their own applications. You can even pre-generate one or more non-expiring access tokens for developers and show it to them on the application details screen. This way they can immediately start making API requests with the token, and not worry about setting up an OAuth flow in order to start testing your API.
In summary, use non-expiring access tokens when:
Resource Link: Acess token lifetime
How to refresh access token? Rules are given here: https://www.oauth.com/oauth2-servers/access-tokens/refreshing-access-tokens/
How to issue an access token are given here:
+----------+
| Resource |
| Owner |
| |
+----------+
v
| Resource Owner
(A) Password Credentials
|
v
+---------+ +---------------+
| |>--(B)---- Resource Owner ------->| |
| | Password Credentials | Authorization |
| Client | | Server |
| |<--(C)---- Access Token ---------<| |
| | (w/ Optional Refresh Token) | |
+---------+ +---------------+
Figure 5: Resource Owner Password Credentials Flow
The flow illustrated in Figure 5 includes the following steps:
(A) The resource owner provides the client with its username and password.
(B) The client requests an access token from the authorization server's token endpoint by including the credentials received from the resource owner. When making the request, the client authenticates with the authorization server.
(C) The authorization server authenticates the client and validates the resource owner credentials, and if valid, issues an access token.
The authorization server MUST follow the rules:
o require client authentication for confidential clients or for any client that was issued client credentials (or with other authentication requirements),
o authenticate the client if client authentication is included, and
o validate the resource owner password credentials using its existing password validation algorithm.
Resource Link: https://www.rfc-editor.org/rfc/rfc6749#page-47
Upvotes: 1