如何在 Spring Boot Rest 应用程序中使用 Swagger ui 配置 oAuth2 和密码流
我有 spring boot rest api (resources),它使用另一个 spring boot 授权服务器,我已将 Swagger 配置添加到资源应用程序中,以便为 rest API 获得一个很好且快速的文档/测试平台.我的 Swagger 配置如下所示:
@Configuration@EnableSwagger2公共类 SwaggerConfig {@自动连线私有类型解析器类型解析器;@Value("${app.client.id}")私有字符串客户端ID;@Value("${app.client.secret}")私有字符串clientSecret;@Value("${info.build.name}")私有字符串 infoBuildName;公共静态最终字符串 securitySchemaOAuth2 = "oauth2";public static final String authorizationScopeGlobal = "global";public static final String authorizationScopeGlobalDesc = "accessEverything";@豆角,扁豆公共案卷 api() {列出<响应消息>list = new java.util.ArrayList();list.add(新的 ResponseMessageBuilder().code(500).message("500 条消息").responseModel(new ModelRef("JSONResult«string»")).建造());list.add(新的 ResponseMessageBuilder().code(401).message("未经授权").responseModel(new ModelRef("JSONResult«string»")).建造());返回新案卷(DocumentationType.SWAGGER_2).选择().apis(RequestHandlerSelectors.any()).paths(PathSelectors.any()).建造().securitySchemes(Collections.singletonList(securitySchema())).securityContexts(Collections.singletonList(securityContext())).pathMapping("/").directModelSubstitute(LocalDate.class,String.class).genericModelSubstitutes(ResponseEntity.class).alternateTypeRules(newRule(typeResolver.resolve(DeferredResult.class,typeResolver.resolve(ResponseEntity.class, WildcardType.class)),typeResolver.resolve(WildcardType.class))).useDefaultResponseMessages(false).apiInfo(apiInfo()).globalResponseMessage(RequestMethod.GET,list).globalResponseMessage(RequestMethod.POST,list);}私人 OAuth 安全架构() {列出<授权范围>授权范围列表 = newArrayList();authorizationScopeList.add(new AuthorizationScope("global", "access all"));列出<GrantType>grantTypes = newArrayList();final TokenRequestEndpoint tokenRequestEndpoint = new TokenRequestEndpoint("http://server:port/oauth/token", clientId, clientSecret);final TokenEndpoint tokenEndpoint = new TokenEndpoint("http://server:port/oauth/token", "access_token");AuthorizationCodeGrant authorizationCodeGrant = new AuthorizationCodeGrant(tokenRequestEndpoint, tokenEndpoint);grantTypes.add(authorizationCodeGrant);OAuth oAuth = new OAuth("oauth", authorizationScopeList, grantTypes);返回 oAuth;}私人 SecurityContext securityContext() {返回 SecurityContext.builder().securityReferences(defaultAuth()).forPaths(PathSelectors.ant("/api/**")).build();}私有列表<安全参考>默认身份验证(){最终 AuthorizationScope 授权范围 =新的 AuthorizationScope(authorizationScopeGlobal, authorizationScopeGlobalDesc);最终 AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];授权范围[0] = 授权范围;返回集合.singletonList(new SecurityReference(securitySchemaOAuth2, authorizationScopes));}私人 APIInfo apiInfo() {返回新的 ApiInfoBuilder().title(我的休息 API").description("这里的描述...").termsOfServiceUrl("https://www.example.com/").contact(新联系人(XXXX XXXX",http://www.example.com"、xxxx@example.com")).license("这里有许可证").licenseUrl("https://www.example.com").version("1.0.0").建造();}}
我从授权服务器获取访问令牌的方式是使用 http POST 到此链接,并在 clientid/clientpass 的标头中使用基本授权:
http://server:port/oauth/token?grant_type=password&username=<username>&password=<password>
响应类似于:
<代码>{access_token":e3b98877-f225-45e2-add4-3c53eeb6e7a8","token_type": "承载者","refresh_token": "58f34753-7695-4a71-c08a-d40241ec3dfb",expires_in":4499,范围":读取信任写入"}
在 Swagger UI 中,我可以看到一个授权按钮,该按钮会打开一个对话框以发出授权请求,但它不起作用并将我引导至如下链接,
http://server:port/oauth/token?response_type=code&redirect_uri=http%3A%2F%2Fserver%3A8080%2Fwebjars%2Fspringfox-swagger-ui%2Fo2c.html&realm=undefined&client_id=undefined&scope=global%2CvendorExtensions&state=oauth
我在这里缺少什么?
解决方案历时 8 个月,终于在 Swagger UI 中支持密码流,这是适合我的最终代码和设置:
1) Swagger 配置:
包com.example.api;导入 org.springframework.beans.factory.annotation.Value;导入 org.springframework.context.annotation.Bean;导入 org.springframework.context.annotation.Configuration;导入 org.springframework.web.bind.annotation.RequestMethod;导入 springfox.documentation.schema.ModelRef;导入 springfox.documentation.service.ApiInfo;导入 springfox.documentation.service.AuthorizationScope;导入 springfox.documentation.service.Contact;导入 springfox.documentation.service.GrantType;导入 springfox.documentation.service.OAuth;导入 springfox.documentation.service.ResourceOwnerPasswordCredentialsGrant;导入 springfox.documentation.service.ResponseMessage;导入 springfox.documentation.service.SecurityReference;导入 springfox.documentation.builders.ApiInfoBuilder;导入 springfox.documentation.builders.PathSelectors;导入 springfox.documentation.builders.RequestHandlerSelectors;导入 springfox.documentation.builders.ResponseMessageBuilder;导入 springfox.documentation.spi.DocumentationType;导入 springfox.documentation.spi.service.contexts.SecurityContext;导入 springfox.documentation.spring.web.plugins.Docket;导入 springfox.documentation.swagger.web.ApiKeyVehicle;导入 springfox.documentation.swagger.web.SecurityConfiguration;导入 springfox.documentation.swagger2.annotations.EnableSwagger2;导入 java.util.Collections;导入 java.util.List;导入静态 com.google.common.collect.Lists.*;@配置@EnableSwagger2公共类 SwaggerConfig {@Value("${app.client.id}")私有字符串客户端ID;@Value("${app.client.secret}")私有字符串clientSecret;@Value("${info.build.name}")私有字符串 infoBuildName;@Value("${host.full.dns.auth.link}")私有字符串 authLink;@豆角,扁豆公共案卷 api() {列出<响应消息>list = new java.util.ArrayList<>();list.add(new ResponseMessageBuilder().code(500).message("500 消息").responseModel(new ModelRef("Result")).build());list.add(new ResponseMessageBuilder().code(401).message("未授权").responseModel(new ModelRef("Result")).build());list.add(new ResponseMessageBuilder().code(406).message("不可接受").responseModel(new ModelRef("Result")).build());return new Docket(DocumentationType.SWAGGER_2).select().apis(RequestHandlerSelectors.any()).paths(PathSelectors.any()).build().securitySchemes(Collections.singletonList(securitySchema())).securityContexts(Collections.singletonList(securityContext())).pathMapping("/").useDefaultResponseMessages(false).apiInfo(apiInfo()).globalResponseMessage(RequestMethod.GET, list).globalResponseMessage(RequestMethod.POST, list);}私人 OAuth 安全架构() {列出<授权范围>授权范围列表 = newArrayList();authorizationScopeList.add(new AuthorizationScope("read", "read all"));authorizationScopeList.add(new AuthorizationScope("trust", "trust all"));authorizationScopeList.add(new AuthorizationScope("write", "access all"));列出<GrantType>grantTypes = newArrayList();GrantType creGrant = new ResourceOwnerPasswordCredentialsGrant(authLink+"/oauth/token");grantTypes.add(creGrant);return new OAuth("oauth2schema", authorizationScopeList, grantTypes);}私人 SecurityContext securityContext() {return SecurityContext.builder().securityReferences(defaultAuth()).forPaths(PathSelectors.ant("/user/**")).建造();}私有列表<安全参考>默认身份验证(){最终 AuthorizationScope[] authorizationScopes = new AuthorizationScope[3];authorizationScopes[0] = new AuthorizationScope("read", "read all");authorizationScopes[1] = new AuthorizationScope("trust", "trust all");authorizationScopes[2] = new AuthorizationScope("write", "write all");return Collections.singletonList(new SecurityReference("oauth2schema", authorizationScopes));}@豆角,扁豆公共安全配置安全信息(){return new SecurityConfiguration(clientId, clientSecret, "", "", "", ApiKeyVehicle.HEADER, "", "");}私人 APIInfo apiInfo() {return new ApiInfoBuilder().title("我的 API 标题").description("").termsOfServiceUrl("https://www.example.com/api").contact(new Contact("Hasson", "http://www.example.com", "hasson@example.com")).license("开源").licenseUrl("https://www.example.com").version("1.0.0").build();}}
2) 在 POM 中使用此 Swagger UI 版本 2.7.0:
<依赖><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><版本>2.7.0</版本></依赖><依赖性><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><版本>2.7.0</版本></依赖><依赖性><groupId>io.springfox</groupId><artifactId>springfox-bean-validators</artifactId><版本>2.7.0</版本></依赖>
3) 在application.properties中添加如下属性:
host.full.dns.auth.link=http://oauthserver.example.com:8081app.client.id=测试客户端app.client.secret=clientSecretauth.server.schem=http
4) 在授权服务器中添加一个 CORS 过滤器:
包com.example.api.oauth2.oauth2server;导入 org.slf4j.Logger;导入 org.slf4j.LoggerFactory;导入 org.springframework.stereotype.Component;导入 javax.servlet.Filter;导入 javax.servlet.FilterChain;导入 javax.servlet.FilterConfig;导入 javax.servlet.ServletException;导入 javax.servlet.ServletRequest;导入 javax.servlet.ServletResponse;导入 javax.servlet.http.HttpServletResponse;导入 java.io.IOException;/*** 允许跨域使用本地文件中的 swagger-ui 测试 swagger 文档* 系统*/@零件公共类 CrossOriginFilter 实现过滤器 {private static final Logger log = LoggerFactory.getLogger(CrossOriginFilter.class);@覆盖public void init(FilterConfig filterConfig) 抛出 ServletException {//由 web 容器调用以向过滤器指示它正在被//投入使用.//我们不想在这里做任何事情.}@覆盖public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)抛出 IOException,ServletException {log.info("应用 CORS 过滤器");HttpServletResponse 响应 = (HttpServletResponse) 响应;response.setHeader("Access-Control-Allow-Origin", "*");response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");response.setHeader("Access-Control-Max-Age", "0");chain.doFilter(req, resp);}@覆盖公共无效销毁(){//由 web 容器调用以向过滤器指示它正在被//停止服务.//我们不想在这里做任何事情.}}
如果您使用这些设置运行,您将在链接中获得授权按钮
然后,当您单击授权按钮时,您将看到以下对话框,添加您的用户名/密码和客户端 ID 和客户端密码的数据,类型必须是请求正文,我不知道为什么但是这个对我有用,尽管我认为它应该是基本身份验证,因为这是发送客户端密码的方式,无论如何这就是 Swagger-ui 与密码流一起工作的方式,并且您的所有 API 端点都可以再次工作.快乐大摇大摆!!!:)
I have spring boot rest api (resources) which uses another spring boot authorisation server, I have added Swagger config to the resource application to get a nice and quick documentation/test platform for the rest API. my Swagger config looks like this:
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Autowired
private TypeResolver typeResolver;
@Value("${app.client.id}")
private String clientId;
@Value("${app.client.secret}")
private String clientSecret;
@Value("${info.build.name}")
private String infoBuildName;
public static final String securitySchemaOAuth2 = "oauth2";
public static final String authorizationScopeGlobal = "global";
public static final String authorizationScopeGlobalDesc = "accessEverything";
@Bean
public Docket api() {
List<ResponseMessage> list = new java.util.ArrayList<ResponseMessage>();
list.add(new ResponseMessageBuilder()
.code(500)
.message("500 message")
.responseModel(new ModelRef("JSONResult«string»"))
.build());
list.add(new ResponseMessageBuilder()
.code(401)
.message("Unauthorized")
.responseModel(new ModelRef("JSONResult«string»"))
.build());
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
.securitySchemes(Collections.singletonList(securitySchema()))
.securityContexts(Collections.singletonList(securityContext()))
.pathMapping("/")
.directModelSubstitute(LocalDate.class,String.class)
.genericModelSubstitutes(ResponseEntity.class)
.alternateTypeRules(
newRule(typeResolver.resolve(DeferredResult.class,
typeResolver.resolve(ResponseEntity.class, WildcardType.class)),
typeResolver.resolve(WildcardType.class)))
.useDefaultResponseMessages(false)
.apiInfo(apiInfo())
.globalResponseMessage(RequestMethod.GET,list)
.globalResponseMessage(RequestMethod.POST,list);
}
private OAuth securitySchema() {
List<AuthorizationScope> authorizationScopeList = newArrayList();
authorizationScopeList.add(new AuthorizationScope("global", "access all"));
List<GrantType> grantTypes = newArrayList();
final TokenRequestEndpoint tokenRequestEndpoint = new TokenRequestEndpoint("http://server:port/oauth/token", clientId, clientSecret);
final TokenEndpoint tokenEndpoint = new TokenEndpoint("http://server:port/oauth/token", "access_token");
AuthorizationCodeGrant authorizationCodeGrant = new AuthorizationCodeGrant(tokenRequestEndpoint, tokenEndpoint);
grantTypes.add(authorizationCodeGrant);
OAuth oAuth = new OAuth("oauth", authorizationScopeList, grantTypes);
return oAuth;
}
private SecurityContext securityContext() {
return SecurityContext.builder().securityReferences(defaultAuth())
.forPaths(PathSelectors.ant("/api/**")).build();
}
private List<SecurityReference> defaultAuth() {
final AuthorizationScope authorizationScope =
new AuthorizationScope(authorizationScopeGlobal, authorizationScopeGlobalDesc);
final AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
return Collections
.singletonList(new SecurityReference(securitySchemaOAuth2, authorizationScopes));
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("My rest API")
.description(" description here … ")
.termsOfServiceUrl("https://www.example.com/")
.contact(new Contact("XXXX XXXX",
"http://www.example.com", "xxxx@example.com"))
.license("license here")
.licenseUrl("https://www.example.com")
.version("1.0.0")
.build();
}
}
The way I get the access token from the Authorisation server is by using http POST to this link with basic authorisation in the header for clientid/clientpass:
http://server:port/oauth/token?grant_type=password&username=<username>&password=<password>
the response is something like:
{
"access_token": "e3b98877-f225-45e2-add4-3c53eeb6e7a8",
"token_type": "bearer",
"refresh_token": "58f34753-7695-4a71-c08a-d40241ec3dfb",
"expires_in": 4499,
"scope": "read trust write"
}
in Swagger UI I can see an Authorisation button, which opens a dialog to make the authorisation request, but it is not working and directing me to a link as following,
http://server:port/oauth/token?response_type=code&redirect_uri=http%3A%2F%2Fserver%3A8080%2Fwebjars%2Fspringfox-swagger-ui%2Fo2c.html&realm=undefined&client_id=undefined&scope=global%2CvendorExtensions&state=oauth
what I am missing here?
解决方案After 8 months, finally the password flow is supported in Swagger UI, here is the final code and settings which works for me:
1) Swagger Config:
package com.example.api;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RequestMethod;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.Contact;
import springfox.documentation.service.GrantType;
import springfox.documentation.service.OAuth;
import springfox.documentation.service.ResourceOwnerPasswordCredentialsGrant;
import springfox.documentation.service.ResponseMessage;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.builders.ResponseMessageBuilder;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger.web.ApiKeyVehicle;
import springfox.documentation.swagger.web.SecurityConfiguration;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.Collections;
import java.util.List;
import static com.google.common.collect.Lists.*;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Value("${app.client.id}")
private String clientId;
@Value("${app.client.secret}")
private String clientSecret;
@Value("${info.build.name}")
private String infoBuildName;
@Value("${host.full.dns.auth.link}")
private String authLink;
@Bean
public Docket api() {
List<ResponseMessage> list = new java.util.ArrayList<>();
list.add(new ResponseMessageBuilder().code(500).message("500 message")
.responseModel(new ModelRef("Result")).build());
list.add(new ResponseMessageBuilder().code(401).message("Unauthorized")
.responseModel(new ModelRef("Result")).build());
list.add(new ResponseMessageBuilder().code(406).message("Not Acceptable")
.responseModel(new ModelRef("Result")).build());
return new Docket(DocumentationType.SWAGGER_2).select().apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any()).build().securitySchemes(Collections.singletonList(securitySchema()))
.securityContexts(Collections.singletonList(securityContext())).pathMapping("/")
.useDefaultResponseMessages(false).apiInfo(apiInfo()).globalResponseMessage(RequestMethod.GET, list)
.globalResponseMessage(RequestMethod.POST, list);
}
private OAuth securitySchema() {
List<AuthorizationScope> authorizationScopeList = newArrayList();
authorizationScopeList.add(new AuthorizationScope("read", "read all"));
authorizationScopeList.add(new AuthorizationScope("trust", "trust all"));
authorizationScopeList.add(new AuthorizationScope("write", "access all"));
List<GrantType> grantTypes = newArrayList();
GrantType creGrant = new ResourceOwnerPasswordCredentialsGrant(authLink+"/oauth/token");
grantTypes.add(creGrant);
return new OAuth("oauth2schema", authorizationScopeList, grantTypes);
}
private SecurityContext securityContext() {
return SecurityContext.builder().securityReferences(defaultAuth()).forPaths(PathSelectors.ant("/user/**"))
.build();
}
private List<SecurityReference> defaultAuth() {
final AuthorizationScope[] authorizationScopes = new AuthorizationScope[3];
authorizationScopes[0] = new AuthorizationScope("read", "read all");
authorizationScopes[1] = new AuthorizationScope("trust", "trust all");
authorizationScopes[2] = new AuthorizationScope("write", "write all");
return Collections.singletonList(new SecurityReference("oauth2schema", authorizationScopes));
}
@Bean
public SecurityConfiguration securityInfo() {
return new SecurityConfiguration(clientId, clientSecret, "", "", "", ApiKeyVehicle.HEADER, "", " ");
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder().title("My API title").description("")
.termsOfServiceUrl("https://www.example.com/api")
.contact(new Contact("Hasson", "http://www.example.com", "hasson@example.com"))
.license("Open Source").licenseUrl("https://www.example.com").version("1.0.0").build();
}
}
2) in POM use this Swagger UI version 2.7.0:
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-bean-validators</artifactId>
<version>2.7.0</version>
</dependency>
3) in the application.properties add the following properties:
host.full.dns.auth.link=http://oauthserver.example.com:8081
app.client.id=test-client
app.client.secret=clientSecret
auth.server.schem=http
4) in the Authorisation server add a CORS filter:
package com.example.api.oauth2.oauth2server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Allows cross origin for testing swagger docs using swagger-ui from local file
* system
*/
@Component
public class CrossOriginFilter implements Filter {
private static final Logger log = LoggerFactory.getLogger(CrossOriginFilter.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// Called by the web container to indicate to a filter that it is being
// placed into service.
// We do not want to do anything here.
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
log.info("Applying CORS filter");
HttpServletResponse response = (HttpServletResponse) resp;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "0");
chain.doFilter(req, resp);
}
@Override
public void destroy() {
// Called by the web container to indicate to a filter that it is being
// taken out of service.
// We do not want to do anything here.
}
}
If you run with these settings you will get the authorize button in the link http://apiServer.example.com:8080/swagger-ui.html#/ (if you run on 8080) as follows:
Then when you click on the authorize button you will get the following dialogue, add the data for your username/password and the client id and the client secret, the type has to be request body, I am not sure why but this is what works with me, although I thought it should be basic auth as this is how the client secret is sent, anyway this is how Swagger-ui works with password flow and all your API endpoints are working again. Happy swaggering!!! :)
相关文章