Spring Security Oauth2: Flow to Handling Expired AccessToken
The OAuth2 Spec has a section on refreshing access tokens. It's implemented in a pretty standard way in Spring OAuth (you just post the refresh token to the /token endpoint).
BTW for SSO you don't normally need a Resource Server. But that's a different question.
Related videos on Youtube
ericagon
Updated on September 19, 2022Comments
-
ericagon over 1 year
I am just a beginner in Spring Security Oauth2. I try to make Authorization Server and Resource Server (separated and connect to JDBC) and the purpose is to make Single Sign-On. My flow success to get accesstoken and refreshtoken from Authorization Server. My accesstoken always used as parameter to access Resouce Server, and this is give a response back.
for example http://127.0.0.1:8080/Resource/res/staff?access_token=xxxxxxxxxxxxxxxxxxxxx
My problem, if the accesstoken expired, the spring security will prevent to access the page and give the error exception. When I must use the refreshtoken to get new token? Or is my flow wrong? Is there other flow to renew accesstoken?
Thanks
Edited:
FYI: I want to make SSO using Spring Security Oauth2. I have several Apps Server (use Spring Framework) and I want to make one server that responsible to manage the login. And I want to make the Apps Server become Resource Server (also the Client) So I make one Authorization Server with Spring Security Oauth2. The user who wants to access the protected Resource Server must login to Authorization Server (the Resource Server authorize to Authorization Server). It will get a code and then the Resource Server will exchange this code with accessToken and refreshToken. This flow is success.
I can also request the new accessToken using the refreshToken that given by Authorization Server. But I cannot call this procedure because if I access the url mapping, previously the spring security was blocking the access and give the invalid token error return.
How can I solve the missing link?
Updated:
This is my Authorization Server Configuration:
@Configuration public class Oauth2AuthorizationServer { @Configuration @EnableAuthorizationServer protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter { @Autowired @Qualifier("authenticationManagerBean") private AuthenticationManager authenticationManager; @Autowired DataSource dataSource; @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints .tokenStore(new JdbcTokenStore(dataSource)) .authenticationManager(authenticationManager); } @Override public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { oauthServer.tokenKeyAccess("isAnonymous() || permitAll()").checkTokenAccess("permitAll()"); } @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients .jdbc(dataSource); } } }
And this is my Resource Server Configuration (as Client too)
@Configuration public class Oauth2ResourceServer { private static final String RESOURCE_ID = "test"; @Configuration @Order(10) protected static class NonOauthResources extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/api/halo").permitAll() .antMatchers("/api/state/**").permitAll() .antMatchers("/**").permitAll() .and().anonymous(); } } @Configuration @EnableResourceServer protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter { @Override public void configure(ResourceServerSecurityConfigurer resources) { RemoteTokenServices tokenService = new RemoteTokenServices(); tokenService.setClientId("jsclient"); tokenService.setClientSecret("jspasswd"); tokenService.setCheckTokenEndpointUrl("http://localhost:8084/Server2Auth/oauth/check_token"); resources .resourceId(RESOURCE_ID) .tokenServices(tokenService); } @Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .filterSecurityInterceptorOncePerRequest(true) .antMatchers("/res/staff").hasRole("STAFF") .antMatchers("/res/client").access("#oauth2.hasScope('trust')") .antMatchers("/res/admin").hasRole("ADMIN") .and() .exceptionHandling().accessDeniedPage("/403"); } } }
Resource (as Client too) request to authorize:
curl -X POST -vu clientauthcode:123456 http://localhost:10000/auth-server/oauth/token -d "client_id=clientauthcode&grant_type=refresh_token&refresh_token=436761f1-2f26-412b-ab0f-bbf2cd7459c4"
Feedback from Authorize Server:
http://localhost:10001/resource-server/api/state/new?code=8OppiR
Resource (as Client) exchange the code to Authorize Server:
curl -X POST -vu clientauthcode:123456 http://localhost:10000/auth-server/oauth/token -H "Accept: application/json" -d "grant_type=authorization_code&code=iMAtdP&redirect_uri=http://localhost:10001/resource-server/api/state/new"
Feedback from Authorize Server:
{ "access_token":"08664d93-41e3-473c-b5d2-f2b30afe7053", "token_type":"bearer", "refresh_token":"436761f1-2f26-412b-ab0f-bbf2cd7459c4", "expires_in":43199, "scope":"write read" }
Resource (as Client) access the url itself
curl http://localhost:10001/resource-server/api/admin?access_token=08664d93-41e3-473c-b5d2-f2b30afe7053
Request new access toke using refresh token
curl -X POST -vu clientauthcode:123456 http://localhost:10000/auth-server/oauth/token -d "client_id=clientauthcode&grant_type=refresh_token&refresh_token=436761f1-2f26-412b-ab0f-bbf2cd7459c4"
-
Dave Syer over 9 yearsUnrelated comment: you should put the access token in a header (it's not secure as a URI query parameter).
-
Dave Syer over 9 yearsAnother unrelated comment: you don't need the token key endpoint, so don't touch it; and the check token endpoint should not be exposed with
permitAll()
in a production server (tryisAuthenticated()
). -
Dave Syer over 9 yearsSomewhat related comment (since it makes the question misleading) you are not doing any SSO here. It is classic auth/resource server with token-based security in the resource server.
-
-
Dave Syer over 9 yearsNo problem. Happy to help. If my answer was helpful, please accept it so other people will know that it might help them.
-
ericagon over 9 yearsSorry,I am just beginner in Oauth2 too. In the Ouath2 spec says "The authorization server MAY issue a new refresh token", how Spring Security Oauth2 can give the issue? I can make a request to get new accesstoken with refreshtoken, but I only knew that the accesstoken expired when it used to access some resource. FYI my SSO separated Authorization and Resource Server because before this I have several Resource Server, so I think I can make Authorization Server separated with my Resources.
-
Dave Syer over 9 yearsA Spring Auth Server issues refresh tokens if you enable them (it might not be the default, I forget). When an access token is issued the client knows approximately when it will expire, so it can use that information or just wait for a 401.
-
ericagon over 9 yearsI just enable the refresh tokens. I want to wait for a 401 and then I can call the procedure to request new access token using refresh token. But I don't know how to make 401 handler?
-
Dave Syer over 9 yearsIf you use
OAuth2RestTemplate
it should be automatic. Otherwise I guess look at the source code for that and copy the pattern. -
ericagon over 9 yearsI haven't found the solution to resolve my issue :| I updated the my configuration code in the question
-
Dave Syer over 9 yearsI don't understand this sentence: "But I cannot call this procedure because if I access the url mapping, previously the spring security was blocking the access and give the invalid token error return." Can you show the code you use to call the token endpoint (preferably try it with curl and post the curl commands)?
-
ericagon over 9 yearsI mean the spring security protect the url if access token invalid (expired or not suitable) and then it gives the xml output about the error. So, I cannot request new access token using refresh token automatically when the access token expired. The code just updated.
-
ericagon over 9 yearsIs OAuth2RestTemplate used in Client? What if my Client also as Resource Server (so it accesses itself and return the jsp page)? Can OAuth2RestTemplate used?
-
ericagon over 9 yearsI want to try use Oauth2RestTemplate. How to use it?
resource.setAccessTokenUri("http://localhost:8084/Server2Auth/oauth/token"); resource.setClientId("admin"); resource.setClientSecret("admin"); resource.setUserAuthorizationUri("http://localhost:8084/auth/oauth/authorize"); request = new DefaultAccessTokenRequest(); template = new OAuth2RestTemplate(resource, new DefaultOAuth2ClientContext(request)); template.getForObject("http://localhost:8084/Rest/admin", String.class);
The error result: UserRedirectRequiredException: A redirect is required to get the users approval -
Dave Syer over 9 yearsThat should work for getting an access token. If you want to automatically refresh it when it expires you have to store it somewhere. And if you create the
Oauth2RestTemplate
every time you use it, you lose the context. Spring OAuth comes with convenience configuration (e.g.@EnableOAuth2Client
) for setting up a template where the context is stored in session scope. -
Ankit Pandoh over 4 years@DaveSyer while using
@EnableOAuth2Client
, I saw once user is authenticated, a session is maintained and even though access token is expired, it won't trigger the refresh token flow to get the new access token. There is an issue reported here github.com/spring-projects/spring-security-oauth2-boot/issues/5 but it seems it is the expected functionality. But then, how can we trigger the refresh token flow while using@EnableOAuth2Client
? -
Dave Syer over 4 yearsI think you are reading that issue wrong. If there is a refresh token then the
OAuth2RestTemplate
will use it when the access token expires, in general, but only if you use it. If you only use it for authentication, it doesn't get used after the initial exchange.