jetty.project icon indicating copy to clipboard operation
jetty.project copied to clipboard

map Jakarta.servlet.http.HttpServletRequest to org.eclipse.jetty.server.Request

Open fgolzari opened this issue 1 year ago • 2 comments

Jetty Version jetty-12

Jetty Environment ee10

Java Version Java 21

Question I want to upgrade our project from jetty 9 to 12. In the previous version, a servlet class was written in our project, whose doGet method is as follows

public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException  {

     Request request= (req instanceof Request) ? (Request) req : null;
     
     //other codes

     UserIdentity identity= loginService.login(null, token, request);
      
      //other codes
      
      Authentication authentication= new UserAuthentication(...);
      request.setAthentication(authentication);
      
      //other codes
}

Request package : org.eclipse.jetty.server.Request HttpServletRequest package: javax.servlet.http.HttpServletRequest UserIdentity package: org.eclipse.jetty.server.UserIdentity and loginService is a object of : org.eclipse.jetty.security.LoginService

Because that in jetty 9 org.eclipse.jetty.server.Request extends javax.servlet.http.HttpServletRequest, we cast HttpServletRequest class to Request class and therefore we did not have any problems in the code. but in jetty 12 org.eclipse.jetty.server.Request does not extend HttpServletRequest. loginService.login() in jetty 12 Takes org.eclipse.jetty.server.Request as input, Furthermore HttpServletRequest does not have a method to set authentication, so we need to have the Request object

How do I map HttpServletRequest that I received as input of the servlet method into org.eclipse.jetty.server.Request that I can pass to LoginService.login() method? How do I initialize org.eclipse.jetty.server.Request to send to this method?

Other than mapping, is there a better solution?

fgolzari avatar Jun 26 '24 14:06 fgolzari

The core LoginService is not the place you call login() and where you get a UserIdentity out of it.

That's the role of LoginAuthenticator, it has the login() method you want.

https://github.com/jetty/jetty.project/blob/15bcc5ecb07ad391841e240754915bca33c22f2b/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginAuthenticator.java#L46-L58

To see a LoginAuthenticator in action, see the ServerUpgradeRequestTest.java

The key takeaway is that the login occurs via the Servlet authentication techniques, based on constraints. If you get to the point of a HttpServletRequest existing, you are already past the point where servlet constraints are implemented (where login occurs).

joakime avatar Jun 26 '24 17:06 joakime

Furthermore HttpServletRequest does not have a method to set authentication, so we need to have the Request object

Sounds like what you are looking for is the HttpServletRequest.login() method.

https://github.com/jakartaee/servlet/blob/6.0.0-RELEASE/api/src/main/java/jakarta/servlet/http/HttpServletRequest.java#L549-L574

Been there since Servlet 3.0

Example usage: https://github.com/jetty/jetty.project/blob/jetty-12.0.x/jetty-ee10/jetty-ee10-demos/jetty-ee10-demo-jetty-webapp/src/main/java/org/example/LoginServlet.java

joakime avatar Jun 26 '24 17:06 joakime

Thanks a lot Mr @joakime for your answer.

The key takeaway is that the login occurs via the Servlet authentication techniques, based on constraints. If you get to the point of a HttpServletRequest existing, you are already past the point where servlet constraints are implemented (where login occurs).

I did not understand this part of your speech. Thank you for giving me a clearer explanation.

My main problem right now is: in the old version, Our code worked in such a way that we had implemented a Filter in our code and when deploying war file on Jetty, we identified this Filter to it and Jetty itself passed Request extends HttpServletRequest to it. Now, instead of Filter, how should I call the login method (in what way or in which method) so that I can introduce it to Jetty exactly like Filter, and Jetty itself fills Request and sends it to my code. What should I replace now so that I can introduce it to Jetty exactly like Filter and it will fill Request and send it to my code?

In general, how is the login operation done in Jetty 12? Please send me an example of it? How can I override the login action that is implemented in the Jetty and make it known to Jetty and leave the entire work to Jetty itself?

Jetty is not embeded in our code and we deploy our project on Jetty. It's just that we have used the login method of Jetty library in our code. And I want to write the code in such a way that Jetty itself turns the request that comes to it from client into Request and calls our login method.

fgolzari avatar Jul 02 '24 06:07 fgolzari

A few more comments that might be helpful.

If you use the EE8 or EE9 environment of jetty-12, then the Servlet Request passed does extend a nested.Request in the same way that jetty-9 did. It might be simpler to get your app working first on ee8 or ee9 and then migrate to ee10.

