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

Spring Boot Swagger 2 UI Oauth2 configuration #767

Closed
haidelber opened this issue May 31, 2015 · 20 comments
Closed

Spring Boot Swagger 2 UI Oauth2 configuration #767

haidelber opened this issue May 31, 2015 · 20 comments
Labels
Milestone

Comments

@haidelber
Copy link

I have some troubles getting Swagger UI running for my OAuth2 secured API with the current 2.0.0 release.

I guess I'm missing something..

I used following Configuration

    @Bean
    public Docket apiDocumentation() {
        return new Docket(DocumentationType.SWAGGER_2).groupName("api").apiInfo(apiInfo())
                .select().paths(internalPaths()).build()
                .securitySchemes(newArrayList(securitySchema()))
                .securityContexts(newArrayList(securityContext()));
    }

    public static final String securitySchemaOAuth2 = "oauth2schema";
    public static final String authorizationScopeGlobal = "global";
    public static final String authorizationScopeGlobalDesc ="accessEverything";

    private OAuth securitySchema() {
        AuthorizationScope authorizationScope = new AuthorizationScope(authorizationScopeGlobal, authorizationScopeGlobal);
        LoginEndpoint loginEndpoint = new LoginEndpoint("http://localhost:9999/sso/login");
        GrantType grantType = new ImplicitGrant(loginEndpoint, "access_token");
        return new OAuth(securitySchemaOAuth2, newArrayList(authorizationScope), newArrayList(grantType));
    }

    private SecurityContext securityContext() {
        return SecurityContext.builder()
                .securityReferences(defaultAuth())
                .forPaths(internalPaths())
                .build();
    }

    private List<SecurityReference> defaultAuth() {
        AuthorizationScope authorizationScope
                = new AuthorizationScope(authorizationScopeGlobal, authorizationScopeGlobalDesc);
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        return newArrayList(
                new SecurityReference(securitySchemaOAuth2, authorizationScopes));
    }

My API methods are annotated like that

@RequestMapping(value = "/languageFile", method = RequestMethod.GET)
    @ApiOperation(value = "Fetches the language file of a given or the default locale",
                  authorizations = {@Authorization(value = SwaggerConfig.securitySchemaOAuth2, type = "oauth2", scopes =
                          {@AuthorizationScope( scope = SwaggerConfig.authorizationScopeGlobal, description = SwaggerConfig.authorizationScopeGlobalDesc)})})
    public I18NGetLanguageFileResponse getLanguageFile(@RequestParam(defaultValue = TechnicalConfig.defaultLocaleString, value = "locale", required = false) String localeString) {

The Result

The problem now is that the authentication button appears in Swagger UI but isn't invokable. There isn't even any js attachted to this button.

{
    "swagger": "2.0",
    "info": {

    },
    "host": "localhost:9000",
    "basePath": "/rest",
    "tags": [
        {
            "name": "i18n-controller"
        }
            ],
    "paths": {
        "/i18n/languageFile": {
            "get": {
                "tags": [
                    "i18n-controller"
                ],
                "summary": "Fetches the language file of a given or the default locale",
                "description": "getLanguageFile",
                "operationId": "getLanguageFileUsingGET",
                "consumes": [
                    "application/json"
                ],
                "produces": [
                    "*/*"
                ],
                "parameters": [
                    {
                        "name": "locale",
                        "in": "query",
                        "description": "localeString",
                        "required": false,
                        "type": "string"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/I18NGetLanguageFileResponse"
                        }
                    },
                    "401": {
                        "description": "Unauthorized"
                    },
                    "403": {
                        "description": "Forbidden"
                    },
                    "404": {
                        "description": "Not Found"
                    }
                },
                "security": [
                    {
                        "oauth2schema": [
                            "global"
                        ]
                    }
                ]
            }
        }
    },
    "securityDefinitions": {
        "oauth2schema": {
            "type": "oauth2",
            "authorizationUrl": "http://localhost:9999/sso/login",
            "flow": "implicit",
            "scopes": {
                "global": "global"
            }
        }
    },
    "definitions": {

    }
}

image
image

@dilipkrish
Copy link
Member

Have you tried pasting the swagger json into http://editor.swagger.io? It may be a bug with the swagger ui.

Also, when you have described the security context, there is no need for the authorization annotation. SecurityContext is a way to describe the security requirements for Apis that match a particular criteria. In your case it's Apis that match the criteria of internalApis.

@haidelber
Copy link
Author

thanks for the fast reply..
on http://editor.swagger.io/ it works somehow.. at least the request displayed seems correct...

