Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Java EE Security alignment #238

Open
glassfishrobot opened this issue May 23, 2015 · 26 comments
Open

Java EE Security alignment #238

glassfishrobot opened this issue May 23, 2015 · 26 comments
Labels
API (Server) enhancement Adding a new feature or improving an existing one
Milestone

Comments

@glassfishrobot
Copy link

In JavaEE 7 we have some security problems with WebSocket.

An authenticated session, with a valid Session.getUserPrincipal()
doesn't authenticates in the container on websocket events, so EJB / CDI calls are unauthenticated.

I've tested with WildFly 8.2.0 and GlassFish 4.1, with a sample app
which calls EJB methods from @onopen, @onclose and @OnMessage.

Although we can workaround these issues with interceptors and vendor
specific security managers, it's a common use case for JavaEE applications and an important requirement for cloud/SaaS applications.

I've created an open-source library to get workaround these problems in
JBoss/WildFly. It's called "JBoss Security Extended" and is available on maven central with GAV "com.github.panga:jboss-security-extended:1.0.0".

Library source and docs:
https://github.com/panga/jboss-security-extended
WebSocket sample app source using library:
https://github.com/panga/websocket-auth

@glassfishrobot
Copy link
Author

@glassfishrobot Commented
Reported by panga

@glassfishrobot
Copy link
Author

@glassfishrobot Commented
Issue-Links:
is related to
WEBSOCKET_SPEC-197

@glassfishrobot
Copy link
Author

@glassfishrobot Commented
@pavelbucek said:
not sure whether this is a direct duplicate or just consequence of #197. Anyway, I don't see what WebSocket spec can do here - if you have to call proprietary APIs to achieve what you want, we cannot really make that a requirement for all implementations, since that would imply tight integration with single CDI implementation.

@glassfishrobot
Copy link
Author

@glassfishrobot Commented
panga said:
I think it's consequence of #197 and it answers the question 4 of that issue "Is the caller Principal passed on to EJB from WebSocket (I think the answer is yes)?"

The answer is No.

I only need call proprietary API to workaround the problem because spec doesn't cover Websocket vs CDI/EJB integration.

Containers should care about websocket integration with Java EE, not the implementations (Tyrus / Undertow).

@glassfishrobot
Copy link
Author

@glassfishrobot Commented
tremes said:
Hi,
I am not 100% sure I understand this issue correctly but I think it's related to https://issues.jboss.org/browse/WELD-2028. This should be IMO clarified.

@glassfishrobot
Copy link
Author

@glassfishrobot Commented
panga said:
You're right, but it's the container responsibility to integrate WebSocket with JAAS, there's nothing to do in WELD, because EJBs are also affected.

I have a test case attached with the issue.

@glassfishrobot
Copy link
Author

@glassfishrobot Commented
arjan_t said:

You're right, but it's the container responsibility to integrate WebSocket with JAAS,

Note that JAAS is not the universal Java EE security framework. In fact, almost nothing in Java EE refers to JAAS, which is itself a Java SE framework from which Java EE only uses a minimal number of types. What you're probably looking for here is the integration of WebSocket with the native security internals of Servlet, EJB and JCA, such that the authenticated identity (the native, vendor specific implementation of it) propagates to WebSocklet.

Servlet, EJB and JCA may use something JAAS based for the initial authentication, but there is no requirement for that and its just an implementation detail. JASPIC does define a bridge profile for JAAS LoginModules, but that is fully optional.

As we're planning to define a (injectable) SecurityContext in the Java EE security EG that is intended to be used "everywhere", it would be great if we could somehow include WebSockets here. One thing to take into account here is that establishing an authenticated identity is only really specified for Servlet and SOAP, and is primarily per request. The authentication mechanism (e.g FORM or BASIC) has to be aware of the session. In other words, the authenticated identity doesn't automatically stick to the HTTP session.

@glassfishrobot
Copy link
Author

@glassfishrobot Commented
This issue was imported from java.net JIRA WEBSOCKET_SPEC-238

@glassfishrobot
Copy link
Author

@bekwam
Copy link

bekwam commented Jan 3, 2019

A Websocket call from a properly-authenticated user should be able to call a secured EJB.

The spec does say that Websockets is built off of the "Servlet defined security mechanism" in 8.1 and 8.2. When I define a security-constraint for my Servlet with a role and a Basic login-config, my identity is propagated to the EJBs. However, when I define an identical security-constraint for my Websocket, the identity is not propagated to the EJB, although I do get a password challenge.

