I'm trying to create a REST API following the HTTP method semantics but I got stuck with the DELETE method.
In my use case, the service is behind a gateway that authenticates the user. This service uses a SSO token that then is used to authenticate the user and get his details. From this point, I'm trying to make a call to my service where I use the id of the resource I want to delete as a path variable but then I don't know how to pass the id of the user for validation.
I've read many posts about the problems of adding a body to a DELETE method. I also think adding a custom header to identify the user is not the right way. Out of the options I have, I think only 2 are sensible:
Issue a POST request with the user id as the body. I don't like this one because I'm basically using POST with an identified resource and because semantically sounds wrong to me.
Make the request so the user id is a path variable. It would look like this. path/to/service/resourceId/{resourceId}/userId/{userId}. My problem with this one is that in the POST and PUT requests, the userId is part of the body. The API wouldn't look consistent but I guess I could still change the other 2 so the user id is also part of the url.
Any suggestions?
You should use HTTP header param for passing user token.
#DELETE
#Path("/{id}")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Info deleteInfo(
#HeaderParam("Authorization") String token,
#PathParam("id") Long id){
}
HTTP authentication, maybe? That's what it is for, no? See RFC 7235.
Related
In my server application, I want to consume some third party API using a MicroProfile REST client. To do so, I need to send an Authorization Header with a bearer token.
I don't want to always get a token before I make any call so I need a mechanism to only retrieve a new token if there is no token yet or if the token expired. The token could then be stored and used in each call until it expires. The next call to the API which would cause a HTTP 401 Unauthorized shall then cause a new token to be obtained.
Unfortunately so far I wasn't able to find any resources on how to consume OAuth secured APIs using the MicroProfile REST client. I hope anybody can give me any tips. I'm using Kotlin and Quarkus but Java related documentation would be fine as well. Anything helps.
Here is my rather simple client:
#RegisterRestClient
#Produces(MediaType.APPLICATION_JSON)
interface SomeThirdPartyApiClient {
#POST
#Path("/some/random/url")
fun someRandomUrl(body: SomeJsonRequestObject, #HeaderParam("Authorization") bearer: String): SomeJsonResponseObject
}
As discussed with iabughosh, there seems to be no automatic way of doing what I want to do. Instead I have written the code myself as suggested by iabughosh. I went with the route of catching errors in the call. If the error has a 401 status, then I retrieve a new token and retry the call.
When the application starts and has no token yet, the first call always causes a 401 and then I get the first token. The next 401 appears only when the token expires (or was removed by a server admin prematurely) so then I simply get the token and do the call again.
As for now this seems to work just fine. I'll have to see how it turns out in production when there are a lot of (parallel) calls. If I find a better solution, I'll try to remember this question and update it accordingly.
There isn't any way to pass it at annotation level, through eclipse microprofile configuration, the only way to pass a dynamic token is by adding
#HeadParameter("Authorization") authString
in your rest call, in case you are using jwt, usually you can inject the JsonWebToken and do all the checks with this object, so you wouldn't need that parameter, however, you can add it and just ignore, than in your rest client method declaration you have to add it too (as I seen your case you did it already, just assure the order of parameters is the same), and the restclient will be able to pass the token though the header (you need to pass "Bearer "+tokenString), but you need to access to the code of your rest service.
I am building the restful web service. For the put request, I first find the testBean with the id in the pathvariable. If it does not exist, then I create a new one. I am wondering if it is right to create a new one here, or I should throw the exception. Because id is auto increment, if I create a new TestBean, the id saved in the db is different from the one from the url path parameter.
#PutMapping("/Test/{id}")
public TestBean updateTestBean(#PathVariable long id, #RequestBody TestBean newTestBean) {
return testBeanService.getTestById(id)
.map(testBean -> {
testBean.setBRR(newTestBean.getBRR());
testBean.setModifiedDate(newTestBean.getModifiedDate());
return crewsBeanService.saveTestBean(testBean);
})
.orElseGet(() -> {
newTestBean.setId(id);
return testBeanService.saveTestBean(newTestBean);
});
}
I'd always prefer to keep PUT method idempotent. Idempotency can be explained as how many times you apply a certain "operation", the result will be the same as the first time. Since REST is just a style, it's up to you, but I will always question to me if it makes sense to keep the operation as PUT or POST.
What if the client of your service is impatient and access your PUT service multiple times while the first request is being served?. You may end up creating two users. So throwing an exception is meaningful if the ID doesn't exist.
It can be 400 or 404, I don't prefer 404 but prefer 400 because of the following reasons,
1) It confuses the client of your APIs if the resource is wrong or the ID they are using is wrong.
(You can always differentiate in your error response and provide meaningful information, but still, I don't prefer!)
2) By using 404,
you're telling the user the problem could be permanent or temporary
,for instance, say your service is not properly registered with discovery server(eureka) or is crashed, the discovery server will send 404 until you fix the problem.
By using 400,
you're asking the user to try with different input, in this case, with a different ID. This is permanent...
as you said id is auto-increment and the client cannot decide the value, so until the user fixes the problem by going back and request your POST service for a new ID, the request is "BAD" and cannot be processed.
Based on Single Responsibility Principle, you should have methods which are doing only one thing. So for your question, you need 2 methods for each request:
GET - asking the server for an object, in your case TestBean.
POST - save new objects (you don't need an id for these).
And in your front end application you could use the GET to ask the server if it have the requested object, and if not, maybe you can add a form which on submit will make the POST request with the data provided in the form fields.
PUT should only be responsible for updating a record. If the id of your bean doesn't exist, you will have an exception on your persistence layer. You can catch that exception on your API and return one of the 400's response code, such as BAD REQUEST.
For creation you should use POST, an id should not be provided in that case
This would be the RESTful way of doing this.
404 is the correct return code for a PUT to a non-existent resource, because the URL used does not address an extant resource.
If the Request-URI does not point to an existing resource, and that URI is capable of being defined as a new resource by the requesting user agent, the origin server can create the resource with that URI.
If the server desires that the request be applied to a different URI, it MUST send a 301 (Moved Permanently) response; the user agent MAY then make its own decision regarding whether or not to redirect the request.
https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
For some subscription process, there is a requirement as follows :
User visits our website
For any subscription process, user click the relevant section that should redirect the view to specified external URL having POST parameters.
External party handles the subscription
It could be solved using <form/> defining the method=POST and putting all the required parameters in hidden fields.
But, we have CMS in place so changing the already existing view to support <form/> would be cumbersome.
I was hoping that there could be some solution that will allow in the following way:
User visits the page
User clicks the relevant section and sends the request to our internal mapping (along with some ID).
Using that ID, will prepare the relevant payload and will redirect the view to external url along with that payload (using POST).
Please let me know for more information.
As documented in the Spring Documentation you can return s.th. like redirect:http://myhost.com/some/arbitrary/path in your controller. To do it dynamically, use the following
#RequestMapping("/redirect")
protected String redirect(#RequestParam("url") String url)
{
return "redirect:" + redirectUrl;
}
Of course you can implement a special RedirectService on your own, but that should be simple.
It is not possible to send payload to another endpoint using a redirect. A redirect is a HTTP-Response with response code of 301,302,303,307,308 - each has a different intention (see Wikipedia Statuscode for additional info). It is the responsibility of the client to decide what to do next - e.g. calling the new location with the same payload.
I figured it out the way to perform the required use case.
In order to redirect the view with POST parameters, please refer the steps below
1) User clicks on the link.
2) Firstly redirect to user to internal mapping.(It would be an intermediate GET request before redirection to external endpoint).
Append all the required parameters to this endpoint.
3) Then transfer the view to intermittent form page populating all the fields in hidden tags.
4) Upon loading the page, submit this form to required destination using POST method.
In this way, we could hack this. I believe it's not a clean way, but if we have to do this, it could be done in this way.
I have a Play Framework version 2.2.2 application. It's fairly simple. I am extending the Security.Authenticator to handle my request authentication.
I am passing in (via the request, either GET or POST) an authorization token generated by another app on the domain. This token is looked up in the database, and will eventually return the userId for the user (rather than the String userName like in the Play demo.
What I would like to do, is append the userId to the GET, so that I can extract the parameter with Javascript, as all the JS code I have currently depends on having a userId, and not the authentication token.
Is there any way to accomplish this, or am I trying to go about this the wrong way? I need to avoid using cookies unless it's otherwise impossible.
Thanks!
Edit
Here is what I am intending to do broken down step by step:
Frontend JS makes a request to Play application via GET request from a browser (eg. http://mycompany.com/playapp/home?authToken=5hgys7Sgh2u4iblahblah)
The request contains a authToken parameter, used for lookup in a user auth DB table
Play Security.Authenticator intercepts request before it makes it to its intended controller (let's say the controller is Application.java)
-The Secured class that extends Security.Authenticator looks for the authToken in my GET data.
-The value is used to do a reverse-lookup of the userId.
-The Secured class public String getUsername(Http.Context ctx) is overridden to return the userId as a string (eg. "1234").
Now what I would like to do, is to modify the URL to append the userId on there (eg. http://mycompany.com/playapp/home?authToken=5hgys7Sgh2u4iblahblah&userId=1234)
I'm not entirely sure this is possible... but suffice it to say, I need to make a request via the browser, do the authentication, and then, upon successful request, let javascript know what the userId is. I would like to do this without setting a cookie, but that doesn't seem to likely now, the more that I think of it...
Update:
So what I ended up doing was setting my userId from inside my Secured.java on the session like this:
ctx.session().put("userId", userId);
Then, from whatever method has been authenticated with the annotation, I retreive the userId from the session like this:
Integer userId = Integer.parseInt(ctx().session().get("uid"));
I then pass the userId to my page template, and insert it into a Javascript variable. Works like a charm, and no cookies needed.
Normally you would return userId in the HTML page that the GET returns. userId could be in the page as Javascript or as HTML. Or return it in a cookie and the javascript can get the cookie.
Many potential security issues though. How will you trust the userId coming back in future requests?
I'm using spring-security web authentication with spring-mvc with a custom authentication and all is well so far:
My problem is: /login loads a view with a fully-featured page, but now I have to provide authentication for iframe/popup format (e.g. for an authenticated bookmarklet), so loading a different view (or with different parameters).
I see two solutions that are not overcomplicated:
In my /login action, I have a way (unkown to me so far) to retrieve the original request and check it against a set of URLs that use the simpler view, then choose the matching view. => How do I retrieve this original request?
I make another login action/form, say /login/minimal, which also POSTs to the spring security URL /j_spring_security_check, but I need to implement the request storage/retrieval mechanism, so that the original request is performed after successful login. => I see this has something to do with SecurityContextPersistenceFilter, yet I don't know how to implement it or call it.
If I understand your question correctly, you're looking to vary the login page based on the original request string. Check out this forum post for accessing the original request url from the session. It's for an older version, but you should be able to use it to get started.
Edit I haven't had a chance to validate this, but it looks like the key changed between Acegi security and Spring Security 3. It looks like you can access it from session using the constants in the WebAttributes class. Effectively
//request is a HttpServletRequest object
SavedRequest savedRequest = (SavedRequest)request.getSession().getAttribute(WebAttributes.SAVED_REQUEST);
String url = savedRequest.getRequestURL();
For your first question:
there is a class org.springframework.security.web.authentication.WebAuthenticationDetails
It contains only the IP of the client and its Session, but
it has a method
protected void doPopulateAdditionalInformation(HttpServletRequest request) {}
I belive you could enhance this by subclassing and add the request url. -- But check first if the request is the request from the login form, or the "blocked" request.
Added
Chris Thompson posted an other part of the puzzle to answer your question:
He mentioned that the saved request can be obtained from the session:
//request is a HttpServletRequest object
SavedRequest savedRequest = (SavedRequest)request.getSession().getAttribute(WebAttributes.SAVED_REQUEST);
String url = savedRequest.getRequestURL();
So you can combine this, instead of enhanding the WebAuthenticationDetails you just need to read its already inclueded session.
#see Chris Thompson answer