Class PetstoreConsoleBoundary

java.lang.Object
org.apache.juneau.petstore.console.PetstoreConsoleBoundary
All Implemented Interfaces:
jakarta.servlet.Filter

public class PetstoreConsoleBoundary extends Object implements jakarta.servlet.Filter
The console's CSRF boundary: a LoopbackBoundaryFilter applied to /console and everything under it.

On console requests the boundary pins the Host, and on console writes it also requires a same-origin Origin, a JSON body and the per-process SynchronizerToken. An allowed console request carries the token to the page renderer, which stamps it on <body data-juneau-csrf> for the console toolkit to send back.

The effective method

A request is judged on the method it claims, not only on its request line. A method query parameter or an X-Method, X-HTTP-Method-Override, X-HTTP-Method or X-Method-Override header naming anything other than a safe method makes the request a write, whether or not the resource is configured to honour that override. So GET /console/pets/1?method=DELETE needs the same Origin and token a real DELETE does, and a later change to a resource's allowed method overrides cannot open a hole here.

The API's token

The /petstore API is not covered by this boundary: it sits behind its own, PetstoreApiBoundary, which has a different token. The console's one write to the API, its photo upload (PUT /petstore/pets/{id}/photo), carries the API's token, not this one. So on every console request it allows, this boundary also publishes a fresh API token as a String under PetstoreApiBoundary.TOKEN_ATTRIBUTE, for the upload form to stamp. A refused request publishes nothing.

A runner must build this boundary with the PetstoreApiBoundary instance it mounts on the API. The no-arg constructor builds a private one that nobody mounts, so the console's uploads are then refused: visibly, and closed rather than open.

This boundary does not mark the response no-store for the API's token: the console page shell already does so for every page carrying the console's token. When the response is already committed, the shell drops only its own token attributes, so on that rare path the API's token can still be emitted, possibly without no-store. That is accepted, as PetstoreApiBoundary describes.

What is outside

The /petstore-ui app (which only reads) is covered by neither boundary. The Jetty deployment puts the microservice admin resources behind a third boundary of their own.

The authority

The single accepted Host is the "petstore.console.authority" setting when set, else localhost:<port> with the port the request arrived on. It is fixed on the first console request, after the server has bound, so a runner that falls back to a random port still gets the right value. Any other spelling of the same server (127.0.0.1, a LAN host name) is answered with 421 on console paths.

Set "petstore.console.authority" whenever the browser's Host differs from the bound port: behind a Docker port mapping or a reverse proxy, the default answers 421. A browser leaves the port out of Host for ports 80 and 443, so the value is then the bare host name.

The boundary expects the Origin http://<authority>, so this works for plain HTTP only. An HTTPS deployment's console stays plain-HTTP-loopback only. The API boundary reads no forwarded headers either, and the demo does not configure the framework boundary's CONTAINER origin source, so behind a TLS-terminating proxy the API refuses writes too (see PetstoreApiBoundary).

Example:

var reg = new FilterRegistrationBean<>(new PetstoreConsoleBoundary(apiBoundary)); reg.addUrlPatterns(PetstoreConsoleBoundary.URL_PATTERN);

In a Juneau microservice, contribute a ServletFilterRegistration @Bean instead:

@Bean public ServletFilterRegistration consoleBoundary() { return ServletFilterRegistration.create(new PetstoreConsoleBoundary(apiBoundary)) .urlPatterns(PetstoreConsoleBoundary.URL_PATTERN) .dispatcherTypes(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.ASYNC, DispatcherType.ERROR) .order(ServletFilterRegistration.HIGHEST_PRECEDENCE) .build(); }

where apiBoundary is the PetstoreApiBoundary the runner mounts on the API.

  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    static final String
    Setting (system property, environment, or any registered source) naming the one accepted console host and port.
    static final String
    The covered path: the console resource's mount point.
    static final String
    The servlet URL pattern the boundary is registered under.
  • Constructor Summary

    Constructors
    Constructor
    Description
    Constructor for a console boundary with an API boundary of its own.
    Constructor.
  • Method Summary

    Modifier and Type
    Method
    Description
    void
    doFilter(jakarta.servlet.ServletRequest req, jakarta.servlet.ServletResponse res, jakarta.servlet.FilterChain chain)
    Applies the boundary to console requests and passes every other request straight through.

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

    Methods inherited from interface jakarta.servlet.Filter

    destroy, init
  • Field Details

  • Constructor Details

  • Method Details

    • doFilter

      public void doFilter(jakarta.servlet.ServletRequest req, jakarta.servlet.ServletResponse res, jakarta.servlet.FilterChain chain) throws IOException, jakarta.servlet.ServletException
      Applies the boundary to console requests and passes every other request straight through.

      An allowed console request also carries the API's token (see PetstoreApiBoundary).

      The path check makes the boundary safe to call for every request (for example when registered under a broader pattern), not only when a container maps it to URL_PATTERN.

      Specified by:
      doFilter in interface jakarta.servlet.Filter
      Throws:
      IOException
      jakarta.servlet.ServletException