GET http://localhost:9000/rest/i18n/languageFile?locale=en HTTP/1.1
Accept: */*
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8,fa;q=0.6,sv;q=0.4
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0MzMxMjk2NTcsInVzZXJfbmFtZSI6InRlc3RhZG1pbkBleGFtcGxlLmNvbSIsImF1dGhvcml0aWVzIjpbIlN1cGVyQWRtaW4iXSwianRpIjoiNGE0M2JmNGUtZjJmNC00ZjA2LThkZDktYzY4ZWY5NTYwMzk3IiwiY2xpZW50X2lkIjoiYWNtZSIsInNjb3BlIjpbIm9wZW5pZCJdfQ.PhM_Qz1yZxJZBAMpLAF2vG1yomJGyKO_-ury1aM-HH-mHuPB8KjxK1l6INOUSSicSNw-k7I-SOwQrBYnvPjHYbmxsu42-Zgg56FHaA5Wbt8QaNStg8JC4t8yd94jqRa0id7dG7AkM2GgFGU0Kgtb19r62XTQUmRt8aui0W7XIGV4apX4cR-qzkuO7h9iTt7iDBFyBM8n2LtL8LU7AeyCaoHVzH7lAMNj8hRQG1FV5vzU-TyCbqWQcF3T4zc7o_I-HfFCHeml47KrZImwCLpxPVWeY4FkwvaxGTTp-hskQtjfwJoVOP_m7a_WmQqxEvu_yiM6GRDTnDWOCzJtkfkg3A
Cache-Control: no-cache
Connection: keep-alive
Host: localhost
Origin: http://editor.swagger.io
Referer: http://editor.swagger.io/
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.81 Safari/537.36

But when I look into Fiddler only following single request is submitted

OPTIONS http://localhost:9000/rest/i18n/languageFile?locale=en HTTP/1.1
Host: localhost:9000
Connection: keep-alive
Access-Control-Request-Method: GET
Origin: http://editor.swagger.io
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.81 Safari/537.36
Access-Control-Request-Headers: accept, accept-language, authorization
Accept: */*
DNT: 1
Referer: http://editor.swagger.io/
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en,en-US;q=0.8,de-AT;q=0.6,de;q=0.4

And this OPTIONS request obviously doesn't succeed

@dilipkrish
Copy link
Member

You might want to report this issue to the swagger-ui/swagger-editor projects. Its really not the scope of this library

@dilipkrish dilipkrish modified the milestone: 2.0.1 Jun 4, 2015
@haidelber
Copy link
Author

thanks for your help.. i finally found out that i had incorrect cors settings

@vivekpandian
Copy link

@haidelber How did you fix this issue. I am facing the same issue. " authentication button appears in Swagger UI but isn't invokable. There isn't even any js attachted to this button.". Please let me know what's was incorrect in your configuration

@dilipkrish
Copy link
Member

You probably need to add a CORS filter

@vivekpandian
Copy link

@dilipkrish. I applied the CORS filter, but it does't fix the issue.

@haidelber
Copy link
Author

@vivekpandian as @dilipkrish mentioned try it with the swagger editor if it's working there your overall settings are correct. But to be honest I didn't get it running with springfox-swagger-ui but my next approach is to configure swagger-ui myself from https://github.com/swagger-api/swagger-ui and write the necessary js to get it running. And take care of HTTP OPTIONS calls in the CORS filter. The browsers issues these preflight requests automatically before cross origin request (http://www.html5rocks.com/en/tutorials/cors/)

@sloppycoder
Copy link
Contributor

I have the very same issue, with springfox-swagger-ui 2.1.2. In order to make the button work, I had to open the swagger-ui.html in browser first, then enter the following snippet from swagger-oauth.js, after loading the swagger-ui.html,

  $('.api-ic').click(function(s) {
    if($(s.target).hasClass('ic-off'))
      handleLogin();
    else {
      handleLogout();
    }
    false;
  });

@miguelfgar
Copy link

Hi guys,

I experienced exactly this issue and I was able to fix the bug in my project using Springfox version 2.2.2 (currently the last published). I have only tried it out with the OAuth2 'implicit' flow which is the one I'm using but I guess it will work with the other flows also.

I would like to share what I found out with you.

