Is there a possibility in Play! Framework to "replay" a Http.Request? Replay in the sense of re-applying the request as if it was received again.
Context Information
In my specific use-case I'm trying to resolve an issue where a POST request (eg. a form submission) is rejected if the user's session is no longer valid. Responding to the POST request with a redirect to the login page will result in the POST data being lost. Which is obviously bad from the end-users perspective.
My current idea is to store the original Http.Request object in a HashMap (or any other kind of store) and then respond with a redirect to the login page. After the user has logged in again, the previously stored request should be replayed as if the user would have re-submitted the form, and the Result of this replay can then be returned back directly to the user.
The first part works fine, storing the original POST Http.Request in a HashMap and then flagging the user's session so the original request can later be retrieved again. After the user has logged in, I can retrieve the original POST request, but I'm unsure if and how it is possible to replay this request now. I could probably convert the Http.Request to a WSRequest and send it from the backend to itself with a WSClient, but this seems a bit hacky, and there probably is a simpler solution.
The code below is just for showcasing an example and is not actual source code. Please ignore the obvious issues it has. My question concerns the part marked with TODO.
public class DummyController extends Controller {
private final FormFactory formFactory;
private final HashMap<UUID, Http.Request> rejectedRequests;
#Inject
public DummyController(FormFactory formFactory) {
this.formFactory = formFactory;
this.rejectedRequests = new HashMap<>();
}
// GET endpoint for the login form
public Result login() {
return ok("render login form");
}
// POST endpoint for the login form submission
public Result loginPost(Http.Request request) {
Form<LoginForm> form = formFactory.form(LoginForm.class).bindFromRequest(request);
if (form.hasErrors()) {
return badRequest("wrong credentials");
} else {
return request.session().get("original-req")
.map(UUID::fromString)
.map(rejectedRequests::get)
.map(originalRequest -> {
// TODO: how to replay the original request here?
return TODO(request).removingFromSession(request, "original-req");
})
.orElseGet(() -> redirect("/"));
}
}
// POST endpoint for some form submission
public Result formSubmit(Http.Request request) {
if (request.session().get("login").isPresent()) {
final UUID key = UUID.randomUUID();
rejectedRequests.put(key, request);
return redirect("/login").addingToSession(request, "original-req", key.toString());
}
return ok("handle form submit");
}
}
Related
I want to create a service that returns a list of online users. The user logs on using JWT tokens -- but I am unsure how to get online users -- or/and return a list of users but indicate if they are online or not.
Would I have to stash session tokens/emails that have logged on/logged off in the mongodb - and what if they don't log out?
My current code looks like this
#CrossOrigin
#GetMapping("/api/getActiveUsers")
public ResponseEntity<Object> activeUser(HttpServletRequest request) {
TokenManagement user = new TokenManagement();
try {
// return the user authenticate
HttpSession session = request.getSession(true);
List<Object> userListHttp = (List<Object>) Arrays.asList(session.getAttribute("user"));
// alternative way for get user logged
List<String> userList = getUsersFromSessionRegistry();
return ResponseEntity.ok().body(userListHttp);
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
public List<String> getUsersFromSessionRegistry() {
return sessionRegistry.getAllPrincipals()
.stream()
.filter((u) -> !sessionRegistry.getAllSessions(u, false)
.isEmpty())
.map(o -> {
if (o instanceof Person) {
return ((Person) o).getEmail();
} else {
return o.toString();
}
}).collect(Collectors.toList());
}
You can extract the user from the JWT with its expiring.
Then you can use a cache (consider Redis for example) storing the user on a record that automatically expires when the JWT expires.
If a user explicitly logout simply remove that user from the cache.
So to count the users, you need only to count items in the cache.
This will not grant you that if a client has an error and disconnects without an explicit logout you will have an error on the logged user number, but it is mitigated by the expiring of the cache
I suggest to use a Redis instead of a local cache because it will works also if you are in a microservice environment with multiple instances of your micro service, because the logged users are stored in an external cache common to all microservices instances
You can use a Filter to intercept all the HTTP incoming requests:
A filter is an object that performs filtering tasks on either the request to a resource (a servlet or static content), or on the response from a resource, or both.
public class SessionFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
HttpServletRequest req = (HttpServletRequest) request;
String bearer = req.getHeader("authorization");
String jwt = bearer.substring(7); // Remove Beared at the beginning
String username = extractUsername(jwt);
Date expiringDate = extractExpiring(jwt);
insertInCache(username, expiringDate);
chain.doFilter(request, response);
}
...
private String extractUsername(String jwt) {
// Use libraries to extract the username from the jwt
}
private Date extractExpiring(String jwt) {
// Use libraries to extract the expiring from the jwt
}
private void insertInCache(String username, Date expiringDate) {
// Insert username in the cache with automatic expiring
}
}
This code will intercept all incoming requests, extract the token, parse it and insert the user in the cache. Consider using any java library to extract the informations from the JWT to create the methods extractUsername and extractExpiring.
This code is just a base code to program. You need to complete it with:
manage not authenticated requests
manage explicit logout (in the controller)
add a method to count users inquiring cache (in the controller)
I have a website through which you can create bundles and add custom or predefined tasks to them.
Everything works okay, I can change all these fields whenever I want. Once all these fields look alright to you, you have to click the "Save" button. Once you click it, the fields are validated through several methods. If all the fields were validated successfully, Ajax sends a post request to my Spring controller which then stores everything into a database. After that, I would like to redirect user to the page which displays all the existing bundles.
I have already tried to do this:
#RequestMapping(value = "/bundle", method = RequestMethod.POST, consumes = {"application/octet-stream", "multipart/form-data"})
public void bundle(MultipartHttpServletRequest request,
HttpServletResponse response) throws IOException {
// Code to store bundles to a database.
// Redirect
response.setHeader("Location", "http://localhost:8080/bundles");
response.setStatus(302); //302 Found
// I have also tried to replace above two statements with this
response.sendRedirect("http://localhost:8080/bundles");
}
The above code does execute and the request is sent to /bundles
But I seem to be stuck on the initial page, no redirect was made.
I had the same problem as you have. I solved the issue by redirecting in the Front-End with Angular.
You can use the answer from your HTTP-Request in javascript and then redirect from there.
My Server-Side code:
#PostMapping(AdminToolConstants.MAPPING_CHECK_USER)
public ResponseEntity checkUser(HttpServletResponse response, #RequestBody UserDto userDto) throws IOException{
if (userService.checkUser(userDto)) {
return new ResponseEntity(HttpStatus.OK);
} else {
return new ResponseEntity(HttpStatus.BAD_REQUEST);
}
}
Client-side javascript:
angular.module('admintool.services', []).factory('UserService', ["$http", "CONSTANTS", function($http, CONSTANTS) {
var service = {};
service.checkUser = function (userDto) {
return $http.post(CONSTANTS.checkUser, userDto).then(function (value) {
window.location.href = "/";
}).catch(function (reason) { window.location.href = "/register" });
};
return service;
}]);
Inside .then I redirect the user when the, for example, login was successfull and inside .catch if the login wasn't successfull.
My objective is to pass model attributes from controller to JSP page during a redirect and avoid the attribute being displayed in URL. The source code below is validating login from datastore using java data objects.
Controller:
#Controller
public class LoginController {
int count;
PersistenceManager pm = PMF.get().getPersistenceManager();
//Instance of data class
User user;
ModelAndView modelAndView=new ModelAndView();
#RequestMapping(value="/Login",method = RequestMethod.POST)
public ModelAndView loginValidate(HttpServletRequest req){
//Getting login values
String uname=req.getParameter("nameLogin");
String pswd1=req.getParameter("pswdLogin");
count=0;
user=new User();
//Generating Query
Query q = pm.newQuery(User.class);
q.setFilter("userName == userNameParam");
q.declareParameters("String userNameParam");
try{
List<User> results = (List<User>) q.execute(uname);
for (User u: results) {
String userName=u.getUserName();
if(userName.equals(uname)){
System.out.println(u.getPassword());
if(u.getPassword().equals(pswd1)){
count=count+1;
modelAndView.setViewName("redirect:welcome");
modelAndView.addObject("USERNAME",uname);
return modelAndView;
}
//rest of the logic
}
JSP:
<h1>Welcome ${USERNAME} </h1>
My current URL is /welcome?USERNAME=robin
My goal is to display it as /welcome
Also, my page is supposed to display "Welcome robin" whereas it displays only Welcome.
RedirectAttributes only work with RedirectView, please follow the same
#RequestMapping(value="/Login",method = RequestMethod.POST)
public RedirectView loginValidate(HttpServletRequest req, RedirectAttributes redir){
...
redirectView= new RedirectView("/foo",true);
redir.addFlashAttribute("USERNAME",uname);
return redirectView;
}
Those flash attributes are passed via the session (and are destroyed immediately after being used - see Spring Reference Manual for details). This has two interests :
they are not visible in URL
you are not restricted to String, but may pass arbitrary objects.
You need to be careful here because I think what are you trying to do is not supported for a good reason. The "redirect" directive will issue a GET request to your controller. The GET request should only retrieve existing state using request parameters, this is the method contract. That GET request should not rely on a previous interaction or on any object stored some where in the session as a result of it. GET request is designed to retrieve existing (persisted) state. Your original (POST) request should have persisted everything you need for you GET request to retrieve a state.
RedirectAttributes are not designed to support you in this case, and even if you managed to correctly use it it will only work once and then they will be destroyed. If you then refresh the browser you will get an application error because it cannot find your attributes anymore.
I have a scenario where I want to store session information across multiple sessions in Application # 2. We have two applications deployed on a tomcat server. Our use case is as follows:
A. Web Application # 1 makes a HTTP Post request to Application # 2 using a HTTP Rest Client. POST request contains a JSON http request body encapsulating the data to be send to Application # 2. The code block is as follows:
RestTemplate template = new RestTemplate();
final SearchCustomer customer = new SearchCustomer();
restTemplate.execute(
SEND_CUSTOMER_PROFILE, HttpMethod.POST,
new SearchRequestCallback(searchCustomer), null);
The request callback function is
static class SearchRequestCallback implements RequestCallback {
/**
* Write a JSON response to the request body.
*/
#Override
public void doWithRequest(ClientHttpRequest request) throws IOException {
HttpHeaders httpHeaders = request.getHeaders();
List<MediaType> acceptableMediaTypes = new LinkedList<>();
acceptableMediaTypes.add(MediaType.APPLICATION_JSON);
httpHeaders.setAccept(acceptableMediaTypes);
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
request.getBody().write(
new Gson().toJson(this.searchCustomer).getBytes(StandardCharsets.UTF_8.displayName()));
}
}
The second application has a Spring controller with the following set up
#Controller
public class SearchCustomerController {
/**
* Builds customer profile knowledge graph.
*
* <p>This is invoked as an synchronous request.
*/
#RequestMapping(value="/searchProfilePayload.go", method=RequestMethod.POST)
#ResponseStatus(HttpStatus.OK)
public void constructSearchCustomerProfileKnowledgeGraph(
#RequestBody final SearchCustomer customer, HttpServletRequest request) {
UserContext userContext =
(UserContext) request.getSession().getAttribute("userContext");
if (userContext == null) {
// Perform heavy operation to fetch user session.
userContext = UserContextHelper.getUserContext(request);
request.getSession("userContext", userContext)
}
userContext.setCustomerProfile(customer);
}
}
When I make a call to another URI within the application # 2 say via browser, I want it done in such as way that the session attributes are retained when making this call. Is there a way to do that?
I know about URL rewriting that stores JSESSIONIDin the cookie, but I don't think how you can set the value when making a rest call, and using the same JESSIONID to maintain session attributes.
Is there a better way to do this? These have no answers. I have looked at these links, but none seem to answer my question.
HTTP and Sessions
comparison of ways to maintain state
jraahhali is spot on.
Set the cookie header with the value of JSESSIONID=${sessionId} or use it directly in the url as per the URL rewriting link.
First step is to retrieve the JSESSIONID from the initial response (this will depend on how you decide to set the session id - URL or Cookies, lets assume by cookie for now)
#Override
public void doWithRequest(ClientHttpRequest request) throws IOException {
HttpHeaders httpHeaders = request.getHeaders();
List<MediaType> acceptableMediaTypes = new LinkedList<>();
acceptableMediaTypes.add(MediaType.APPLICATION_JSON);
httpHeaders.setAccept(acceptableMediaTypes);
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
request.getBody().write(
new Gson().toJson(this.searchCustomer).getBytes(StandardCharsets.UTF_8.displayName()));
ClientHttpResponse response = request.execute();
String sessionId = response.getHeaders().get(HttpHeaders.SET_COOKIE).split(":")[1].trim(); // I didnt test this, will prolly get a NPE :P
this.sessionId = sessionId;
}
Then in subsequent requests (ie from the app #1 or a browser or whatever)
if (this.sessionId != null && !this.sessionId.equals(""))
httpHeaders.set(HttpHeaders.COOKIE, "JSESSIONID=" + this.sessionId);
// ...
request.execute();
Note if you really want to use a browser as the other client then I would use the URL rewriting method for ease of use ...
I believe I have basic authentication working but I'm not sure how to protect resources so that they can only be accessed when the user is signed in.
public class SimpleAuthenticator implements Authenticator<BasicCredentials, User> {
UserDAO userDao;
public SimpleAuthenticator(UserDAO userDao) {this.userDao = userDao;}
#Override
public Optional<User> authenticate(BasicCredentials credentials) throws AuthenticationException
{
User user = this.userDao.getUserByName(credentials.getUsername());
if (user!=null &&
user.getName().equalsIgnoreCase(credentials.getUsername()) &&
BCrypt.checkpw(credentials.getPassword(), user.getPwhash())) {
return Optional.of(new User(credentials.getUsername()));
}
return Optional.absent();
}
}
My Signin resource is like this:
#Path("/myapp")
#Produces(MediaType.APPLICATION_JSON)
public class UserResource {
#GET
#Path("/signin")
public User signin(#Auth User user) {
return user;
}
}
And I sign the user with:
~/java/myservice $ curl -u "someuser" http://localhost:8080/myapp/signin
Enter host password for user 'someuser':
{"name":"someuser"}
Question
Let's say the user signs in from a browser or native mobile app front end using the /myapp/signin endpoint. How then can I protect another endpoint, say, /myapp/{username}/getstuff which requires a user to be signedin
#GET
#Path("/myapp/{username}/getstuff")
public Stuff getStuff(#PathParam("username") String username) {
//some logic here
return new Stuff();
}
There are 2 things when you are trying to implement REST. One is Authentication (which seems that you have got it working) and other is Authorization (which is what I believe your question is).
The way I have handled it in dropwizard before is, with every user signin, you return some kind of access_token (this proves they authenticated) back to the client which has to be returned by them in EVERY successive call they make as a part of some header (normally this is done through "Authorization" header). On the server side, you will have to save/map this access_token to THAT user before returning it back to the client and when all the successive calls are made with that access_token, you look up the user mapped with that access_token and determine if that user is authorized to access that resource or not. Now an example:
1) User signs in with /myapp/signin
2) You authenticate the user and send back an access_token as a response while saving the same on your side, such as, access_token --> userIdABCD
3) The client comes back to /myapp/{username}/getstuff. If the client does not provided the "Authorization" header with the access_token you gave them, you should return 401 Unauthorized code right away.
4) If the client does provide the access_token, you can look up the user based on that access_token you saved in step # 2 and check if that userId has access to that resource of not. If it does not, return 401 unauthorized code, and if it does have access, return the actual data back.
Now coming ot the "Authorization" header part. You could get access to "Authoroziation" header in all of your calls using the "#Context HttpServletRequest hsr" parameter but does it make sense to add that parameter in each call? No it doesn't. This is where the Security Filters help in dropwizard. Here's an example to how to add security filter.
public class SecurityFilter extends OncePerRequestFilter{
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException{
String accessToken = request.getHeader("Authorization");
// Do stuff here based on the access token (check for user's authorization to the resource ...
}
Now, which resource does this security filter really protects? For that you will need to add this filter to the specific resources you want to protect which can be done as follows:
environment.addFilter(SecurityFilter, "/myapp/*");
Remember on thing here that both your urls /myapp/signin and /myapp/{username}/getstuff, both will go through this security filter, BUT, /myapp/signin will NOT have an access_token, obviously because you haven't given any to the client yet. That wil have to be taken care of in the filter itself such as:
String url = request.getRequestURL().toString();
if(url.endsWith("signin"))
{
// Don't look for authorization header, and let the filter pass without any checks
}
else
{
// DO YOUR NORMAL AUTHORIZATION RELATED STUFF HERE
}
The url that you are protecting will depend on the how your urls are structured and what you want to protect. The better urls you design, the easier it will be to write security filters for their protection With the addition of this security filter the flow will be like this:
1) User goes to /myapp/signin. The call will go through the filter and because of that "if" statement, it will continue to your ACTUAL resource of /myapp/signin and you will assign an access_token based on successful authentication
2) User makes a call to /myapp/{username}/mystuff with the access_token. This call will go through the same security filter and will go through the "else" statement where you actually do your authorization. If the authorization goes through, the call will continue to you actual resource handler, and if not authorized, 401 should be returned.
public class SecurityFilter extends OncePerRequestFilter
{
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException
{
String url = request.getRequestURL().toString();
String accessToken = request.getHeader("Authorization");
try
{
if (accessToken == null || accessToken.isEmpty())
{
throw new Exception(Status.UNAUTHORIZED.getStatusCode(), "Provided access token is either null or empty or does not have permissions to access this resource." + accessToken);
}
if (url.endsWith("/signin"))
{
//Don't Do anything
filterChain.doFilter(request, response);
}
else
{
//AUTHORIZE the access_token here. If authorization goes through, continue as normal, OR throw a 401 unaurhtorized exception
filterChain.doFilter(request, response);
}
}
catch (Exception ex)
{
response.setStatus(401);
response.setCharacterEncoding("UTF-8");
response.setContentType(MediaType.APPLICATION_JSON);
response.getWriter().print("Unauthorized");
}
}
}
I hope this helps! Took me about 2 days to figure this out myself!
Sorry for being a simple user . I believe you can protect the resource by using a #Auth User user
public Service1Bean Service1Method1(
#Auth User user,
#QueryParam("name") com.google.common.base.Optional<String> name) {