I think the identity should survive the initial upgrade and carry over to all subsequent post-open calls.

My Servlet and Websocket observations have been on WildFly.

@joakime
Copy link
Contributor

joakime commented Jan 3, 2019

Once upgraded you are no longer HTTP or within the scope of a Servlet, so there's that to contend with.
There's open issues surrounding this nuance for WELD/CDI (#197) and HttpSession too (#175).
The HttpServletRequest and HttpServletResponse are both recycled once upgraded as well.
The only time you can safely use Session.getUserPrincipal() (currently) is from @OnOpen or the ServerEndpointConfig.Configurator.

@bekwam
Copy link

bekwam commented Jan 3, 2019

Are these the only options for using EJBs with Websockets?

  1. Write unsecured EJBs (no RolesAllowed)
  2. Use a façade EJB marked with RunAs and PermitAll to call a secured EJB
  3. Use a proprietary extension that saves off the Principal and re-applies it to the secured EJB context

@markt-asf markt-asf added API (Server) enhancement Adding a new feature or improving an existing one Jakarta EE 10 and removed Priority: Major Type: Bug labels May 6, 2020
@markt-asf
Copy link
Contributor

When this was originally discussed in the WebSocket EG the concern was that the life cycle of the HTTP session was separate from that of the WebSocket session. What if the app expires the HTTP session? What impact does that have on any authenticated identity in WebSocket? Does termination of the HTTP session imply termination of the WebSocket session? And so on. There didn't seem to be a solution that was appropriate for all use cases.
HandshakeRequest.getUserPrincipal() and HandshakeRequest.getHttpSession() were added to allow applications to implement the solution that best met their requirements.
What if we added Session..getUserPrincipal() and Session.getHttpSession() that return null on clients and on servers return the HTTP session and/or associated user principal if

  • there was a valid HTTP session and/or associated user principal at the time the WebSocket session was initiated; and
  • the HTTP session and/or associated user principal are still valid

In terms of implementation my expectation is that the WebSocket retains a copy of the HTTP session ID, looks up the HTTP session on a call to Session..getUserPrincipal() or Session.getHttpSession() and returns either the requested object or null if the HTTP session is no longer valid.
If this sounds as if it could work, I could code up something in Tomcat that did essentially the same thing but via custom user properties that interested users could test. Any takers?

@joakime
Copy link
Contributor

joakime commented May 6, 2020

I don't like the idea of polluting the client Session with server / servlet specific concepts.

Perhaps a new ServerSession extends Session for these kinds of things?

Since these are very "Servlet" specific, would it make sense to have a Session.getHttpContext() with these 2 fields exposed?

Alternatively we could have @ServerEndpoint based endpoints support both HttpSession and UserPrincipal for the on @OnOpen event.
Or even introduce a new server specific annotation akin to @OnUpgrade to support transferring any variety of HTTP specific details to the endpoint before @OnOpen
Heck, make it support using the HttpServletRequest object as an optional parameter (before it actually upgrades the connection).

In Eclipse Jetty we have the following exposed (currently) in the websocket api:
(sorted by most commonly used)

  • HttpServletRequest.getRemoteAddr() - a super common requested feature
  • HttpServletRequest.getRemotePort()
  • HttpServletRequest.getLocales() - a surprisingly common requested feature
  • HttpServletRequest.getParameterMap()
  • HttpServletRequest.getSession(false) - not as commonly requested as above
  • HttpServletRequest.getLocalAddr()
  • HttpServletRequest.getLocalPort()
  • HttpServletRequest.getRemoteUser()
  • HttpServletRequest.getUserPrincipal()
  • HttpServletRequest.getAuthType()
  • (a copy of) the HttpServletRequest.getAttributes() map at the time of upgrade

With a @OnUpgrade annotation, all of those become doable in a standard way.

@markt-asf
Copy link
Contributor

Good point re the client Session. I'm still thinking about pros/cons of the approaches you suggest. What I am going to do is use this as the master issue and mark the various other issues that touch on this as duplicates. I'll note any additional specific requirements in this issue when I do that.

@markt-asf
Copy link
Contributor

#219 also requested ServletContext HttpSession and for @OnOpen, @OnMessage, @OnError and @OnClose.

@markt-asf
Copy link
Contributor