The issue shows (and looks like) a JS error not allowing to turn on the OAuth2 switch. However, the problem is not the JS code. I found the bug in the Java classes that allows to write the proper SpringFox-Swagger-OAuth2 setup (you can't pass the 'clientSecret' using SecurityConfiguration class!).

The problem that we can see in the JS code is located in the following method (initOAuth) because there is not way no initialize the 'clientSecret' parameter from Java (is always 'null'). However, if the parameter has no value is treated like an error and the function returns straight away without excuting the code that initializes the OAuth swicht icon. Here is the code:

function initOAuth(opts) {
var o = (opts||{});
var errors = [];

appName = (o.appName||errors.push('missing appName'));
popupMask = (o.popupMask||$('#api-common-mask'));
popupDialog = (o.popupDialog||$('.api-popup-dialog'));
clientId = (o.clientId||errors.push('missing client id'));
clientSecret = (o.clientSecret||errors.push('missing client secret'));
realm = (o.realm||errors.push('missing realm'));
scopeSeparator = (o.scopeSeparator||' ');

if(errors.length > 0){
log('auth unable initialize oauth: ' + errors);
return;
}

$('pre code').each(function(i, e) {hljs.highlightBlock(e)});
$('.api-ic').unbind();
$('.api-ic').click(function(s) {
if($(s.target).hasClass('ic-off'))
handleLogin();
else {
handleLogout();
}
false;
});
}

As I said, nothing wrong with the JS code (apart from the fact that an addtional condition could be added so that if the grant type is "implicit" then the "clientSecret" parameter is no mandatory - as the OAuth2 spec says that the secret is not mandatory for this flow).
The fix comes in the Java code, in particular in the following class:

springfox.documentation.swagger.web.SecurityConfiguration from springfox-swagger-common library.

The problem is that this class that can be used to specify the configuration does not allows to set the 'clientSecret' attribute (at least in version 2.2.2).
Here it is the modified code where I have just added the "clientSecret" attribute and the proper accessors and a new parameter to the constructor:

/*
*

  • Copyright 2015 the original author or authors.
  • Licensed under the Apache License, Version 2.0 (the "License");
  • you may not use this file except in compliance with the License.
  • You may obtain a copy of the License at
  •     http://www.apache.org/licenses/LICENSE-2.0
    
  • Unless required by applicable law or agreed to in writing, software
  • distributed under the License is distributed on an "AS IS" BASIS,
  • WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  • See the License for the specific language governing permissions and
  • limitations under the License.

*/
package springfox.documentation.swagger.web;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;

@JsonInclude(value = JsonInclude.Include.NON_NULL)
public class SecurityConfiguration {
public static final SecurityConfiguration DEFAULT = new SecurityConfiguration();

private String clientId;
private String clientSecret;
private String realm;
private String appName;
private String apiKey;
private String scopeSeparator;

private SecurityConfiguration() {
this(null, null, null, null, null, ",");
}

public SecurityConfiguration(String clientId, String clientSecret, String realm, String appName, String apiKey,
String scopeSeparator) {
this.clientId = clientId;
this.clientSecret = clientSecret;
this.realm = realm;
this.appName = appName;
this.apiKey = apiKey;
this.scopeSeparator = scopeSeparator;
}

@JsonProperty("clientId")
public String getClientId() {
return clientId;
}

@JsonProperty("realm")
public String getRealm() {
return realm;
}

@JsonProperty("appName")
public String getAppName() {
return appName;
}

@JsonProperty("apiKey")
public String getApiKey() {
return apiKey;
}

@JsonProperty("clientSecret")
public String getClientSecret() {
return clientSecret;
}

@JsonProperty("apiKey")
public String scopeSeparator() {
return scopeSeparator;
}
}

Now that is possible to specify a "clientSecret" in the Java setup you can pass the client secret required according to your OAuth2 flow or you can pass an empty String for instance if your flow is 'implicit'.

From your Java setup using Spring Boot you could create the SecurityConfig bean like this:

@Bean
public SecurityConfiguration securityConfiguration(){
    SecurityConfiguration config = new SecurityConfiguration("yourclientid", "yourrealm", "yourappname", "yourapiKey", "");
    return config;
}

Where the last parameter "" would be the secret. In this case implicit flow so I just pass "".

With this changes it works with version 2.2.2 and implicit grant type.

Please let me know If I could help more with this.

Regards

@miguelfgar
Copy link

Hi, I just pull the last changes because I wanted to pull-request the change suggested in my previous comment and I see exactly this change was done after version 2.2.2 was released.
Summary: This issue should be fixed with the last changes in master.

@miguelfgar
Copy link

Hi guys,

Just to point out something I have seen in last version of class SecurityConfiguration.java in sprinfox-swagger-common project (in 'master' branch).

I guess the following code:
@JsonProperty("apiKey")
public String scopeSeparator() {
return scopeSeparator;
}

Should be (just the @JsonProperty annotation value was wrong):

@JsonProperty("scopeSeparator")
public String scopeSeparator() {
return scopeSeparator;
}

Regards!

mrestivill added a commit to mrestivill/springfox that referenced this issue Nov 9, 2015
mrestivill added a commit to mrestivill/springfox that referenced this issue Nov 16, 2015
dilipkrish added a commit that referenced this issue Nov 16, 2015
@abelyaevskiy
Copy link

@miguelfgar could you please describe what should I pass as realm, apiKey, appName?

@chrisihoby
Copy link

I have faced the same case, but i have no idea what should be pass as realm ,apiKey and appName

@dilipkrish dilipkrish modified the milestone: 2.3.0 Dec 5, 2015
@vijaychd
Copy link

i am getting same issue in 2.4.0. Authentication button not opening any popup. Please help

@dilipkrish
Copy link
Member

What issue are you getting @vijaychd ☝️ there are a variety of issues. Have you tried any of the suggestions above?

@vijaychd
Copy link

Oauth button doesn't work. i applied CORS filter and used latest release. Is there anything i should try?

@anshchauhan
Copy link

anshchauhan commented Jul 14, 2016

My Oauth button doesn't work either. No known solution as of now.

@tvajjala
Copy link

tvajjala commented Aug 1, 2016

facing same issue , did you get any solution yet

@dilipkrish
Copy link
Member

@trvajjala This issue has been fixed in 2.5.0, Please create a new issue if it is still a problem

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

10 participants