I'm using springdoc-openapi-ui 1.6.14
I have following class
#Configuration
public class GroupsConfig {
private final PropertyResolver propertyResolver;
public GroupsConfig(PropertyResolver propertyResolver) {
this.propertyResolver = propertyResolver;
}
#Bean
public GroupedOpenApi adminApi() {
return GroupedOpenApi.builder()
.group("admin")
.pathsToMatch("/admin/**")
.build();
}
#Bean
public GroupedOpenApi externalApi() {
return GroupedOpenApi.builder()
.group("external")
.pathsToMatch("/external/**")
.build();
}
#Bean
public GroupedOpenApi clientApi() {
return GroupedOpenApi.builder()
.group("client")
.pathsToMatch("/client/**")
.build();
}
#Bean
public GroupedOpenApi externalClientApi() {
return GroupedOpenApi.builder()
.group("extclient")
.pathsToMatch("/extclient/**")
.build();
}
#Bean
public OpenAPI apiInfo() {
String title = propertyResolver.getRequiredProperty(SwaggerPropertyKey.API_TITLE);
String description = propertyResolver.getRequiredProperty(SwaggerPropertyKey.API_DESCRIPTION);
String version = propertyResolver.getRequiredProperty(SwaggerPropertyKey.API_VERSION);
String contactName = propertyResolver.getRequiredProperty(SwaggerPropertyKey.API_CONTACT_NAME);
String contactUrl = propertyResolver.getRequiredProperty(SwaggerPropertyKey.API_CONTACT_URL);
String contactEmail = propertyResolver.getRequiredProperty(SwaggerPropertyKey.API_CONTACT_EMAIL);
String termsOfServiceUrl = propertyResolver.getRequiredProperty(SwaggerPropertyKey.API_TERMS_OF_SERVICE_URL);
String licence = propertyResolver.getRequiredProperty(SwaggerPropertyKey.API_LICENCE);
String licenceUrl = propertyResolver.getRequiredProperty(SwaggerPropertyKey.API_LICENCE_URL);
Contact contact = new Contact()
.name(contactName)
.url(contactUrl)
.email(contactEmail);
return new OpenAPI()
.info(new Info().title(title)
.description(description)
.version(version)
.license(new License().name(licence).url(licenceUrl))
.contact(contact)
.termsOfService(termsOfServiceUrl))
.components(new Components());
}
}
The OpenAPI info is working correctly and displayed in the UI.
I then have follewing class to import all Springdoc configurations manually
#Configuration
#Import({org.springdoc.core.SpringDocConfigProperties.class,
org.springdoc.webmvc.core.MultipleOpenApiSupportConfiguration.class,
org.springdoc.core.SpringDocConfiguration.class, org.springdoc.webmvc.core.SpringDocWebMvcConfiguration.class,
SwaggerUiConfigParameters.class, SwaggerUiOAuthProperties.class,
org.springdoc.core.SwaggerUiConfigProperties.class, org.springdoc.core.SwaggerUiOAuthProperties.class,
org.springdoc.webmvc.ui.SwaggerConfig.class, GroupsConfig.class,
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration.class})
public class SwaggerConfig {
}
If I go to /v3/api-docs, I get a giant JSON with all the different paths in my application. If I go to /v3/api-docs/admin, I get a 404. So the GroupedOpenApi beans are not getting picked up by Springdoc.
Anyone having the same issue or an idea how to fix this?
Thanks in advance!
Edit: I just tried it with 1.4.4 and it works. What should I do to get it working with the newest version?
Related
Facing this issue with Open API 3.0, I am generating the code using openapi-generator-maven-plugin. I am able to generate the code as well. Code is there in the finally generated Jar of Spring boot as well. But Some how I am not able to see the swagger-doc. All I see is this pop-up with this description
Unable to infer base url. This is common when using dynamic servlet registration or when the API is behind an API Gateway. The base url is the root of where all the swagger resources are served. For e.g. if the api is available at http://example.org/api/v2/api-docs then the base url is http://example.org/api/. Please enter the location manually:
I tried setting up the baseUrl property, like this
baseUrl=http://localhost:8080/api
This isn't working, generated Config & controller has following code.
#Controller
public class HomeController {
#RequestMapping("/")
public String index() {
return "redirect:swagger-ui.html";
}
}
Following is configuration class.
#Configuration
#EnableSwagger2
public class OpenAPIDocumentationConfig {
ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("School REST API")
.description("School REST API")
.license("ABC")
.licenseUrl("http://localhost:8080")
.termsOfServiceUrl("")
.version("1.0.0")
.contact(new Contact("","", "xyz#abc.com"))
.build();
}
#Bean
public Docket customImplementation(ServletContext servletContext, #Value("${openapi.schoolRest.base-path:}") String basePath) {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("com.school.rest.generated.controllers"))
.build()
.pathProvider(new BasePathAwareRelativePathProvider(servletContext, basePath))
.directModelSubstitute(java.time.LocalDate.class, java.sql.Date.class)
.directModelSubstitute(java.time.OffsetDateTime.class, java.util.Date.class)
.genericModelSubstitutes(Optional.class)
.apiInfo(apiInfo());
}
class BasePathAwareRelativePathProvider extends RelativePathProvider {
private String basePath;
public BasePathAwareRelativePathProvider(ServletContext servletContext, String basePath) {
super(servletContext);
this.basePath = basePath;
}
#Override
protected String applicationPath() {
return Paths.removeAdjacentForwardSlashes(UriComponentsBuilder.fromPath(super.applicationPath()).path(basePath).build().toString());
}
#Override
public String getOperationPath(String operationPath) {
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromPath("/");
return Paths.removeAdjacentForwardSlashes(
uriComponentsBuilder.path(operationPath.replaceFirst("^" + basePath, "")).build().toString());
}
}
}
I have also removed the springfox & swagger related jars from my project.
Please advice.
I have a couple of APIs and using springfox-swagger for API documentation.
I have a requirement to add the creation date to the respective API. How can I achieve this using swagger. I don't need any API versioning.
Ex:
#ApiOperation(value = "Creates a new user and returns the created user.")
#PostMapping(/user)
public ResponseEntity<UserDto> createUser(#RequestBody UserDto userDto) {
User user =userService.create(userDto);
return new ResponseEntity<>(UserMappers.USER_ENTITY_TO_DTO.apply(user),HttpStatus.CREATED);
}
In the above example, I want to add the creation date of /user so that I can trace the creation date.
In my project I have a similar requirement. As a solution I have created a custom annotation (for marking the endpoint) and wrote a plugin (for updating the API description).
Option #1
#ApiSince annotation:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface ApiSince {
String value() default "";
}
ApiSincePlugin plugin:
#Component
public class ApiSincePlugin implements OperationBuilderPlugin {
private final DescriptionResolver resolver;
#Autowired
public ApiSincePlugin(DescriptionResolver resolver) {
this.resolver = resolver;
}
#Override
public void apply(OperationContext context) {
final String sinceTemplate = "### Since %s%n%n%s";
String notes = "";
Optional<ApiOperation> apiOperationOptional = context.findAnnotation(ApiOperation.class);
if (apiOperationOptional.isPresent()) {
notes = apiOperationOptional.get().notes();
}
String finalNotes = notes;
Optional<ApiSince> apiSinceOptional = context.findAnnotation(ApiSince.class);
if (apiSinceOptional.isPresent()) {
finalNotes = String.format(sinceTemplate, apiSinceOptional.get().value(), notes);
}
context.operationBuilder().notes(resolver.resolve(finalNotes));
}
#Override
public boolean supports(DocumentationType type) {
return true;
}
}
#ApiSince in action:
#ApiSince(value = "2019-10-31")
#PostMapping(value = "/login")
#ApiOperation(value = "Authenticate user", nickname = "login", notes = "your API description")
#ResponseStatus(HttpStatus.OK)
#ApiResponses(value = {
#ApiResponse(code = 200, response = LoginResponse.class, message = HTTP_200_OK),
...
})
#ResponseBody
ResponseEntity<LoginResponse> login(...);
If you don't want do add it in the description but as an extra JSON attribute then take a look at this solution: Custom Operation Builder Plugin
.
Option #2
#ApiSince annotation (code same as above)
ApiSincePlugin plugin:
#Component
public class ApiSincePlugin implements OperationBuilderPlugin {
#Override
public void apply(OperationContext context) {
Optional<ApiSince> annotation = context.findAnnotation(ApiSince.class);
if (annotation.isPresent()) {
String value = annotation.get().value();
ObjectVendorExtension extention = new ObjectVendorExtension("x-since");
extention.addProperty(new StringVendorExtension("value", value));
context.operationBuilder().extensions(Collections.singletonList(extention));
}
}
#Override
public boolean supports(DocumentationType documentationType) {
return true;
}
}
Activate extensions in the Swagger UI:
#Bean
UiConfiguration uiConfig() {
return UiConfigurationBuilder
.builder()
.showExtensions(true)
...
.build();
}
#ApiSince in action (code same as above):
I am trying to integrate Spring Boot and Shiro. When I tried to call SecurityUtils.getSubject() in one of my controllers, an exception occurred:
org.apache.shiro.UnavailableSecurityManagerException: No SecurityManager accessible to the calling code, either bound to the org.apache.shiro.util.ThreadContext or as a vm static singleton. This is an invalid application configuration.
I just followed some tutorials and docs to configure Shiro and here is my ShiroConfig class:
#Configuration
public class ShiroConfig {
#Bean
public Realm realm() {
return new UserRealm();
}
#Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName(PasswordEncoder.getALGORITHM());
hashedCredentialsMatcher.setHashIterations(PasswordEncoder.getITERATION());
return hashedCredentialsMatcher;
}
#Bean
public UserRealm shiroRealm() {
UserRealm userRealm = new UserRealm();
userRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return userRealm;
}
#Bean
public SessionsSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(shiroRealm());
return securityManager;
}
#Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setUsePrefix(true);
return defaultAdvisorAutoProxyCreator;
}
#Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition() {
DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
definition.addPathDefinition("/login", "anon");
definition.addPathDefinition("/register", "anon");
definition.addPathDefinition("/api/**", "user");
return definition;
}
}
And this is the code which caused exception:
#PostMapping("/login")
#ResponseBody
public Object login(#RequestParam("username") String username,
#RequestParam("password") String password) {
if (username.equals("") || password.equals("")) {
return "please provide complete information";
}
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
Subject subject = SecurityUtils.getSubject(); // this line caused exception
...
}
I am very confused about this exception. Could anyone help?
EDIT
I am using Spring Boot 2.1.6.RELEASE and shiro-spring-boot-starter 1.4.0.
Are you using the shiro-spring-boot-web-starter dependency instead of the shiro-spring-boot-starter dependency?
It looks like that is required for spring boot web applications according to this doc.
https://shiro.apache.org/spring-boot.html#Spring-WebApplications
I have the AuthorizationServer. Besides standard functions i have controller who let to create user. After successful user creates the method must to return token for this user. The problem is that the method return valid token only at first call. At next calls - following users will get the first user's token. I tried to set scope(request) for restTemplate - but obtained the error: " Scope 'request' is not active for the current thread"
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Override
public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {
...
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
...
}
protected ResourceOwnerPasswordResourceDetails getOwnerPasswordResource(){
ResourceOwnerPasswordResourceDetails resource = new ResourceOwnerPasswordResourceDetails();
List scopes = new ArrayList<String>(3);
scopes.add(SCOPE_READ);
scopes.add(SCOPE_WRITE);
scopes.add(SCOPE_TRUST);
resource.setAccessTokenUri(tokenUrl);
resource.setClientId(CLIENT_ID);
resource.setClientSecret(CLIENT_SECRET_UNCODED);
resource.setGrantType(GRANT_TYPE_PASSWORD);
resource.setScope(scopes);
return resource;
}
}
Here the OAuth2Client:
#EnableOAuth2Client
#Configuration
public class ClientConfig {
#Autowired
AuthorizationServerConfig authorizationServerConfig;
#Bean
//#Scope("request")
public OAuth2RestOperations restTemplate() {
AccessTokenRequest atr = new DefaultAccessTokenRequest();
return new OAuth2RestTemplate(authorizationServerConfig.getOwnerPasswordResource(), new DefaultOAuth2ClientContext(atr));
}
}
And my controller:
#RestController
public class UserRestController {
#Autowired
private OAuth2RestOperations restTemplate;
#PostMapping("/user")
public OAuth2AccessToken createUserCredential(#RequestBody UserCredential user) {
user.validate();
userCredentialService.checkAndSaveUser(user, getClientIp(request));
restTemplate.getOAuth2ClientContext().getAccessTokenRequest().set("username", user.getLogin());
restTemplate.getOAuth2ClientContext().getAccessTokenRequest().set("password", user.getPassword);
return restTemplate.getAccessToken();
}
}
May be exists more correct way to obtain token inside of AuthorizationServer ?
I thought have some special way.. but not found it. And solved problem on following way
#EnableOAuth2Client
#Configuration
public class OAuthClientConfig {
#Autowired
AuthorizationServerConfig authorizationServerConfig;
public OAuth2RestOperations restTemplate() {
AccessTokenRequest atr = new DefaultAccessTokenRequest();
return new OAuth2RestTemplate(authorizationServerConfig.getOwnerPasswordResource(), new DefaultOAuth2ClientContext(atr));
}
}
And my controller:
#RestController
public class UserRestController {
#Autowired
private OAuthClientConfig oAuthClientConfig;
#PostMapping("/user")
public OAuth2AccessToken createUserCredential(#RequestBody UserCredential user) {
user.validate();
userCredentialService.checkAndSaveUser(user, getClientIp(request));
OAuth2RestOperations restTemplate = oAuthClientConfig.restTemplate();
restTemplate.getOAuth2ClientContext().getAccessTokenRequest().set("username", user.getLogin());
restTemplate.getOAuth2ClientContext().getAccessTokenRequest().set("password", user.getPassword);
return restTemplate.getAccessToken();
}
}
May be it will help to someone
I was facing the same issue I found this other way to make it work
#Bean
#Primary
#Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public OAuth2RestTemplate oauth2RestTemplate(OAuth2ClientContext context,
OAuth2ProtectedResourceDetails details) {
AccessTokenRequest atr = new DefaultAccessTokenRequest();
OAuth2RestTemplate template = new OAuth2RestTemplate(resource(), new DefaultOAuth2ClientContext(atr));
AccessTokenProvider accessTokenProvider = new AccessTokenProviderChain(Arrays.<AccessTokenProvider>asList(
new AuthorizationCodeAccessTokenProvider(), new ImplicitAccessTokenProvider(),
new ResourceOwnerPasswordAccessTokenProvider(), new ClientCredentialsAccessTokenProvider()));
template.setAccessTokenProvider(accessTokenProvider);
return template;
}
And then I just did the injection
private final OAuth2RestTemplate oauth2RestTemplate;
#GetMapping(path = "/token")
public String token(Credentials credentials) {
oauth2RestTemplate.getOAuth2ClientContext()
.getAccessTokenRequest().add("username", credentials.getEmail());
oauth2RestTemplate.getOAuth2ClientContext()
.getAccessTokenRequest().add("password", credentials.getPass());
final OAuth2AccessToken accessToken = oauth2RestTemplate.getAccessToken();
final String accessTokenAsString = accessToken.getValue();
return accessTokenAsString ;
}
EDIT: I'm using Swagger UI 2.5.0 and attempting to configure it to use oauth authentication. From what I understand from looking at the petstore example and other pieces I've read if I include a security schema & context in my Docket it should automatically display the button - is this the case? If so what else am I missing here?
My API's appear fine in the swagger UI - it's just that the authorize button (and therefore any means of authorization) is missing
#Configuration
#EnableSwagger2
public class SwaggerConfig {
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("api")
.select()
.apis(RequestHandlerSelectors.basePackage("com.example"))
.paths(PathSelectors.regex("/api.*"))
.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(PathSelectors.regex("/api.*"))
.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));
}