This post will walk through setting up an OAuth2 provider service for protecting access to REST resources. The code is available in github. You can fork the code and start writing services that will be protected by OAuth access. This is a separate module but builds on services covered in a previous series that includes:
It would be useful but not required to be familiar with some of the technologies covered such as:
* OAuth2 Protocol
* Spring Security
* Spring Integration
* Spring Data
* Jersey/JAX-RS
* Gradle / Groovy
* MongoDB
* Spring Security
* Spring Integration
* Spring Data
* Jersey/JAX-RS
* Gradle / Groovy
* MongoDB
Resource Owner Password Flow
Subsequent posts will deal with the other types of authorization flow, such as using third party providers (Facebook, Google, etc). The intent of this post is a walk through of the Resource Owner Password flow. This is a typical use case if you are the system of record for user credentials and trust client applications. It is simply the exchange of the user's username and password for an access token. This token can then be used on subsequent requests to authorize access to resources. It is also important to support token expiration and by extension token refresh.
Let's test out the code and then I'll walk through the application and explain how it is built
Let's test out the code and then I'll walk through the application and explain how it is built
Building the project
Check out the source code
> git clone git@github.com:iainporter/oauth2-provider.git
> cd oauth2-provider
> ./gradlew clean build integrationTest
> cd oauth2-provider
> ./gradlew clean build integrationTest
Running the Web Application
The application uses MongoDB as the persistence store. Before running the application ensure that mongod is running on port 27017. If you don't have mongo installed get it here
Once mongoDB is installed and running fire up the web application using the following command
> ./gradlew tomcatRun
To try it out you can open a browser window and navigate to http://localhost:8080/oauth2-provider/index.html
Testing with Curl
To create a user:
> curl -v -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Basic MzUzYjMwMmM0NDU3NGY1NjUwNDU2ODdlNTM0ZTdkNmE6Mjg2OTI0Njk3ZTYxNWE2NzJhNjQ2YTQ5MzU0NTY0NmM=" \
-d '{"user":{"emailAddress":"user@example.com"}, "password":"password"}' \
'http://localhost:8080/oauth2-provider/v1.0/users'
-H "Content-Type: application/json" \
-H "Authorization: Basic MzUzYjMwMmM0NDU3NGY1NjUwNDU2ODdlNTM0ZTdkNmE6Mjg2OTI0Njk3ZTYxNWE2NzJhNjQ2YTQ5MzU0NTY0NmM=" \
-d '{"user":{"emailAddress":"user@example.com"}, "password":"password"}' \
'http://localhost:8080/oauth2-provider/v1.0/users'
The result:
{"apiUser":
{"emailAddress":"user@example.com",
"firstName":null,
"lastName":null,
"age":null,
"id":"8a34d009-3558-4c8c-a8da-1ad2b2a393c7",
"name":"user@example.com"},
"oauth2AccessToken":
{"access_token":"7e0e4708-7837-4a7e-9f87-81c6429b02ac",
"token_type":"bearer",
"refresh_token":"d0f248ab-e30f-4a85-860c-bd1e388a39b5",
"expires_in":5183999,
"scope":"read write"
}
}
{"emailAddress":"user@example.com",
"firstName":null,
"lastName":null,
"age":null,
"id":"8a34d009-3558-4c8c-a8da-1ad2b2a393c7",
"name":"user@example.com"},
"oauth2AccessToken":
{"access_token":"7e0e4708-7837-4a7e-9f87-81c6429b02ac",
"token_type":"bearer",
"refresh_token":"d0f248ab-e30f-4a85-860c-bd1e388a39b5",
"expires_in":5183999,
"scope":"read write"
}
}
Requesting an access token:
> curl -v -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Basic MzUzYjMwMmM0NDU3NGY1NjUwNDU2ODdlNTM0ZTdkNmE6Mjg2OTI0Njk3ZTYxNWE2NzJhNjQ2YTQ5MzU0NTY0NmM=" \
'http://localhost:8080/oauth2-provider/oauth/token?grant_type=password&username=user@example.com&password=password'
-H "Content-Type: application/json" \
-H "Authorization: Basic MzUzYjMwMmM0NDU3NGY1NjUwNDU2ODdlNTM0ZTdkNmE6Mjg2OTI0Njk3ZTYxNWE2NzJhNjQ2YTQ5MzU0NTY0NmM=" \
'http://localhost:8080/oauth2-provider/oauth/token?grant_type=password&username=user@example.com&password=password'
The result:
{
"access_token":"a838780e-35ef-4bd5-92c0-07a45aa74948",
"token_type":"bearer",
"refresh_token":"ab06022f-247c-450a-a11e-2ffab116e3dc",
"expires_in":5183999
}
"access_token":"a838780e-35ef-4bd5-92c0-07a45aa74948",
"token_type":"bearer",
"refresh_token":"ab06022f-247c-450a-a11e-2ffab116e3dc",
"expires_in":5183999
}
Refreshing a token:
> curl -v -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Basic MzUzYjMwMmM0NDU3NGY1NjUwNDU2ODdlNTM0ZTdkNmE6Mjg2OTI0Njk3ZTYxNWE2NzJhNjQ2YTQ5MzU0NTY0NmM=" \
'http://localhost:8080/oauth2-provider/oauth/token?grant_type=refresh_token&refresh_token=ab06022f-247c-450a-a11e-2ffab116e3dc'
-H "Content-Type: application/json" \
-H "Authorization: Basic MzUzYjMwMmM0NDU3NGY1NjUwNDU2ODdlNTM0ZTdkNmE6Mjg2OTI0Njk3ZTYxNWE2NzJhNjQ2YTQ5MzU0NTY0NmM=" \
'http://localhost:8080/oauth2-provider/oauth/token?grant_type=refresh_token&refresh_token=ab06022f-247c-450a-a11e-2ffab116e3dc'
The result:
{
"access_token":"4835cd11-8bb7-4b76-b857-55c6e7f36fc4",
"token_type":"bearer",
"refresh_token":"ab06022f-247c-450a-a11e-2ffab116e3dc",
"expires_in":5183999
}
"access_token":"4835cd11-8bb7-4b76-b857-55c6e7f36fc4",
"token_type":"bearer",
"refresh_token":"ab06022f-247c-450a-a11e-2ffab116e3dc",
"expires_in":5183999
}
That's all that is needed to support registration of Users and managing tokens on their behalf. The source code also contains everything needed to support lost password and email verification. See previous posts for how to set it up.
In a typical deployment the authorization server and the resource server(s) would be separate, but for the purposes of this tutorial they are managed within the same application.
Let's delve into the details:
In a typical deployment the authorization server and the resource server(s) would be separate, but for the purposes of this tutorial they are managed within the same application.
Let's delve into the details:
Web Context
There are two servlets.
A Jersey servlet as the default to handle all resource calls:
<servlet-mapping> <servlet-name>jersey-servlet</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping>A Spring servlet to handle all oauth calls:
<servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>/oauth/*</url-pattern> </servlet-mapping>Wire in spring security by defining a filter:
<filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <param-name>contextAttribute</param-name> <param-value>org.springframework.web.servlet.FrameworkServlet.CONTEXT.spring</param-value> </init-param> </filter>and then map it to the root context so all calls are filtered:
<filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
Configuring OAuth flows to support
<oauth:authorization-server client-details-service-ref="client-details-service" token-services-ref="tokenServices"> <oauth:refresh-token/> <oauth:password/> </oauth:authorization-server>
In this scenario only password flow and refresh token are being supported. The default token endpoint is /oauth/token.
Protecting the token endpoint
It is a good idea to protect access to token requests to only those client applications that you know about. Using Spring security we can set up basic authentication on calls to the token endpoint:
<http pattern="/oauth/token" create-session="stateless" authentication-manager-ref="clientAuthenticationManager" xmlns="http://www.springframework.org/schema/security"> <anonymous enabled="false"/> <http-basic entry-point-ref="clientAuthenticationEntryPoint"/> <access-denied-handler ref="oauthAccessDeniedHandler"/> </http>Next we configure the authentication manager and client details service
<bean id="clientCredentialsTokenEndpointFilter" class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter"> <property name="authenticationManager" ref="clientAuthenticationManager"/> </bean> <authentication-manager id="clientAuthenticationManager" xmlns="http://www.springframework.org/schema/security"> <authentication-provider user-service-ref="client-details-user-service"/> </authentication-manager> <bean id="client-details-user-service" class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService"> <constructor-arg ref="client-details-service" /> </bean>
Depending on how many clients we expect to access the service will determine what type of client details service we implement. A simple file-based service might be sufficient. If you wanted to go a lot further with client sign up and managing API keys then you would have to involve a persistence tier for the client details service. Configuring with the default Spring file-based service is trivial:
<oauth:client-details-service id="client-details-service"> <oauth:client client-id="353b302c44574f565045687e534e7d6a" secret="286924697e615a672a646a493545646c" authorized-grant-types="password,refresh_token" authorities="ROLE_TEST" access-token-validity="${oauth.token.access.expiresInSeconds}" refresh-token-validity="${oauth.token.refresh.expiresInSeconds}" /> <oauth:client client-id="7b5a38705d7b3562655925406a652e32" secret="655f523128212d6e70634446224c2a48" authorized-grant-types="password,refresh_token" authorities="ROLE_WEB" access-token-validity="${oauth.token.access.expiresInSeconds}" refresh-token-validity="${oauth.token.refresh.expiresInSeconds}" /> <oauth:client client-id="5e572e694e4d61763b567059273a4d3d" secret="316457735c4055642744596b302e2151" authorized-grant-types="password,refresh_token" authorities="ROLE_IOS" access-token-validity="${oauth.token.access.expiresInSeconds}" refresh-token-validity="${oauth.token.refresh.expiresInSeconds}" /> <oauth:client client-id="302a7d556175264c7e5b326827497349" secret="4770414c283a20347c7b553650425773" authorized-grant-types="password,refresh_token" authorities="ROLE_ANDROID" access-token-validity="${oauth.token.access.expiresInSeconds}" refresh-token-validity="${oauth.token.refresh.expiresInSeconds}" /> </oauth:client-details-service>
Accessing the oauth endpoint now requires a basic authentication header with the client id and secret concatenated with a ":" separator and base64 encoded.
e.g. -H "Authorization: Basic MzUzYjMwMmM0NDU3NGY1NjUwNDU2ODdlNTM0ZTdkNmE6Mjg2OTI0Njk3ZTYxNWE2NzJhNjQ2YTQ5MzU0NTY0NmM="
e.g. -H "Authorization: Basic MzUzYjMwMmM0NDU3NGY1NjUwNDU2ODdlNTM0ZTdkNmE6Mjg2OTI0Njk3ZTYxNWE2NzJhNjQ2YTQ5MzU0NTY0NmM="
Configuring User Authentication Services
The Resource Owner Password flow requires an authentication manager for managing users.
<bean id="passwordEncoder" class="org.springframework.security.crypto.password.StandardPasswordEncoder"/> <sec:authentication-manager alias="userAuthenticationManager"> <sec:authentication-provider user-service-ref="userService"> <sec:password-encoder ref="passwordEncoder"/> </sec:authentication-provider> </sec:authentication-manager>
The password encoder is used to encrypt the password on authentication. The user service also uses the same encoder to encrypt passwords before persisting them.
The user service must implement UserDetailsService and retrieve the user by username.
The user service must implement UserDetailsService and retrieve the user by username.
@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { notNull(username, "Mandatory argument 'username' missing."); User user = userRepository.findByEmailAddress(username.toLowerCase()); if (user == null) { throw new AuthenticationException(); } return user; }
Configuring Token Services
The final piece of configuration is to manage storage and retrieval of access tokens.
<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="client-details-service"/> </bean>
Spring has a handy in-memory implementation that is useful when testing:
<bean id="tokenStore" class="org.springframework.security.oauth2.provider.token.InMemoryTokenStore"/>
For this tutorial I have chosen MongoDB as the persistence technology, but it would be easy to wire in an alternative such as MySql or Redis It is a matter of implementing org.springframework.security.oauth2.provider.token.TokenStore
<bean id="tokenStore" class="com.porterhead.oauth2.mongodb.OAuth2RepositoryTokenStore"> <constructor-arg ref="OAuth2AccessTokenRepository"/> <constructor-arg ref="OAuth2RefreshTokenRepository"/> </bean>
Protecting access to resources
First we need to configure a spring resource server filter. This will check that there is a valid access token in the request header.
<oauth:resource-server id="resourceServerFilter" token-services-ref="tokenServices"/>
Spring security has some useful classes for fine-grain control of roles and permissions. I prefer to use JSR-250 annotations. Luckily it is easy to wire this in. First we need to enable JSR-250 annotations with the following configuration:
<sec:global-method-security jsr250-annotations="enabled" access-decision-manager-ref="accessDecisionManager"/>
Configure an access manager that uses the Jsr250Voter
<bean id="accessDecisionManager" class="org.springframework.security.access.vote.UnanimousBased"> <property name="decisionVoters"> <list> <bean class="org.springframework.security.access.annotation.Jsr250Voter"/> </list> </property> </bean>
Now we can protect REST resource methods with JSR-250 annotations such as @RolesAllowed
Trying it out
Typically the glue between the OAuth server and the application is a user identifier. When a client gets an access token for a user the next step is to typically load data related to that user. This will usually involve building a url with the userId as part of the path
i.e. /v1.0/users/{id}/someresource
A useful service to provide is a way to get user information based on the access token. The application can identify the user that owns the token and return information on that user to the client.
@Path("/v1.0/me") @Component @Produces({MediaType.APPLICATION_JSON}) @Consumes({MediaType.APPLICATION_JSON}) public class MeResource extends BaseResource { @RolesAllowed({"ROLE_USER"}) @GET public ApiUser getUser(final @Context SecurityContext securityContext) { User requestingUser = loadUserFromSecurityContext(securityContext); if(requestingUser == null) { throw new UserNotFoundException(); } return new ApiUser(requestingUser); } protected User loadUserFromSecurityContext(SecurityContext securityContext) { OAuth2Authentication requestingUser = (OAuth2Authentication) securityContext.getUserPrincipal(); Object principal = requestingUser.getUserAuthentication().getPrincipal(); User user = null; if(principal instanceof User) { user = (User)principal; } else { user = userRepository.findByEmailAddress((String)principal); } return user; } }
To test this out start up the application:
> ./gradlew tomcatRun
Execute the following curl statement, substituting the access token that was returned from the login curl above
> curl -v -X GET \
-H "Content-Type: application/json" \
-H "Authorization: Bearer [your token here]" \
'http://localhost:8080/oauth2-provider/v1.0/me'
-H "Content-Type: application/json" \
-H "Authorization: Bearer [your token here]" \
'http://localhost:8080/oauth2-provider/v1.0/me'
To try out some of the other services included in the code see previous posts:
- Part 1: An introduction to writing REST Services in Java
- Email Verification
- Lost Password
- Moving to Production
- JSR 303 Validation
Does it work with java 8?
ReplyDeleteI got error
..
..
com.porterhead.user.UserServiceTest > badNameRequest FAILED
java.lang.ClassFormatError at UserServiceTest.java:48
.
while building
and while running
org.springframework.beans.factory.BeanDefinitionStoreException: Failed to read candidate component class: file [/home/mshanmuga/rnd/oauth2-provider/build/classes/main/com/porterhead/filter/StaticContentFilter.class]; nested exception is org.springframework.core.NestedIOException: ASM ClassReader failed to parse class file - probably due to a new Java class file version that isn't supported yet: file [/home/mshanmuga/rnd/oauth2-provider/build/classes/main/com/porterhead/filter/StaticContentFilter.class]; nested exception is java.lang.IllegalArgumentException
...
Thanks for that.
ReplyDeleteI had not tested against Java 8
Disabled emma and updated spring dependencies to work with Java 8
Very helpful. Thanks very much !
ReplyDeleteHello, nice post.
ReplyDeleteCan you give some advices to implement a persistence service for client applications ? (I saw there is an implementation with mysql (JdbcTokenStore), but how do I do with mongodb?
Thanks
If you pull the code you will see that the implementation of the TokenStore is for MongoDB
ReplyDeleteYes, but I meant store clients_id in Mongo rather than in the xml file :)
DeleteIf you look at the Spring JdbcClientDetailsImplementationService then all you have to do for a MongoDB service is implement:
DeleteClientDetailsService and ClientRegistrationService
in a similar fashion to com.porterhead.oauth2.mongodb.OAuth2RepositoryTokenStore
any chance to change the xml configs to java based ones?
ReplyDeleteThat was on my list of things to do. Could be a while though...
DeleteYou could always submit a pull request :-)
Tried to configure it for the past 2 days with no success :D, it would be interesting though because of the lack of examples.
DeleteGreat work by the way ;)
Since I desided to go with the XML configuration, I am facing this issue, the application boots up normally but when trying to get a token as saw on you example, I am getting this response:
Delete{
error: "unauthorized"
error_description: "There is no client authentication. Try adding an appropriate authentication filter."
}
any ideas on what causes this to happen?
can you post:
Delete1. the command you used to start the app
2. The curl request you sent to it
3. The stacktrace of the error
I've been trying to do this as well except I'm trying to get it to run with Spring Boot. I have the dependencies stabalized, just need that Java Config. Perhaps I'll upload it in a bit if it helps.
DeleteThis comment has been removed by the author.
ReplyDeleteAmazing post! Any chance of merging your previous code that contains the user registration along with this new one so we can have a complete example and we can test it using the browser rather then curl.
ReplyDeleteAll of the registration code and Lost Password code is included. If you want to use it set it up as explained in the previous posts.
DeleteBy default it will run with an in-memory mail sender.
To run it execute:
> ./gradlew tomcatRun
and then open the location in your browser: http://localhost:8080/oauth2-provider/index.html
make sure you have mongod running on port 27017
Excuse my ignorance but how do we go about protecting our own web services. Perhaps you can point me to some tutorial (on Spring security or something). Thanks again for an amazing framework.
ReplyDeleteThe spring OAuth2 sample covers setting up a separate resource server and the sample code shows how to do it: http://projects.spring.io/spring-security-oauth/docs/oauth2.html
DeleteMy code sample has the auth server and resource server together and uses JSR250 annotations so you can protect your services using @RolesAllowed
Sorry for the late response, I got this working using programmatic configuration.
ReplyDeleteUnfortunately my Gradle knowledge is very limited so I cannot configure the project and do a PL.
I have created a Gist (https://gist.github.com/maxsap/da154f99a2dbb414471a), if you like you can use that to configure the programmatic configuration.
Thank you for the help :D
forgot to mention that I have updated Spring, Jersey versions and also the model is now compatible with JPA.
DeleteAre you using JWT instead of storing the token details?
Deletexfghsdrgsdrgtsdfg srgts gsdrgsdfgdfgdfgsdfgsdfghttp://www.moneycontrol.com/mutual-funds/nav/axis-long-term-equity-fund/MAA011A nonymousMay 10, 2015 at 1:31 AM
DeleteAre you using JWT instead of storing the token details?
I have developed a web app (with REST services) with its own ant build scripts, etc. Is there any way to use your sample project to secure my web app but maintain both as a separate projects so one can focus on user registration and authentication and token management and the other on the actual webapp functionality?
ReplyDeleteI have been playing around with it and having some problems with the web-based components.
ReplyDeleteI can create a user account by visiting http://localhost:8080/oauth2-provider/index.html - and I can log in. Once I login, I can see the dashboard with my user name and it says email unverified. When i refresh the page I get an empty dashboard with the console error: [Error] TypeError: 'undefined' is not an object (evaluating 'oauth2.user.user.firstName')
After I login (using web-based login form), hitting http://localhost:8080/oauth2-provider/v1.0/me gives an error:
{"errorCode":null,"consumerMessage":"Internal System error","applicationMessage":"Server error","validationErrors":[]} and on server:
Internal Server Error: org.springframework.security.access.AccessDeniedException: Access is denied
Also I'm trying to manually verify users by updating the mongodb manually (db.app_user.update({'verified':false},{$set:{'verified':true}});) and all the accounts are verified but it still shows it as unverified.
Thanks for any assistance.
The web-based components I included are in no way production quality. I included them merely to give an easy visual door on the functionality. I would use curl as a means to test out the code.
DeleteHaving said that I'll take a look at the problems you mentioned
You cannot access the url http://localhost:8080/oauth2-provider/v1.0/me from a browser as that call is protected and requires an access token in the header.
DeleteYou would have to add a view page and construct the request with JQuery.
Thanks Iain. I understand that the web component are not production quality but I want to try to develop a web-based login and registration and want to understand why the oauth2 variable is not returning a user after a page is refreshed.
DeleteThis comment has been removed by the author.
ReplyDeleteI have been testing and its very good. This could become a powerful framework. Here are a few features that would be nice to have and perhaps you can give us guidance on how to best implement them:
ReplyDelete1- CORS support (probably through a ContainerResponseFilter)
2- Support for 3rd party keys (to allow 3rd party developers access to your API)
3- and of course integration with 3rd party resource servers (FB, Twitter, LinkedIn, etc) as you mentioned for your future series
Thanks again for a great product...
Thanks for giving it a work out.
DeleteThose are some good suggestions (first I have heard of CORS)
I would be happy to merge any Pull Requests if you want to contribute
Hi Iain, have you tried running this in using the production spring profile ? I get tons of errors when doing so (clean build integrationTest)
ReplyDeleteWorks perfectly fine with the default profile.
I would not recommend trying to run tests with anything other than the default profile. This is configured to use in-memory proxies.
DeleteUsing the production profile means that your real database will be used for inserting and deleting data.
If you try and execute the following:
./gradlew tomcatRun -Dspring.profiles.active=production
then you will also get errors if you have not set up your production database config and mail properties.
Gotcha, thanks again
DeleteThis comment has been removed by the author.
DeleteWhat are the steps to move this into production. I guess you need a mongodb up and running and to configure the mail server? What file is used to specify the mail server configuration? If we are creating a WAR to deploy on a remote server, do we have to create one that is production ready? and if yes, how is this done?
DeleteThanks
I added instructions to the ReadMe (https://github.com/iainporter/oauth2-provider)
DeleteI have a client that uploads a file via REST but cannot pass the access token in header (because the client uses a form) but can pass the token as a form field. How can I manually identify the user by looking up the ApiUser using the access token in my resource class (that implements BaseResource)?
ReplyDeleteI don't quite understand why the client cannot set an authorization header.
DeleteBut, you can achieve what you need by injecting the OAuth2AccessTokenRepository into your service class and do a look up with the following method:
public OAuth2AuthenticationAccessToken findByTokenId(String tokenId);
The resultant Access Token object will contain the username.
You can use the UserRepository to do a look up and find the user.
The client is using a Form to submit the file (using GWT) and you cannot assign headers to a request initiated from a form (at least using GWT)
DeleteFor the createUser API, I can't figure out why the response's ApiUser contains a "Name" field set to the email address. I debugged and I can't see that field at all. Any idea ?
ReplyDelete{"apiUser":
{"emailAddress":"user@example.com",
"firstName":null,
"lastName":null,
"age":null,
"id":"8a34d009-3558-4c8c-a8da-1ad2b2a393c7",
"name":"user@example.com"},
Look at com.porterhead.user.api.ApiUser which implements Principal
Delete@Override
public String getName() {
return this.getEmailAddress();
}
A solution is to make sure the user is refreshed on a GET call.
ReplyDeleteI modified the GET api to ensure that happens.
I also added a /v1.0/users{id} PUT to allow updates.
Sample curl:
curl -v -X PUT \
-H "Content-Type: application/json" \
-H "Authorization: Bearer {your oauthToken}" \
-d '{"firstName":"Foo", "lastName": "Bar", "emailAddress":"foobar@example.com"}' \
'http://localhost:8080/oauth2-provider/v1.0/users/{userId}'
and the GET:
curl -v -X GET \
-H "Content-Type: application/json" \
-H "Authorization: Bearer {your oauthToken}" \
'http://localhost:8080/oauth2-provider/v1.0/users/{userId}'
Yes of course that makes sense. I was hoping I could avoid a query to the DB but don't think that will be possible. thanks !
ReplyDeleteHi Iain, can you please discuss a little on how to add Facebook (and other social apps) to this workflow ? Do we need to first login to FB client side and then pass the access token ? ( like in your previous rest-java work) or is this a different approach ?
ReplyDeleteBeen busy on other things but I'll tackle this next
DeleteGreat job Iain, I'm new with REST and your series helped a lot. Thanks
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteHi
ReplyDeleteThank you for the great post
i have a problem of receiving an empty response when i send a correct token request.
so if i execute the token request command mentioned at the post exactly as its is i receive an empty success response, but if i change the command parameters to give me an error, like change the encoded client id, i got an error response
like the following :
===================================
correct command :
--------------------------------------------
curl -v -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Basic MzUzYjMwMmM0NDU3NGY1NjUwNDU2ODdlNTM0ZTdkNmE6Mjg2OTI0Njk3ZTYxNWE2NzJhNjQ2YTQ5MzU0NTY0NmM=" \
'http://localhost:8080/oauth/oauth2/token?grant_type=password&username=user@example.com&password=password'
the response in this case :
========================================
* Hostname was NOT found in DNS cache
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST /oauth/oauth2/token?grant_type=password&username=user@example.com&password=password HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:8080
> Accept: */*
> Content-Type: application/json
> Authorization: Basic MzUzYjMwMmM0NDU3NGY1NjUwNDU2ODdlNTM0ZTdkNmE6Mjg2OTI0Njk3ZTYxNWE2NzJhNjQ2YTQ5MzU0NTY0NmM=
>
< HTTP/1.1 200 OK
* Server Apache-Coyote/1.1 is not blacklisted
< Server: Apache-Coyote/1.1
< Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
< Access-Control-Max-Age: 3600
< Access-Control-Allow-Headers: x-requested-with
< Content-Length: 0
< Date: Wed, 03 Sep 2014 02:10:19 GMT
<
* Connection #0 to host localhost left intact
now when i change the request and give a wrong client id, like changing one letter in the authorization header:
i got the following response:
================================================
< HTTP/1.1 401 Unauthorized
* Server Apache-Coyote/1.1 is not blacklisted
< Server: Apache-Coyote/1.1
< Cache-Control: no-store
< Pragma: no-cache
< WWW-Authenticate: Basic realm="oauth", error="unauthorized", error_description="No client with requested id: 353b302c44577&565045687e534e7d6a"
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Wed, 03 Sep 2014 02:12:28 GMT
<
* Connection #0 to host localhost left intact
{"error":"unauthorized","error_description":"No client with requested id: 353b302c44577&565045687e534e7d6a"}
why i am getting this empty response in success case?
I can't replicate your problem. The url looks wrong.
DeleteIf you have not modified any of the client details then the following should work to register a user and then login:
Register:
curl -v -X POST -H "Content-Type: application/json" \
-H "Authorization: Basic MzUzYjMwMmM0NDU3NGY1NjUwNDU2ODdlNTM0ZTdkNmE6Mjg2OTI0Njk3ZTYxNWE2NzJhNjQ2YTQ5MzU0NTY0NmM=" \
-d '{"user":{"emailAddress":"user@example.com"}, "password":"password"}' \
'http://localhost:8080/oauth2-provider/v1.0/users'
login:
curl -v -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Basic MzUzYjMwMmM0NDU3NGY1NjUwNDU2ODdlNTM0ZTdkNmE6Mjg2OTI0Njk3ZTYxNWE2NzJhNjQ2YTQ5MzU0NTY0NmM=" \
'http://localhost:8080/oauth2-provider/oauth/token?grant_type=password&username=user@example.com&password=password'
The response from the login:
{"access_token":"c0deeea5-9dee-43bc-80cd-4ae476fc8696","token_type":"bearer","refresh_token":"9450289b-f939-40f6-ac13-b312ebf48fb9","expires_in":2269879,"scope":" write read"}
hi
ReplyDeletewhat part is responsible for parsing the base64 authorization code and get the client id from it?
The mechanism for this is provided by Spring Security.
DeleteIf you look in src/main/resources/META-INF/spring/oauth/oauth2-configuration.xml
the token endpoint for retrieving an auth token is defined:
<http pattern="/oauth/token" create-session="stateless" authentication-manager-ref="clientAuthenticationManager"
xmlns="http://www.springframework.org/schema/security">
<anonymous enabled="false"/>
<http-basic entry-point-ref="clientAuthenticationEntryPoint"/>
<access-denied-handler ref="oauthAccessDeniedHandler"/>
<custom-filter ref="corsFilter" after="LAST"/>
</http>
A BasicAuthenticationFilter is added to the call chain by Spring. This filter handles decoding of the Basic Header and delegates to an authenticationManager to authenticate with the decoded client name and client secret.
In this case an In-memory ClientDetailsService matches the credentials against the clients set up in src/main/resources/META-INF/spring/oauth/client-details.xml
Trying to run the generated war file in Tomee but keep getting this error:
ReplyDeletejava.lang.NullPointerException
at com.porterhead.RestResourceApplication.(RestResourceApplication.java:32)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:408)
at java.lang.Class.newInstance(Class.java:433)
at org.apache.openejb.config.AnnotationDeployer$ProcessAnnotatedBeans.deploy(AnnotationDeployer.java:2097)
at org.apache.openejb.config.AnnotationDeployer$ProcessAnnotatedBeans.deploy(AnnotationDeployer.java:1895)
at org.apache.openejb.config.AnnotationDeployer.deploy(AnnotationDeployer.java:358)
at org.apache.openejb.config.ConfigurationFactory$Chain.deploy(ConfigurationFactory.java:401)
at org.apache.openejb.config.ConfigurationFactory.configureApplication(ConfigurationFactory.java:962)
at org.apache.tomee.catalina.TomcatWebAppBuilder.startInternal(TomcatWebAppBuilder.java:1214)
at org.apache.tomee.catalina.TomcatWebAppBuilder.configureStart(TomcatWebAppBuilder.java:1087)
at org.apache.tomee.catalina.GlobalListenerSupport.lifecycleEvent(GlobalListenerSupport.java:130)
at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:117)
at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:90)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5378)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:901)
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:877)
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:649)
at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:1083)
at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1880)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Any ideas how to fix this? Thanks
I deployed it successfully to Tomcat.
DeleteThat stacktrace does not make sense as line 32 is not valid in the RestResourceApplication class.
Have you made any made any modifications to that class?
Sorry just added System.out.println("rootCtx="+rootCtx); at line 31
DeleteHow do you enable JSP processing within this framework? I tried adding a simple <%= new java.util.Date() %> to a jsp file to no avail. I followed instructions at http://stackoverflow.com/questions/12951047/spring-mvc-jsp-mapping. Is there anything in particular that need to be done to enable JSP within this framework? Thanks
ReplyDeleteThat would entail using an MVC approach as opposed to a pure REST application model.
DeleteJQuery has date functions that you can use.
I would need to use JSP for my app. So would you recommend splitting the project into 2 apps? Where my JSP-based app uses REST authentication (from the second app based on your framework)
DeleteI see that Jersey has an extension that might provide what you need: https://jersey.java.net/documentation/latest/mvc.html
DeleteThanks. That worked great.
DeleteThis comment has been removed by the author.
ReplyDeleteI started receiving the following error when trying to login a user (registration works fine): HTTP Status 500 - Request processing failed; nested exception is org.springframework.data.mapping.model.MappingException: No property clientAuthentication found on entity class org.springframework.security.oauth2.provider.OAuth2Authentication to bind constructor parameter to!
DeleteDo you have any idea what this could be? Thanks a bunch
Server side log: http://pastebin.com/ZpLLjFQq
Yes that is due to the default Mongo converter in Spring not being able to bind to the clientAuthentication property.
DeleteThat is why I cobbled together a Custom Converter here:
com.porterhead.oauth2.mongodb.OAuth2AuthenticationReadConverter
It is registered by the com.porterhead.configuration.MongoDBConfiguration class.
Have you made any modifications to the configuration that would cause the ReadConverter class to not be picked up on startup?
Thanks Iain,
DeleteOnly changes I've made was to add a couple of classes to @EnableMongoRepositories(basePackageClasses = {..}
It seems that customConversions() is called normally.
I also created a class that inherits from AbstractBinder for another part of the project but I don't think that should have any effect. Any idea
You might want to pull the latest changes as I upgraded to the latest spring and mongoDB releases a couple of months ago
DeleteI did upgrade to the latest version.
DeleteCould it be because I'm running it on Tomcat 8? Was working fine and it suddenly stopped working.
Deletedoes ./gradlew integrationTest work?
DeleteI've imported the project into a maven project so have not tried running the test. Everything was working fine a few days ago so I'm at a loss. customConversions() is being called though so I'm not sure why this is happening. Is there anything I need to be looking out for to make sure it works.
DeleteThe penny dropped,
DeleteYou must be loading a token that was created before my changes.
The later versions of Spring Security changed the object model and there is no longer a
clientAuthentication property to load.
If you can I would suggest dropping your tables and repopulating
Hello,
ReplyDeleteI've integrated Swagger to this project (oauth2-provider) for providing a clean documentation of the REST API.
Swagger is simply run as a servlet (com.wordnik.swagger.jersey.config.JerseyJaxrsConfig) defined in web.xml. I'd like to programmatically configure this servlet using properties (api version, application name, application description, etc.) defined in app-.properties.
The first step I took was to extend JerseyJaxrsConfig so I can perform custom initialization. For example, one could access PropertySourcesPlaceholderConfigurer using something like context.getBean("mySpringBean") however I'm not sure if that would be the proper way to access the application properties. How would you do it?
Thanks!
Swagger + Jersey2: http://wordnik.github.io/swagger-core/service/java/tomcat-jersey-2.html
I have used Swagger on previous projects and it worked great.
DeleteNot sure on the config but presumably as it is Spring based you can annotate with @Value
Swagger is easy to integrate, but enabling oauth2 is slightly more difficult. By any chance, did you already do it in the past?
DeleteFirst of all maybe, Swagger UI allows to enter an API key
http://petstore.swagger.wordnik.com/
In your blog you mention only once the term "API key" but it's not precisely described. Is the API key the client id or the 64base encoded string "client_id:client_secret"?
I think you will need to add some spring config for each of the urls that are accessed by Swagger.
DeleteAn example:
<http pattern="/api-docs/v1.0/users/**" create-session="stateless" use-expressions="true" entry-point-ref="http403EntryPoint">
<intercept-url pattern="/api-docs/v1.0/users/**" access="permitAll()"/>
</http>
In Swagger, I redirect the user to a login page when he wants to access a method which requires oauth2 authentication. The server returns an access token which is then given to Swagger.
DeleteThe access arrives here in Swagger:
window.authorizations.add("oauth2", new ApiKeyAuthorization("Authorization", "Bearer " + b, "header"));
So far so good! :)
However I get an error in the JS console when doing a request:
XMLHttpRequest cannot load http://localhost:8080/rest-oauth2/v1.0/events/1. Request header field Authorization is not allowed by Access-Control-Allow-Headers.
However I have no problem with a similar curl request and the authorization header (-H "Authorization: Bearer 206d391a-5f11-49db-a9b6-0aa86b884405").
Both browser and curl requests are executed from localhost, which is an allowed origin.
Thanks!
(This is most likely an issue with Swagger, I'll let you know once I found the answer)
DeleteNot sure why it worked but I have added it as an allowed header and allow OPTIONS in the Jersey filter.
DeleteHi,
ReplyDeleteStarting from the GitHub project 'oauth2-provider', I've created a new package to give the possibility to users to create/get/update Items. The implementation of this package is based on the package com.porterhead.user (Item, ApiItem, ItemRepository, ItemService, ItemResource, CreateItemRequest, etc.).
For getting items, the method:
public ApiItem getItem(final @PathParam("id") String itemId, final @Context SecurityContext securityContext)
doesn't work, but everything works fine if reference to the security context is removed:
public ApiItem getItem(final @PathParam("id") String itemId)
However, I'd like to access the security context (as UserResource does) so that I can extract from it the id of the logged in user and thus return only the object associated to the user.
What am I missing? I suspect authentification-managers.xml should be edited but I don't know how.
Pull the latest code and check out my latest Blog Post
Deletehttp://porterhead.blogspot.co.uk/2014/10/implementing-jsr-250-hierarchical-roles.html
It has an example of using SecurityContext
I would need to see some config/code to understand what you are trying to achieve.
DeleteHello,
ReplyDeleteI'd like to enable the HTTPS/SSL mode for your project "oauth2-provider".
Do the settings regard only the container tomcat/jetty or do they involve Jersey or Spring components? Could you please mention briefly the steps you perform to user SSL?
Thanks!
Enabling SSL is something done at the container level.
DeleteAlso to consider is the hosting service which may have different set up requirements (AWS, Heroku, etc)
I did it using Gretty, which is a awesome Gradle plugin. Check it out! (it provides the latest version of Tomcat and Jetty unlike the default tomcat and jetty Gradle plugin in addition to be super easy to configure and well documented)
Deletehttps://github.com/akhikhl/gretty
Thanks, I'll take a look.
DeleteHi
ReplyDeleteweb context goes into web.xml
Which file the following head scripts goes..
Configuring OAuth flows to support
Protecting the token endpoint
Configuring User Authentication Services
Protecting access to resources
Dear Iain Porter,
ReplyDeleteI have read your code. It is a very good demo. I found that you are using AJAX if I want to POST, PUT, GET, something to the server. But if I want to to the old method, by using the "FORM POST" action, how can I modify the code to do so?
Hi Iain . Happy New Year from Madrid :)
ReplyDeleteI have been able to implement the Spring Integration part in the sample project oauth2-provider-master to have guaranteed delivery in the MongoDB database. Works great.
What other rest-java-master project features would be interesting to implement in oauth2-provider-master? I'd like to take (merge) the best things about both projects. What else should be added to oauth2-provider-master project?
The oauth2-provider project is the most up to date project and actively updated.
DeleteIt should contain most of the useful code from the rest-java project (apart form the SI stuff) and more besides.
Btw. If you have anything that you want to contribute to it then by all means issue a pull request.
Thanks Iain. I could contribute uploading the necessary changes to implement guaranteed delivery with MongoDB, although i'm totally newbie in GitHub ... should i do it through the section 'pull request' ?.
DeleteThis link explains it quite well: https://help.github.com/articles/creating-a-pull-request/
DeleteExcellent post Iain. Thanks for sharing. A couple of questions.
ReplyDelete- Is it possible to timeout access tokens? I've played with the app a few times, and i seem to be able to access the API's with the original access_token days later. Any advice?
- I'd like to add a last_logged_in_date. Any advice on how i may go about doing this?
Thanks!
Anre
Arne,
DeleteIf you look in src/main/resources/properties/application.properties you will see two properties that effect the expiration date of the access tokens and refresh tokens:
#Expiry time for access tokens in seconds
oauth.token.access.expiresInSeconds=5184000
#Expiry time for refresh tokens in seconds
oauth.token.refresh.expiresInSeconds=5184000
If you want a last logged in date then the easiest solution would be to add a custom filter and add it to the chain for the /oauth/token endpoint.
Look at oauth2-configuration.xml for an example of this using the corsFilter
Hello,
ReplyDeleteThx alot for your effort. I need to further help. If you could help, i would be appreciated. I want to position mysql instead of mongodb. How can i achieve that?
I'm trying to do the same, but with MariaDB which is a drop in replacement for mysql.
DeleteI'll share when I have any succes
And tbh, i'm wandering that why did not you include social user function that you have written on your previous post. That would be really perfect unrivalled full stack sample
ReplyDeleteTime permitting my next task is to add in support for Authorization grant type
DeleteVery nice posts, Thank you.
ReplyDeleteI have a question about the refresh_token.
How can we run then via curl?
I tried:
curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -H "Authorization: Bearer MzUzYjMwMmM0NDU3NGY1NjUwNDU2ODdlNTM0ZTdkNmE6Mjg2OTI0Njk3ZTYxNWE2NzJhNjQ2YTQ5MzU0NTY0NmM=" -H "Cache-Control: no-cache" -H "Postman-Token: 8c089968-8136-7368-2a3c-ad88cc0eb1c1" -d 'grant_type=refresh_token&refresh_token=7352b75a-d468-499d-85f1-cce1751d1fda' http://localhost:8080/oauth2-provider/oauth/token
Thank you
This should work:
Deletecurl -v -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Basic [BASE 64 Encoded client credentials]" \
'http://localhost:8080/oauth2-provider/oauth/token?grant_type=refresh_token&refresh_token=[The value of the refresh token returned from login]"'
ty
Delete./gradlew appRun -Dspring.profiles.active=production -DMONGODB_HOST=localhost -DMONGODB_PORT=27017
ReplyDeleteThis was not working for me (could not send email ... and more)
So after some time...
----------
./gradlew appRun
+
build.gradle :
--------
...
gretty{
jvmArg '-Dspring.profiles.active=production'
jvmArg '-DMONGODB_HOST=localhost'
jvmArg '-DMONGODB_PORT=27017'
}
...
--------
or
Deletegretty {
jvmArgs = ['-Dspring.profiles.active=production',
'-DMONGODB_HOST=localhost',
'-DMONGODB_PORT=27017']
}
Adding in gretty caused the problem. It looks like gretty does not support system properties.
DeleteI have reverted to using tomcat explicitly in the build.
This should work for you:
./gradlew tomcatRun -Dspring.profiles.active=production -DMONGODB_HOST=localhost -DMONGODB_PORT=27017
Hola !
ReplyDeleteQuisiera saber si es posible dar seguridad a un servicio Rest con Switchyard y resteasy.
Soy novato en esto y si es posible me gustaría que dejaran un ejemplo del como se realizaría.
Has anyone successfully migrated from MongoDB to MySQL ?
ReplyDeleteAny tips?
grtz,
Harry
I just found this:
ReplyDeletehttps://github.com/ben-manes/gradle-versions-plugin
"Displays a report of the project dependencies that are up-to-date, exceed the latest version found, have upgrades, or failed to be resolved."
Mr Porter,
ReplyDeleteI try to apply your solution into my existing project. I stucked with the error that i shared below. And i can not forward. However, I am sure that all complaint classes are actually exists under right package. Seems to be that i am missing something.
Could you plz lighten me? If you wanna see the project. Here it is=> https://github.com/webyildirim/trafikhaberci
thx
tomcat error log start such as:
SEVERE: Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#0': Invocation of init method failed; nested exception is org.springframework.beans.factory.CannotLoadBeanClassException: Cannot find class [com.trafficalarm.rest.security.HierarchicalJsr250Voter] for bean with name 'roleVoter' defined in class path resource [spring/security/security-configuration.xml]; nested exception is java.lang.ClassNotFoundException: com.trafficalarm.rest.security.HierarchicalJsr250Voter
Related cause: org.springframework.beans.factory.CannotLoadBeanClassException: Cannot find class [com.trafficalarm.rest.security.HierarchicalJsr250Voter] for bean with name 'roleVoter' defined in class path resource [spring/security/security-configuration.xml]; nested exception is java.lang.ClassNotFoundException: com.trafficalarm.rest.security.HierarchicalJsr250Voter
Related cause: org.springframework.beans.factory.CannotLoadBeanClassException: Cannot find class [com.trafficalarm.rest.filter.spring.SpringCrossOriginResourceSharingFilter] for bean with name 'corsFilter' defined in class path resource [spring/oauth/oauth2-configuration.xml]; nested exception is java.lang.ClassNotFoundException: com.trafficalarm.rest.filter.spring.SpringCrossOriginResourceSharingFilter
Related cause: org.springframework.beans.factory.CannotLoadBeanClassException: Cannot find class [com.trafficalarm.rest.security.OAuthRestEntryPoint] for bean with name 'oauthRestEntryPoint' defined in class path resource [spring/oauth/oauth2-configuration.xml]; nested exception is java.lang.ClassNotFoundException: com.trafficalarm.rest.security.OAuthRestEntryPoint
Any succes porting to mysql ??
ReplyDeleteplease share.
in "token-store.xml"
Deletein "build.gradle"
// Database
compile "mysql:mysql-connector-java:5.1.34"
// DataSource (HikariCP)
compile "com.zaxxer:HikariCP:2.3.2"
in "app-production.properties"
#Database Configuration
db.driver=com.mysql.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/dbname
db.username=root
db.password=pass123
token-store:
Delete(I can't publish xml ...)
http://pastebin.com/PP9UG7m5
Hi All, Does any one has used OAuth2 client (other than Apache Oltu) in order to connect to the external social sites or Cloud Sites? Have anyone implemented Spring OAuth2 working example?
ReplyDeleteCould you please tell us how to remove the mongodb from this project and replace it by mysql.
ReplyDeleteI removed the oauth2 form mongo but cant do the same for users and verificationtoken.
thank you
anyone any luck? Tried porting java-rest to newer dependencies. But I keep getting errors because of hibernate 3.6 -> 4.3
DeleteCould you help me with this ??
ReplyDeletehttp://stackoverflow.com/questions/28455911/can-not-set-token-defaulttokenservices-field-userconfiguration-tokenservices-to
thank you.
Hey, i occured with the same situation same on yours. Your stack overflow question has removed. Could you resolve it?
DeleteVery good, thank you so much!
ReplyDeleteI am trying to follow your example. i see that the code is already converted to Java annotation - great. Do you have an example to use MySQL instead of Mongo for JDBC?
ReplyDeleteHow can we save a new user that is logging in using OAuth 2 (using Twitter/Google/FB credentials?) - The client will pass an access_token but how do we create a new user or login a user with just an access_token? We can retrieve the email address of the user using the token but then how do we tell your framework to create the user (without a password)?
ReplyDeleteGood question. That involves implementing the Authorization grant type. The spring oauth2 sample project has a good example of how to do this.
DeleteIt is on my long list of things to do as soon as my regular workload clears up a bit.
Thanks for your response. How long would it take someone with Spring experience to implement this? I'm thinking I could outsource this and then contribute the code to your project.
DeleteGood point. I am also interested in this subject
DeleteThis comment has been removed by the author.
ReplyDeleteMr Porter,
ReplyDeleteI have face an exception in my trials. If you could review it, i would be appreciated.
Here is my github: https://github.com/webyildirim/trafikhaberci
Thank you..
And the exception is:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userConfiguration': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private org.springframework.security.oauth2.provider.token.DefaultTokenServices com.trafficalarm.rest.configuration.UserConfiguration.tokenServices; nested exception is java.lang.IllegalArgumentException: Can not set org.springframework.security.oauth2.provider.token.DefaultTokenServices field com.trafficalarm.rest.configuration.UserConfiguration.tokenServices to com.sun.proxy.$Proxy67
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:292)
...
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private org.springframework.security.oauth2.provider.token.DefaultTokenServices com.trafficalarm.rest.configuration.UserConfiguration.tokenServices; nested exception is java.lang.IllegalArgumentException: Can not set org.springframework.security.oauth2.provider.token.DefaultTokenServices field com.trafficalarm.rest.configuration.UserConfiguration.tokenServices to com.sun.proxy.$Proxy67
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:508)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:289)
... 26 more
Caused by: java.lang.IllegalArgumentException: Can not set org.springframework.security.oauth2.provider.token.DefaultTokenServices field com.trafficalarm.rest.configuration.UserConfiguration.tokenServices to com.sun.proxy.$Proxy67
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(Unknown Source)
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(Unknown Source)
at sun.reflect.UnsafeObjectFieldAccessorImpl.set(Unknown Source)
at java.lang.reflect.Field.set(Unknown Source)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:504)
... 28 more
The problem was solved after adding @EnableAspectJAutoProxy(proxyTargetClass=true)
DeleteDetail is here: http://stackoverflow.com/questions/29349692/throwing-an-exception-while-implementing-oauth2
Hi Porter,
ReplyDeleteIn memory token store setting is not working for dev profile, always it is checking for mongo instance
This comment has been removed by the author.
ReplyDeleteI've added an ArrayList to the User (and ApiUser) classes. I'm able to persist an instance of the User class with the new ArrayList but when the user requests his profile or logging-in again, the new ArrayList data is lost.
ReplyDeleteI noticed that oAuth2AuthenticationAccessToken keeps an old instance of the user class and when the user requests a protected resource (like getting his profile) or logging-in again, it uses that instance. Do we need to refresh the token after we update the User instance class? Thanks
I would need to see some code to be able to comment.
DeleteThe User class already persists an ArrayList property (roles)
Well this problem also exists for the Roles property. Basically, if you update the user's role property, when he logs in again it uses the user instance from oAuth2AuthenticationAccessToken and you will lose any changes you've made to User.roles. The issue here is that there's a cached version of the User instance in both oAuth2AuthenticationAccessToken and oAuth2AuthenticationRefreshToken, and any request using the access token will use that instance and override the User instance.
DeleteYou can try this by logging user in, modify Roles in mongo, and then log user again and you will notice that the roles revert back to what you had before you made the manual changes.
DeleteAh, I see what you mean. The token needs to be updated when a change is made to the user. As a workaround your method to retrieve the user details should not rely on the cached info in the Security Context but make a callback to the UserRepository to get the data.
DeleteGreat that worked. Did not realize that the User info cached in Security Context was out of sync. Thanks
DeleteI solved this by modify the TokenStore implementation. So it first removes the token if if exists, and then inserts the new "updated" token, in method storeAccessToken. Like so:
DeleteremoveAccessToken(token);
OAuth2AuthenticationAccessToken oAuth2AuthenticationAccessToken =
new OAuth2AuthenticationAccessToken(
token,
authentication,
authenticationKeyGenerator.extractKey(authentication));
oAuth2AccessTokenRepository.insert(oAuth2AuthenticationAccessToken);
Do you see any pitfalls with this solution?
What is the difference with your project vs Spring Cloud Security?
ReplyDeleteIf we curl with Authorization Basic MzUzYjMwMmM0NDU3NGY1NjUwNDU2ODdlNTM0ZTdkNmE6Mjg2OTI0Njk3ZTYxNWE2NzJhNjQ2YTQ5MzU0NTY0NmM= will give us the token. Then anyone who can steal above header can get the user resource, then how can we secure it?
ReplyDeleteYou can't access a resource with the basic header. That is only for accessing endpoints that return a token. And you can't get a token unless you provide credentials.
DeleteWith that said all communications using the OAuth2 protocol should be over SSL.
I would like to separate Authentication server and Resource server completely, which means the tokenstore cannot be shared as well as they geographically separated. Based on your project, we need to share the access token between authorization server and resource server, so how can we separate them? Is JWT solution for it?
Deletei was able create test user using curl, but i don't see any collections in mongodb.
ReplyDelete> use test
switched to db test
> show collections
>
the name of the mongo db can be found in src/main/resources/properties/application.properties
DeleteThe property: mongo.db.name=oauth-provider
> use oauth-provider
Just wondering why the authentication part is repeated both in the refresh token and access token.
ReplyDelete"authentication" : {
"storedRequest" : {
"authorities" : [
{
"role" : "ROLE_USER",
"_class" : "org.springframework.security.core.authority.SimpleGrantedAuthority"
}
],
"approved" : true,
"responseTypes" : [ ],
"extensions" : {
},
"clientId" : "353b302c44574f565045687e534e7d6a",
"scope" : [
" write",
"read"
],
"requestParameters" : {
}
},
"userAuthentication" : {
"_class" : "org.springframework.security.authentication.UsernamePasswordAuthenticationToken",
"principal" : "4285a4d6-288a-4732-aa5a-a304b2131652",
"credentials" : "37c31d7b5cd8691e193e601810353d07bef26273d229cd2cb9fcca5e7b35624c78e5a807a93d19e9",
"authorities" : [
{
"role" : "ROLE_USER",
"_class" : "org.springframework.security.core.authority.SimpleGrantedAuthority"
}
],
"authenticated" : true
},
"authorities" : [
{
"role" : "ROLE_USER",
"_class" : "org.springframework.security.core.authority.SimpleGrantedAuthority"
}
],
"authenticated" : false
},
I'm getting following error after updating oauth to 2.0.7.RELEASE. Any idea?
ReplyDeletecurl -v -X POST \
> -H "Content-Type: application/json" \
> -H "Authorization: Basic MzUzYjMwMmM0NDU3NGY1NjUwNDU2ODdlNTM0ZTdkNmE6Mjg2OTI0Njk3ZTYxNWE2NzJhNjQ2YTQ5MzU0NTY0NmM=" \
> --data-urlencode 'username=test@example.com' --data-urlencode 'password=password' --data-urlencode 'grant_type=password' \
> 'http://localhost:8080/oauth/token'
* Hostname was NOT found in DNS cache
* Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> POST /oauth/token HTTP/1.1
> User-Agent: curl/7.37.1
> Host: localhost:8080
> Accept: */*
> Content-Type: application/json
> Authorization: Basic MzUzYjMwMmM0NDU3NGY1NjUwNDU2ODdlNTM0ZTdkNmE6Mjg2OTI0Njk3ZTYxNWE2NzJhNjQ2YTQ5MzU0NTY0NmM=
> Content-Length: 65
>
* upload completely sent off: 65 out of 65 bytes
< HTTP/1.1 400 Bad Request
* Server Apache-Coyote/1.1 is not blacklisted
< Server: Apache-Coyote/1.1
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-XSS-Protection: 1; mode=block
< X-Frame-Options: DENY
< X-Content-Type-Options: nosniff
< Cache-Control: no-store
< Pragma: no-cache
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Fri, 08 May 2015 19:57:32 GMT
< Connection: close
<
* Closing connection 0
{"error":"invalid_request","error_description":"Missing grant type"}
I'd like to allow a client request to GET a resource, but allow only registered users to POST. Something like this:
ReplyDeleteWould really appreciate if you could give pointers on how to achieve this. So far my attempts been unsuccessful :(
Thank you in advance.
Mike
shoot, the sample code did not got through. here is what I had in mind:
Delete< http pattern="/rest/v1.0/myobject/**"
create-session="never"
entry-point-ref="oauthAuthenticationEntryPoint"
xmlns="http://www.springframework.org/schema/security" >
< intercept-url pattern="/rest/v1.0/myobject" method="GET" access="ROLE_CLIENT"/>
< intercept-url pattern="/rest/v1.0/myobject" method="POST" access="ROLE_USER"/>
what is the purpose of userconfiguration.java? Is it really needed?
ReplyDeletewhere is the userAuthenticationManager is used?...none of the end point use this authentication manager...How the flow authenticates the username and password ?...
ReplyDeleteHi, I manage to implement the above code and I tested it through rest client. It works. My question is I have a web application with a login which is using spring security. Now I want my login service to authenticate through the webservice using oauth2. That is my web app will consume your web service. Do you have a tutorial on this?
ReplyDeleteHope to get a reply soon.
Thanks in advance
I have some basic web client pages that you can use as the basis for your own implementation.
DeleteSee https://github.com/iainporter/oauth2-provider/blob/master/src/main/webapp/index.html
Hi Iain,
ReplyDeleteThak you for explaing and proving this blog and code. I downloaded and did the build for the first time. The create user worked but when I try to get the token via curl I am getting {"error":"invalid_request","error_description":"Missing grant type"}
Also trying to login via the http://localhost:8080/oauth2-provider/index.html on the gradle console I am getting these errors
logbak: 16:59:36.466 com.porterhead.user.UserService - Credentials [user@example.com] failed to locate a user.
logbak: 16:59:36.473 o.s.s.o.p.endpoint.TokenEndpoint - Handling error: InvalidGrantException, Bad credentials
Looks like some thing changed in the new build. I did not change any version defined in the gradle, Using Java 8. Any idea why?
This looks like a problem with your mongoDB set up.
DeleteHave you checked that the account exists in your DB?
Failing that send me your exact curl statements along with the console output from the client and server.
Hi Porter,
ReplyDeleteI'm not that good in gradle for handling dependencies. So I refactor the code to used maven in my project. Here is the flow of my project:
1-I have an Authorization server and a resource server in separate projects.
2-I then included your mongo token provider.
3-I'm able to login and persist the token without any problem. But when I want to access my protected resource, I have the following stack trace.
org.springframework.data.mapping.model.MappingInstantiationException: Failed to instantiate org.springframework.security.authentication.UsernamePasswordAuthenticationToken using constructor NO_CONSTRUCTOR with arguments
at org.springframework.data.convert.ReflectionEntityInstantiator.createInstance(ReflectionEntityInstantiator.java:64) ~[spring-data-commons-1.9.4.RELEASE.jar:na]
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:250) ~[spring-data-mongodb-1.6.4.RELEASE.jar:na]
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:230) ~[spring-data-mongodb-1.6.4.RELEASE.jar:na]
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readValue(MappingMongoConverter.java:1180) ~[spring-data-mongodb-1.6.4.RELEASE.jar:na]
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.access$200(MappingMongoConverter.java:77) ~[spring-data-mongodb-1.6.4.RELEASE.jar:na]
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$MongoDbPropertyValueProvider.getPropertyValue(MappingMongoConverter.java:1128) ~[spring-data-mongodb-1.6.4.RELEASE.jar:na]
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$MongoDbPropertyValueProvider.getPropertyValue(MappingMongoConverter.java:1091) ~[spring-data-mongodb-1.6.4.RELEASE.jar:na]
at org.springframework.data.mapping.model.PersistentEntityParameterValueProvider.getParameterValue(PersistentEntityParameterValueProvider.java:78) ~[spring-data-commons-1.9.4.RELEASE.jar:na]
at org.springframework.data.mapping.model.SpELExpressionParameterValueProvider.getParameterValue(SpELExpressionParameterValueProvider.java:63) ~[spring-data-commons-1.9.4.RELEASE.jar:na]
at org.springframework.data.convert.ReflectionEntityInstantiator.createInstance(ReflectionEntityInstantiator.java:71) ~[spring-data-commons-1.9.4.RELEASE.jar:na]
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:250) ~[spring-data-mongodb-1.6.4.RELEASE.jar:na]
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:230) ~[spring-data-mongodb-1.6.4.RELEASE.jar:na]
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readValue(MappingMongoConverter.java:1180) ~[spring-data-mongodb-1.6.4.RELEASE.jar:na]
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.access$200(MappingMongoConverter.java:77) ~[spring-data-mongodb-1.6.4.RELEASE.jar:na]
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$MongoDbPropertyValueProvider.getPropertyValue(MappingMongoConverter.java:1128) ~[spring-data-mongodb-1.6.4.RELEASE.jar:na]
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.getValueInternal(MappingMongoConverter.java:864) ~[spring-data-mongodb-1.6.4.RELEASE.jar:na]
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$1.doWithPersistentProperty(MappingMongoConverter.java:279) ~[spring-data-mongodb-1.6.4.RELEASE.jar:na]
My guess is that there is a problem with converting/mapping of the object in the database back to Java object. So I included the bean OAuth2AuthenticationReadConverter in the project. Sorry my question mine be too silly, I don't know exactly where and how I have to reference this bean.
Thanks for your help.
Look at com.porterhead.configuration.MongoDbConfiguration
DeleteThis class extends AbstractMongoConfiguration
and overrides the method to register the custom conversion:
@Override
@Bean
public CustomConversions customConversions() {
List> converterList = new ArrayList>();
OAuth2AuthenticationReadConverter converter = new OAuth2AuthenticationReadConverter();
converterList.add(converter);
return new CustomConversions(converterList);
}
After integrating the converter, I now have the following stack trace
Deleteorg.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type com.mongodb.BasicDBObject to type org.springframework.security.oauth2.provider.OAuth2Authentication
at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:313) ~[spring-core-4.1.8.RELEASE.jar:4.1.8.RELEASE]
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:195) ~[spring-core-4.1.8.RELEASE.jar:4.1.8.RELEASE]
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:176) ~[spring-core-4.1.8.RELEASE.jar:4.1.8.RELEASE]
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readValue(MappingMongoConverter.java:1174) ~[spring-data-mongodb-1.6.4.RELEASE.jar:na]
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.access$200(MappingMongoConverter.java:77) ~[spring-data-mongodb-1.6.4.RELEASE.jar:na]
It's very bizarre. After reading the stack trace, I notice that it seems like there is a problem at this level
OAuth2AuthenticationAccessToken token = oAuth2AccessTokenRepository.findByTokenId(tokenValue);
But I don't know why?
Great post. Thanks a lot
ReplyDeleteCan we have maven version?
ReplyDeleteWhen I visited the url "/oauth/token", i get the error "Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'."
ReplyDeleteThat sounds like a problem with Spring and CSRF.
DeleteSee https://docs.spring.io/spring-security/site/docs/current/reference/html/csrf.html
I also have a companion project for testing out CSRF - https://github.com/iainporter/oauth2-provider-test-client
Could you please implements a TokenStore and save tokens into Redis
ReplyDeletecan any one help me in configuring resource server, oauth2 provider, and the client?. all these 3 components should reside and run in different machine. if you guys coded for this pls share some info.
ReplyDeleteCan you provide a Java Config equivalent to this xml based configuration?
ReplyDeleteIF you are looking for a solution like an extension of Spring Social Security, Check this out :
ReplyDeletehttp://stackoverflow.com/questions/35911723
Hi Ian,
ReplyDeleteyour example is really awesome! I'm using it and adapting to Spring 4.1.6 and Java 8.
Of course I had to do some changes in order to work with this environment and finally is working; however I have some problems with this request:
curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Basic MzUzYjMwMmM0NDU3NGY1NjUwNDU2ODdlNTM0ZTdkNmE6Mjg2OTI0Njk3ZTYxNWE2NzJhNjQ2YTQ5MzU0NTY0NmM=" \
'http://localhost:8080/auth/oauth/token?grant_type=password&username=user@example.com&password=password'
It's response is: {"error":"invalid_grant","error_description":"Bad credentials"}
While debugging I found that when it attempts to load user data at org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService.loadUserByUsername() is using an instance of InMemoryClientDetailsService which only have client data but no users, so it throws NoSuchClientException.
Surely I have a missconfiguration in order to work with Spring 4.1.6. I was wondering if you already have seen this problem.
Can you help me a litle bit with this?
Thanks in advance.
I also met with the same problem trebor mentioned. {"error":"invalid_grant","error_description":"Bad credentials"} caused by InMemoryClientDetailsService when it tries to check userName in the clientDetailsStore.
ReplyDeleteAny idea how to solve this?
First time, it checks for client id then second calls comes with userName. Username is not there in the clientDetailsStore
ReplyDeleteThe InMemoryClientDetailsStore is used to store the client authentication details that are stored in client-details.xml
DeleteTo make a call to oauth/token or v1.0/users POST you need to Base64 encode the clientId and secretId of one of the client configs in that file and send that as the Basic Authentication header.
Can you send the curl statements that you are using to 1. register a new user and 2. ask for a token for that user
Let me explain the implementation.
DeleteMy web.xml
< servlet>
< servlet-name>rest
< servlet-class>org.springframework.web.servlet.DispatcherServlet
< load-on-startup>1
< servlet-mapping>
< servlet-name>rest
< url-pattern>/
< listener>
< listener-class>org.springframework.web.context.ContextLoaderListener
< context-param>
< param-name>contextConfigLocation
< param-value>
/WEB-INF/spring-security.xml
< filter>
< filter-name>springSecurityFilterChain
< filter-class>org.springframework.web.filter.DelegatingFilterProxy
< filter-mapping>
< filter-name>springSecurityFilterChain
< url-pattern>/*
rest-servlet.xml
< beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
< context:component-scan base-package="com.oAuth2.ws" />
< mvc:annotation-driven />
Then I have a controller class
Delete@Controller
@RequestMapping("/api/users")
public class RequestController {
@Autowired
DataService dataService;
@RequestMapping(value = "/", method = RequestMethod.GET)
@ResponseBody
public List< User> list() {
return dataService.getUserList();
}
}
and
public class DataServiceImpl implements DataService {
@Override
public List< User> getUserList() {
List< User> userList = new ArrayList< User>();
userList.add(new User(1, "user_a", "user_a@example.com", "9898989898"));
userList.add(new User(2, "user_b", "user_b@example.com", "9767989898"));
userList.add(new User(3, "user_c", "user_c@example.com", "9898459898"));
return userList;
}
}
User class is a simple java bean
Deployed the above code to Tomcat as oAuth2
Trying to invoke
http://localhost:8081/oAuth2/oauth/token?grant_type=password&client_id=restapp&client_secret=restapp&username=sajin&password=test&scope=read,write
i am not able to put spring-security.xml here due to its size, even after splitting.
ReplyDeleteGreat article Lot's of information to Read...Great Man Keep Posting and update to People..Thanks
ReplyDeleteHow change port 8080 to another?
ReplyDeleteAccess token works fine, when trying to get refresh token i get an internal server error:
ReplyDelete2018-05-04 10:34:02.177 ERROR 7048 --- [tp1827371870-25] o.s.s.o.provider.endpoint.TokenEndpoint : Handling error: NullPointerException, null
java.lang.NullPointerException: null
at org.springframework.security.authentication.AccountStatusUserDetailsChecker.check(AccountStatusUserDetailsChecker.java:32) ~[spring-security-core-4.2.4.RELEASE.jar:4.2.4.RELEASE]
at org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider.authenticate(PreAuthenticatedAuthenticationProvider.java:105) ~[spring-security-web-4.2.4.RELEASE.jar:4.2.4.RELEASE]
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:174) ~[spring-security-core-4.2.4.RELEASE.jar:4.2.4.RELEASE]
at org.springframework.security.oauth2.provider.token.DefaultTokenServices.refreshAccessToken(DefaultTokenServices.java:150) ~[spring-security-oauth2-2.2.1.RELEASE.jar:na]
at org.springframework.security.oauth2.provider.refresh.RefreshTokenGranter.getAccessToken(RefreshTokenGranter.java:47) ~[spring-security-oauth2-2.2.1.RELEASE.jar:na]
at org.springframework.security.oauth2.provider.token.AbstractTokenGranter.grant(AbstractTokenGranter.java:65) ~[spring-security-oauth2-2.2.1.RELEASE.jar:na]
at org.springframework.security.oauth2.provider.CompositeTokenGranter.grant(CompositeTokenGranter.java:38) ~[spring-security-oauth2-2.2.1.RELEASE.jar:na]
at org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer$4.grant(AuthorizationServerEndpointsConfigurer.java:561) ~[spring-security-oauth2-2.2.1.RELEASE.jar:na]
at org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken(TokenEndpoint.java:132) ~[spring-security-oauth2-2.2.1.RELEASE.jar:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_131]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_131]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_131]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_131]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) ~[spring-web-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133) ~[spring-web-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97) ~[spring-webmvc-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827) ~[spring-webmvc-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738) ~[spring-webmvc-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) ~[spring-webmvc-4.3.14.RELEASE.jar:4.3.14.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967) ~[spring-webmvc-4.3.14.RELEASE.jar:4.3.14.RELEASE]
curl -X POST \
http://localhost:8080/login \
-H 'Authorization: Basic c3F1ZWV6ZXI6c3F1ZWV6ZXI=' \
-H 'Cache-Control: no-cache' \
-H 'Postman-Token: 8d8ef632-bb3c-6511-980e-bc45b54b3a6e' \
-H 'content-type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW' \
-F grant_type=refresh_token \
-F refresh_token=17d915ea-cefc-4f7b-8bb8-f72cac74e6cc
any help please!!!
This comment has been removed by a blog administrator.
ReplyDeleteThis comment has been removed by a blog administrator.
ReplyDeleteThis comment has been removed by a blog administrator.
ReplyDeleteThis comment has been removed by a blog administrator.
ReplyDeleteThis comment has been removed by a blog administrator.
ReplyDeleteThis comment has been removed by a blog administrator.
ReplyDeleteThis comment has been removed by a blog administrator.
ReplyDeleteThis comment has been removed by a blog administrator.
ReplyDeleteThis comment has been removed by a blog administrator.
ReplyDeletedgdgbdfhgfgjhngfjh
ReplyDeleteشركة تسليك مجاري
sdfsdgfvdfgdfhg
ReplyDeleteشركة تسليك مجاري