#218 was another request for the HttpSession along with some discussion on exactly what is exposed (original object, (read-only) copy.
#217 was another request for the ServletContext
And finally, #175 (HttpSession last accessed time) needs to be kept in mind.

@jhanders34
Copy link

Is anything being done in JakartaEE 9 to resolve this issue? It has recently come up again in a question in stackoverflow.

@joakime
Copy link
Contributor

joakime commented Jun 22, 2020

#219 also requested ServletContext HttpSession and for @OnOpen, @OnMessage, @OnError and @OnClose.

HttpSession isn't valid for @OnMessage and @OnError and @OnClose (the HTTP side of things is long gone and recycled at the point in time we can call those methods)

@jhanders34
Copy link

I was speaking specifically about the initial post in this issue referring to calling ejbs on web socket method calls.

@joakime
Copy link
Contributor

joakime commented Jun 22, 2020

I was speaking specifically about the initial post in this issue referring to calling ejbs on web socket method calls.

Issue #197 would need to be solved for that.

The current problem has to do with scope, as in websocket connection scope, it's length (in time) complicates a lot of things.
Many things around websocket will need to change (CDI / EJB / Servlet) in order for this to have a chance to work.
That kind of change cannot happen in Jakarta EE 8 or Jakarta EE 9 (too drastic).
Jakarta EE 10 is the first time this has a hope of being addressed. (we are currently working on Jakarta EE 9)

@VGerris
Copy link

VGerris commented Nov 20, 2020

and now, when I am reading is we have this issue 5 years open and the other 7 years.
Are there any ways forward ?
For starters a good documentation on how to fetch the proper session and corresponding websocket session in a thread safe way?
The problems with time could perhaps be mitigated by suggesting some scenarios for authentication?
Then a user/developer knows the best practice for handling that.
Those best practices can then perhaps be added as an extension.

@joakime
Copy link
Contributor

joakime commented Nov 20, 2020

@VGerris HTTP overlaps with WebSocket only during the handshake.

So that means all behaviors that come from HTTP (be it Authentication, Authorization, Cookies, HttpSession, etc) can only apply during the WebSocket Handshake.
And once that Handshake is over, the information obtained during that WebSocket Handshake becomes immutable.

Once you are upgraded fully into WebSocket, there's no longer any "live" information from HTTP.
Example:

  • The HttpSession object is useless at this point, it is essentially a read-only empty shell, as the HttpSession object was recycled (per Servlet spec) once the Http Request / Response exchange was deemed complete. You should have pulled the information you need out of the live HttpSession during handshake for use later, you cannot continue to hold onto, or access, the HttpSession object.
  • Authentication on WebSocket server is based on HTTP behaviors, and only validates / updates during the Http request. This means that if the User should be logged out, have their roles revoked, their authorization changed, etc ... This is not communicated to WebSocket as there's no HTTP behavior to trigger this change. There is also no event on the authorization / authentication to hook into to notice this kind of change.

To fix the behaviors on HttpSession would require a dramatic change on the Servlet spec for HttpSession, likely rendering the HttpSession API no longer backward compatible with older versions of the Servlet spec. The HttpSession would essentially have to be a "live" object where changes from other threads would need to be represented in the HttpSession object whereever it may be. This is very problematic for clustered environments, and it one of the reasons this isn't done currently.

I question if we would even still be able to use the standard java.security package classes (eg: java.security.Principal), as there's no notification / listener / event mechanism present in that API for long lived authn/authz like on WebSocket.

In short, this is not an easy thing to fix, to support what you are looking for would requires changes in a half dozen or more Jakarta EE specs outside of WebSocket, and those change would be dramatic.

@VGerris
Copy link

VGerris commented Nov 20, 2020

Thank you for the quick reply. I have found some posts on StackOverflow from you too I think, thanks for that.
I am mostly looking for a concrete description of a threadsafe implementation for the handling and storing of the client id of the http session. I understand after that it is not in the spec what to do when it comes to checking if the websocket is authenticated (like the HTTP session could or could not be). I wonder if there are best practices for that that kind of add to requirements of an implementation in a secure 'enterprise' environment.
Those best practices I would like to see available in additional libraries, that could be run in any J2EE server (like Tomcat).
If I get tot the point of understanding enough to write it myself in a proper way, I will publish it.
I was surprised that a protocol like this has still so many implementation challenges that don't seem to be 'a standard'.
Thanks for all your efforts and contributions, they are greatly appreciated!

@faceless2
Copy link

faceless2 commented Dec 3, 2020

