micronaut-openapi icon indicating copy to clipboard operation
micronaut-openapi copied to clipboard

Custom context-path, swagger-ui, and `res` folder

Open bmunyan opened this issue 2 years ago • 6 comments

I am having an issue with rendering the swagger-ui view in my application that uses a custom context-path. I am seeing 401 responses attempting to access some of the resources in the res folder. Note that when I enter /api/content/swagger/my-application.yml I correctly see the YAML file.

Steps to Reproduce

My Micronaut version is 3.7.0 and Micronaut-OpenAPI is version 4.5.2

I have an application running on a custom context-path of /api/content. I also have the following configurations for swagger:

micronaut:
  application:
    name: content-api
  server:
    context-path: /api/content
  router:
    static-resources:
      swagger:
        paths: classpath:META-INF/swagger
        mapping: /swagger/**
      swagger-ui:
        paths: classpath:META-INF/swagger/views/swagger-ui
        mapping: /swagger-ui/**
  security:
    intercept-url-map:
      - pattern: /**/swagger-ui/**
        httpMethod: GET
        access:
          - isAnonymous()
      - pattern: /**/swagger/**
        httpMethod: GET
        access:
          - isAnonymous()

My openapi.properties file indicates:

swagger-ui.enabled=true
micronaut.openapi.server.context.path=/api/content

Expected Behaviour

I'm expecting to see the swagger-ui.

Actual Behaviour

My logs indicate 401 responses retrieving the javascript and CSS resources:

DEBUG i.m.h.s.netty.RoutingInBoundHandler - Request GET /api/content/swagger-ui
DEBUG i.m.s.a.AuthenticationModeCondition - CookieBasedAuthenticationModeCondition is not fulfilled because micronaut.security.authentication is not one of [cookie, idtoken].
DEBUG i.m.s.t.reader.HttpHeaderTokenReader - Looking for bearer token in Authorization header
DEBUG i.m.s.t.reader.DefaultTokenResolver - Request GET, /api/content/swagger-ui, no token found.
DEBUG i.m.security.rules.IpPatternsRule - One or more of the IP patterns matched the host address [0:0:0:0:0:0:0:1]. Continuing request processing.
DEBUG i.m.s.rules.AbstractSecurityRule - The given roles [[isAnonymous()]] matched one or more of the required roles [[isAnonymous()]]. Allowing the request
DEBUG i.m.security.filters.SecurityFilter - Authorized request GET /api/content/swagger-ui. The rule provider io.micronaut.security.rules.ConfigurationInterceptUrlMapRule authorized the request.
DEBUG i.m.h.s.netty.RoutingInBoundHandler - Request GET /api/content/res/swagger-ui-bundle.js
DEBUG i.m.h.s.netty.RoutingInBoundHandler - No matching route: GET /api/content/res/swagger-ui-bundle.js
DEBUG i.m.h.s.netty.RoutingInBoundHandler - Request GET /api/content/res/swagger-ui-standalone-preset.js
DEBUG i.m.h.s.netty.RoutingInBoundHandler - No matching route: GET /api/content/res/swagger-ui-standalone-preset.js
DEBUG i.m.h.s.netty.RoutingInBoundHandler - Request GET /api/content/res/swagger-ui.css
DEBUG i.m.h.s.netty.RoutingInBoundHandler - No matching route: GET /api/content/res/swagger-ui.css
DEBUG i.m.s.t.reader.HttpHeaderTokenReader - Looking for bearer token in Authorization header
DEBUG i.m.s.t.reader.DefaultTokenResolver - Request GET, /api/content/res/swagger-ui-bundle.js, no token found.
DEBUG i.m.security.rules.IpPatternsRule - One or more of the IP patterns matched the host address [0:0:0:0:0:0:0:1]. Continuing request processing.
DEBUG i.m.s.rules.InterceptUrlMapRule - No url map pattern exact match found for path [/api/content/res/swagger-ui-bundle.js] and method [GET]. Searching in patterns with no defined method.
DEBUG i.m.s.rules.InterceptUrlMapRule - No url map pattern match found for path [/api/content/res/swagger-ui-bundle.js]. Returning unknown.
DEBUG i.m.s.t.reader.HttpHeaderTokenReader - Looking for bearer token in Authorization header
DEBUG i.m.s.t.reader.DefaultTokenResolver - Request GET, /api/content/res/swagger-ui.css, no token found.
DEBUG i.m.security.filters.SecurityFilter - Authorized request GET /api/content/res/swagger-ui-bundle.js. No rule provider authorized or rejected the request.
DEBUG i.m.security.rules.IpPatternsRule - One or more of the IP patterns matched the host address [0:0:0:0:0:0:0:1]. Continuing request processing.
DEBUG i.m.s.rules.InterceptUrlMapRule - No url map pattern exact match found for path [/api/content/res/swagger-ui.css] and method [GET]. Searching in patterns with no defined method.
DEBUG i.m.s.rules.InterceptUrlMapRule - No url map pattern match found for path [/api/content/res/swagger-ui.css]. Returning unknown.
DEBUG i.m.security.filters.SecurityFilter - Authorized request GET /api/content/res/swagger-ui.css. No rule provider authorized or rejected the request.
DEBUG i.m.s.t.reader.HttpHeaderTokenReader - Looking for bearer token in Authorization header
DEBUG i.m.s.t.reader.DefaultTokenResolver - Request GET, /api/content/res/swagger-ui-standalone-preset.js, no token found.
DEBUG i.m.security.rules.IpPatternsRule - One or more of the IP patterns matched the host address [0:0:0:0:0:0:0:1]. Continuing request processing.
DEBUG i.m.s.rules.InterceptUrlMapRule - No url map pattern exact match found for path [/api/content/res/swagger-ui-standalone-preset.js] and method [GET]. Searching in patterns with no defined method.
DEBUG i.m.s.rules.InterceptUrlMapRule - No url map pattern match found for path [/api/content/res/swagger-ui-standalone-preset.js]. Returning unknown.
DEBUG i.m.security.filters.SecurityFilter - Authorized request GET /api/content/res/swagger-ui-standalone-preset.js. No rule provider authorized or rejected the request.
DEBUG i.m.h.s.netty.RoutingInBoundHandler - Response 401 - GET /api/content/res/swagger-ui-bundle.js
DEBUG i.m.h.s.netty.RoutingInBoundHandler - Response 401 - GET /api/content/res/swagger-ui-standalone-preset.js
DEBUG i.m.h.s.netty.RoutingInBoundHandler - Response 401 - GET /api/content/res/swagger-ui.css

Environment Information

  • Operating System: Windows
  • Micronaut Version: 3.7.0
  • JDK Version: 17 (Also, I'm using Groovy)

Example Application

Sorry, I can't give this.

bmunyan avatar Oct 07 '22 19:10 bmunyan

Add to config:

micronaut:
  router:
    static-resources:
      swagger-ui-res:
        paths: classpath:META-INF/swagger/views/swagger-ui/res
        mapping: /res/**

altro3 avatar Oct 07 '22 19:10 altro3

Updated application.yml:

  router:
    static-resources:
      swagger:
        paths: classpath:META-INF/swagger
        mapping: /swagger/**
      swagger-ui-res:
        paths: classpath:META-INF/swagger/views/swagger-ui/res
        mapping: /res/**
      swagger-ui:
        paths: classpath:META-INF/swagger/views/swagger-ui
        mapping: /swagger-ui/**

Similar 401 errors:

DEBUG i.m.h.s.netty.RoutingInBoundHandler - Request GET /api/content/swagger-ui
DEBUG i.m.s.t.reader.HttpHeaderTokenReader - Looking for bearer token in Authorization header
DEBUG i.m.s.t.reader.DefaultTokenResolver - Request GET, /api/content/swagger-ui, no token found.
DEBUG i.m.security.rules.IpPatternsRule - One or more of the IP patterns matched the host address [0:0:0:0:0:0:0:1]. Continuing request processing.
DEBUG i.m.s.rules.AbstractSecurityRule - The given roles [[isAnonymous()]] matched one or more of the required roles [[isAnonymous()]]. Allowing the request
DEBUG i.m.security.filters.SecurityFilter - Authorized request GET /api/content/swagger-ui. The rule provider io.micronaut.security.rules.ConfigurationInterceptUrlMapRule authorized the request.
DEBUG i.m.h.s.netty.RoutingInBoundHandler - Request GET /api/content/res/swagger-ui-bundle.js
DEBUG i.m.s.t.reader.HttpHeaderTokenReader - Looking for bearer token in Authorization header
DEBUG i.m.s.t.reader.DefaultTokenResolver - Request GET, /api/content/res/swagger-ui-bundle.js, no token found.
DEBUG i.m.security.rules.IpPatternsRule - One or more of the IP patterns matched the host address [0:0:0:0:0:0:0:1]. Continuing request processing.
DEBUG i.m.s.rules.InterceptUrlMapRule - No url map pattern exact match found for path [/api/content/res/swagger-ui-bundle.js] and method [GET]. Searching in patterns with no defined method.
DEBUG i.m.s.rules.InterceptUrlMapRule - No url map pattern match found for path [/api/content/res/swagger-ui-bundle.js]. Returning unknown.
DEBUG i.m.security.filters.SecurityFilter - Authorized request GET /api/content/res/swagger-ui-bundle.js. No rule provider authorized or rejected the request.
DEBUG i.m.h.s.netty.RoutingInBoundHandler - Response 401 - GET /api/content/res/swagger-ui-bundle.js
DEBUG i.m.h.s.netty.RoutingInBoundHandler - Request GET /api/content/res/swagger-ui-standalone-preset.js
DEBUG i.m.h.s.netty.RoutingInBoundHandler - Request GET /api/content/res/swagger-ui.css
DEBUG i.m.s.t.reader.HttpHeaderTokenReader - Looking for bearer token in Authorization header
DEBUG i.m.s.t.reader.DefaultTokenResolver - Request GET, /api/content/res/swagger-ui-standalone-preset.js, no token found.
DEBUG i.m.security.rules.IpPatternsRule - One or more of the IP patterns matched the host address [0:0:0:0:0:0:0:1]. Continuing request processing.
DEBUG i.m.s.rules.InterceptUrlMapRule - No url map pattern exact match found for path [/api/content/res/swagger-ui-standalone-preset.js] and method [GET]. Searching in patterns with no defined method.
DEBUG i.m.s.rules.InterceptUrlMapRule - No url map pattern match found for path [/api/content/res/swagger-ui-standalone-preset.js]. Returning unknown.
DEBUG i.m.s.t.reader.HttpHeaderTokenReader - Looking for bearer token in Authorization header
DEBUG i.m.security.filters.SecurityFilter - Authorized request GET /api/content/res/swagger-ui-standalone-preset.js. No rule provider authorized or rejected the request.
DEBUG i.m.s.t.reader.DefaultTokenResolver - Request GET, /api/content/res/swagger-ui.css, no token found.
DEBUG i.m.security.rules.IpPatternsRule - One or more of the IP patterns matched the host address [0:0:0:0:0:0:0:1]. Continuing request processing.
DEBUG i.m.s.rules.InterceptUrlMapRule - No url map pattern exact match found for path [/api/content/res/swagger-ui.css] and method [GET]. Searching in patterns with no defined method.
DEBUG i.m.s.rules.InterceptUrlMapRule - No url map pattern match found for path [/api/content/res/swagger-ui.css]. Returning unknown.
DEBUG i.m.security.filters.SecurityFilter - Authorized request GET /api/content/res/swagger-ui.css. No rule provider authorized or rejected the request.
DEBUG i.m.h.s.netty.RoutingInBoundHandler - Response 401 - GET /api/content/res/swagger-ui-standalone-preset.js
DEBUG i.m.h.s.netty.RoutingInBoundHandler - Response 401 - GET /api/content/res/swagger-ui.css
DEBUG i.m.h.s.netty.RoutingInBoundHandler - Request GET /api/content/res/favicon-32x32.png
DEBUG i.m.s.t.reader.HttpHeaderTokenReader - Looking for bearer token in Authorization header
DEBUG i.m.s.t.reader.DefaultTokenResolver - Request GET, /api/content/res/favicon-32x32.png, no token found.
DEBUG i.m.security.rules.IpPatternsRule - One or more of the IP patterns matched the host address [0:0:0:0:0:0:0:1]. Continuing request processing.
DEBUG i.m.s.rules.InterceptUrlMapRule - No url map pattern exact match found for path [/api/content/res/favicon-32x32.png] and method [GET]. Searching in patterns with no defined method.
DEBUG i.m.s.rules.InterceptUrlMapRule - No url map pattern match found for path [/api/content/res/favicon-32x32.png]. Returning unknown.
DEBUG i.m.security.filters.SecurityFilter - Authorized request GET /api/content/res/favicon-32x32.png. No rule provider authorized or rejected the request.
DEBUG i.m.h.s.netty.RoutingInBoundHandler - Response 401 - GET /api/content/res/favicon-32x32.png
DEBUG i.m.h.s.netty.RoutingInBoundHandler - Request GET /api/content/res/favicon-16x16.png
DEBUG i.m.s.t.reader.HttpHeaderTokenReader - Looking for bearer token in Authorization header
DEBUG i.m.s.t.reader.DefaultTokenResolver - Request GET, /api/content/res/favicon-16x16.png, no token found.
DEBUG i.m.security.rules.IpPatternsRule - One or more of the IP patterns matched the host address [0:0:0:0:0:0:0:1]. Continuing request processing.
DEBUG i.m.s.rules.InterceptUrlMapRule - No url map pattern exact match found for path [/api/content/res/favicon-16x16.png] and method [GET]. Searching in patterns with no defined method.
DEBUG i.m.s.rules.InterceptUrlMapRule - No url map pattern match found for path [/api/content/res/favicon-16x16.png]. Returning unknown.
DEBUG i.m.security.filters.SecurityFilter - Authorized request GET /api/content/res/favicon-16x16.png. No rule provider authorized or rejected the request.
DEBUG i.m.h.s.netty.RoutingInBoundHandler - Response 401 - GET /api/content/res/favicon-16x16.png

bmunyan avatar Oct 07 '22 19:10 bmunyan

@bmunyan ok. I'll check it tomorrow

altro3 avatar Oct 07 '22 19:10 altro3

@bmunyan Well, I added the micronaut.server.context-path property to the resource paths. In addition, I added the ability to customize the context path to the resources. By default, it is set to /res. The final config should look like this:

micronaut:
  server:
    context-path: /api/content
  openapi:
    views:
      spec: 'swagger-ui.enabled=true'
  router:
    static-resources:
      swagger:
        paths: classpath:META-INF/swagger
        mapping: /swagger/**
      swagger-ui:
        paths: classpath:META-INF/swagger/views/swagger-ui
        mapping: /swagger-ui/**
      swagger-ui-res:
        paths: classpath:META-INF/swagger/views/swagger-ui/res
        mapping: /res/**

And in my sample project I see this: изображение

altro3 avatar Oct 08 '22 13:10 altro3

I pulled your branch, built it, added it as a dependency in my project and, with a little fenagling of the micronaut.security.intercept-url-map, the above configuration worked for me.

micronaut:
  application:
    name: content-api
  server:
    context-path: /api/content
  router:
    static-resources:
      swagger:
        paths: classpath:META-INF/swagger
        mapping: /swagger/**
      swagger-ui:
        paths: classpath:META-INF/swagger/views/swagger-ui
        mapping: /swagger-ui/**
      swagger-ui-res:
        paths: classpath:META-INF/swagger/views/swagger-ui/res
        mapping: /res/**
  security:
    intercept-url-map:
      - pattern: ${micronaut.server.context-path}/swagger-ui/**
        httpMethod: GET
        access:
          - isAnonymous()
      - pattern: ${micronaut.server.context-path}/res/**
        httpMethod: GET
        access:
          - isAnonymous()
      - pattern: ${micronaut.server.context-path}/swagger/**
        httpMethod: GET
        access:
          - isAnonymous()

Is there generally a "ballpark" timeframe for when your PR will be merged and released with a new Micronaut version? Thank you for all the help!

bmunyan avatar Oct 08 '22 19:10 bmunyan

@bmunyan I am glad to help. As for the timing, I don't know, because I'm not in the micronaut team :-). As they decide to create a new release - so be it :-)

altro3 avatar Oct 08 '22 21:10 altro3

@altro3 I am fighting a similar problem. I upgraded to the latest version of micronaut and my swagger-ui endpoint stopped working. Looking at the UI it appears that the javascript/css/image files being incorrectly referenced at {url}/res/....

It appears that there is a mismatch between the rendering of the index.html template and the location of the resource files that index.html uses. index.html wants them located at {url}/res/... but the template engine is placing them at META-INF/swagger/views/swagger-ui/res/....

Based on the conversation above you seem to be recommending that @bmunyan get around this problem by mapping the static content at META-INF/swagger/views/swagger-ui/res to resolve at /res and then also add a security rules for /res. If you notice @bmunyan already had to add a static-content rule to allow access to swagger-ui/index.html and given that rule the javascript files are already addressable via the url swagger-ui/res/swagger-ui-bundle.js. Given this fact why complicate setup by requiring a 3rd static-content rule and a 3rd security rule? Why not simply fix index.html to access the js/css/image files where they are already located and where rules have been made to allow access?

kevind-wgu avatar Oct 24 '22 21:10 kevind-wgu

To press the point further, looking at OpenApiViewConfig.render():165 you set the swaggerUiDir to be swagger-ui. Then on line 170 you call the copyResources method with the swagger-ui as the parent directory. Then in SwaggerUIConfig.DEFAULT_SWAGGER_JS_PATH you explicitly configure the location of those resource files inside of index.html to be res/ instead of swagger-ui/res. IMO they should be configured to use the same location rather than two different locations. (Thus preventing confusion and unneeded configuration)

kevind-wgu avatar Oct 24 '22 21:10 kevind-wgu

One other thought. If SwaggerUIConfig.DEFAULT_SWAGGER_JS_PAT was changed from OpenApiViewConfig.RESOURCE_DIR + "/" to be "./" + OpenApiViewConfig.RESOURCE_DIR + "/" it would make those url's relative and the problems I described above go away.

kevind-wgu avatar Oct 24 '22 21:10 kevind-wgu