However, as @joakime has said, it looks like your application is trying to mimic the behaviour of a SecurityHandler. Rather than replicate that code, it may be far simpler to implement your own Authenticator rather than write a handler that is doing Authentication. Note that in EE10, the request is not mutable and authentication is done by wrapping rather than setting.

gregw avatar Jul 02 '24 07:07 gregw

@joakime , @gregw I have made a very big mistake in explaining the problem And that is that we call these codes inside the filter and not inside the servlet.

public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException  {
     Request request= (req instanceof Request) ? (Request) req : null;
     //other codes
     UserIdentity identity= loginService.login(null, token, request);
      //other codes
      Authentication authentication= new UserAuthentication(...);
      request.setAthentication(authentication);
      //other codes
}

In fact, this part is not the method of doGet of a servlet, but the implementation of doFilter method of a Filter

public void doFilter(HttpServletRequest req, HttpServletResponse res,FilterChain Chain) throws ServletException, IOException  {
     Request request= (req instanceof Request) ? (Request) req : null;
     //other codes
     UserIdentity identity= loginService.login(null, token, request);
      //other codes
      Authentication authentication= new UserAuthentication(...);
      request.setAthentication(authentication);
      //other codes
}

fgolzari avatar Jul 02 '24 10:07 fgolzari

I have made a very big mistake in explaining the problem And that is that we call these codes inside the filter and not inside the servlet.

If you are handling the request in a Filter or Servlet, then you are already dispatched to the Filter Chain, and that means you are past the point of Servlet constraints and Servlet authentication. (The "Authentication" of the request is already set by this point)

Instead of doing that login in a Filter, write a proper authentication layer, and define the constraints in your webapp to use that authentication layer (you wont have that Filter too).

Other libraries that want security / authentication / authorization, but want to handle it during a Servlet dispatch (in a Filter or Servlet) do not use actually use the Servlet spec authentication / constraints to accomplish it. (eg: Spring security, and many others) But this means an entirely new security framework and APIs that your webapps must use instead of the Servlet security APIs.

joakime avatar Jul 02 '24 13:07 joakime

This is a very high level description of the steps involved. (there's a lot of nuance and detail missing)

  1. Connnection arrives on network
  2. HTTP protocol is parsed
  3. Request object is created
  4. WebApp to call is determined based on contextPath of available WebApps and path present on Request
  5. WebApp receives Request handling
  6. Servlet Security is handled
  7. Session handling occurs
  8. FilterChain is calculated (all Filters to eventual end Servlet)
  9. Constraints based on FilterChain performed
  10. HttpServletRequest and HttpServletResponse are created
  11. FilterChain servicing is called (first Filter, or if no Filters, the Servlet itself)
  12. FilterChain is determined based on

The LoginService and things like the Authenticator happens around step 6.

You are attempting to do things all the way down at step 12.

joakime avatar Jul 02 '24 15:07 joakime

@gregw , @joakime Both of you mentioned that I should write my own authenticator. Could you please show me an example of doing this in a project on GitHub or anywhere else?

Is it possible to write my own authentication using handlers? Jetty 12 is still too dumb for me

And the next question, when deploying the project on Jetty 12, how do we introduce the handlers to it?

fgolzari avatar Jul 02 '24 16:07 fgolzari

@fgolzari It is possible to write your "own authentication using a handler". You will need to reverse engineer what the SecurityHandler is doing and do that yourself.

But why do that? the SecurityHandler is designed to be pluggable with Authenticators. So just plug in your own algorithm there and don't duplicate all the effort in SecurityHandler. Start of simple by looking at org.eclipse.jetty.security.authentication.BasicAuthenticator to see how that works for very simple authentication of every request. If your authentication wants to be session based, then look at org.eclipse.jetty.security.authentication.FormAuthenticator.

For a more complex Authenticator you can look at org.eclipse.jetty.security.openid.OpenIdAuthenticator or this PR that is adding an Authenticator for etherium.

As for adding handlers to jetty-12, there are many answers that depend on how you are using the server, if you are using servlets etc. etc. Have a read of the documentation available via https://jetty.org/

gregw avatar Jul 03 '24 00:07 gregw

.... and yet another approach.... If you really want to do it in a filter, then just so the request you forward to return the values you want for authentication. No need to modify the underlying request, as you have already passed the votes authentication later and we don't think you are authenticated no matter what you do in a filter.

gregw avatar Jul 03 '24 02:07 gregw

I think we've given you several directions to try, so I'm closing this now.

gregw avatar Jul 03 '24 02:07 gregw