While I completely understand the argument for HttpSession, I don't see it for ServletContext. ServletContext and ServerContainer represent essentially the same thing when run in an engine that supports both. The ServletContext is responsible for loading the classes implementing my WebSocket Endpoints, and I can get a reference for the WebSocketContainer from the ServletContext by calling servletContext.getAttribute("javax.websocket.server.ServerContainer"), so I know a ServletContainer contains at most one of these.

What I can't do is get a reference from the ServerContainer back to the ServletContext, which makes it impossible to read initialization parameters from the web.xml in the WS environment. The approach sometimes recommended (take it from the HttpSession in ServletEndPointConfig.Configurator.modifyHandshake) is no use if there is no HttpSession in place.

There are obviously ways around this, but they shouldn't be necessary. The addition of a WebSocketContainer.getUserProperties() method would allow me to pass through data from the ServletContext; the addition of Object getServletContext() on ServerContainer would be better; or, failing that, adding the same method to HandshakeRequest would be another approach that is clearly not going to cause any conceptual issues, given we can already access HttpSesssion from the same object.

@joakime
Copy link
Contributor

joakime commented Dec 3, 2020

The ServletContext exist to handle the world of Servlets. (Filters, various Servlet Listeners, HTTP, Authentication, Cookies, HttpSession, request dispatching, etc..)

The HTTP/1.1 upgrade to websocket is the overlap with the Servlet world.
Once you are upgraded to WebSocket, you are are no longer in the world of Servlets too.

From WebSocket point of view, the ServletContext exists to facilitate HTTP/1.1 negotiation (less so for the abbreviated negotiation for websocket in HTTP/2 or HTTP/3) to an eventual WebSocket connection. Once the websocket connection exists, you are no longer part of the Servlet world. And only tangentially connected to the ServletContext as a result of the context path the websocket was negotiated against. (meaning the websocket connections should shutdown with the ServletContext during shutdown/destroy)

To me, where we are now is a historical side effect of how the Servlet spec was written.
It really didn't have (for a long time) the idea of long lived lifecycles for objects.
Then HttpSession came along to fill that kind of role, but even that was designed for (relatively) short lived http exchanges (performing updates/merges at the end of exchange only).

Meanwhile, the JSP spec comes along and identifies a hierarchy of the behaviors in the servlet context, creating a few JSP scopes: application, session, request. page.
These make sense and are happily adopted.

Next, CDI comes along and identifies a few scopes on the servlet spec (application, session, request, etc) for it's own purposes.
These are nice and tidy, a good layering, where the next scope acts like a subset of the higher scope (this is just a viewpoint of how those scopes behave, and is not what the CDI spec actually says)

Now you have WebSocket, which is very long lived.
You can easily have during a single WebSocket connection a few sessions, many requests, authentication that changes (denied access, logout, etc), and it could even outlive the application as well (in a cross context scenario).
Neither the Servlet spec, the WebSocket spec, or CDI have hammered out what to do in these kinds of scenarios. (new scopes? new lifecycles identified and detailed? new listeners? new annotations? new exceptions? new behaviors? backward incompatible changes to existing behavior? we'll see.)
Most of the behaviors we have currently rely on the performing actions around the start (sometimes the end) of the scopes already defined.
However, the long lived websocket connection break this model in interesting ways. In WebSocket we have the HTTP land upgrade, the WebSocket open handshake, the WebSocket connection read/write, and finally the WebSocket close handshake. (imagine if we had to update/query various states of scopes or objects in the ServletContext on each websocket connection event - read/write of frames or messages - in order to trigger some kind of behavior)

Note: I've seen folks have some success with the websocket authn/authz angle by controlling things on the non-websocket components (like the chat component, or data component, or pubsub component, etc), tossing exceptions when the authn/authz changes in specific ways (rejecting the action that the websocket message triggered) so that the websocket component can identify when to start the websocket close handshake. Don't ask me how they did that, I'm just aware that they solved their issue in this general way.

There is a lot of people interested, a few aborted starts, and a realization that its going to take a significant effort on the parts of many people, and jakarta specs, to pull off successfully.

Stay tuned, it will likely happen, and this specific issue (focused on just the security angle) is just one small part of the bigger picture to make it happen.

@markt-asf markt-asf added this to the backlog milestone May 4, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API (Server) enhancement Adding a new feature or improving an existing one
Projects
None yet
Development

No branches or pull requests

7 participants