How to configure oAuth2 with password flow with Swagger ui in spring boot rest application(如何在 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!!! :)
这篇关于如何在 Spring Boot Rest 应用程序中使用 Swagger ui 配置 oAuth2 和密码流的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!