APIs not getting detected by swagger on a Spring ROO project - swagger

I have tried a lot of things but APIs are not getting detected by swagger for some reason. Do i have to specify a package for swagger to scan? or some url include patterns?
My Swager Config :
#Configuration
#EnableSwagger
#EnableWebMvc
public class SwaggerConfiguration {
private final Logger log = LoggerFactory
.getLogger(SwaggerConfiguration.class);
/**
* Swagger Spring MVC configuration.
*/
#Bean
public SwaggerSpringMvcPlugin swaggerSpringMvcPlugin(
SpringSwaggerConfig springSwaggerConfig) {
log.debug("Starting Swagger");
StopWatch watch = new StopWatch();
watch.start();
SwaggerSpringMvcPlugin swaggerSpringMvcPlugin = new SwaggerSpringMvcPlugin(
springSwaggerConfig).apiInfo(apiInfo())
.genericModelSubstitutes(ResponseEntity.class);
swaggerSpringMvcPlugin.build();
watch.stop();
log.debug("Started Swagger in {} ms", watch.getTotalTimeMillis());
return swaggerSpringMvcPlugin;
}
/**
* API Info as it appears on the swagger-ui page.
*/
private ApiInfo apiInfo() {
return new ApiInfo("Title", "Description", "terms of service",
"contact", "license", "licenseUrl");
}
}
Sample Controller
#RequestMapping("/settings")
#Controller
#Api(value = "/settings", description = "Endpoint for settings management")
public class SettingsController {
#ApiOperation(value = "API Operation")
#RequestMapping(value = "/changepassword", method = RequestMethod.POST)
public #ResponseBody Map<String, Object> changePassword(#RequestParam Map<String, String> userProperties,
Model model, HttpServletRequest httpServletRequest, Locale locale) {
Map<String, Object> responseMap = new HashMap<String, Object>();
return responseMap;
}
}
I get an empty response
{
"apiVersion": "1.0",
"swaggerVersion": "1.2",
"apis": [ ],
"authorizations": [ ],
"info":
{
"title": "Title",
"description": "Description",
"termsOfServiceUrl": "terms of service",
"contact": "contact",
"license": "license",
"licenseUrl": "licenseUrl"
}
}
I am using swagger-springmvc version 1.0.2 and spring version 4.1.6.RELEASE

Follow the instructions in the following URL :
http://naddame.blogspot.in/2014/12/spring-roo-mvc-integration-for-swagger.html

Related

Issues getting an api with swagger to authenticate with IdentityServer4 using .net5.0

I am currently learning how microservices work for an application i am building for my portfolio and for a small community of people that want this specific application. I have followed a tutorial online and successfully got IdentityServer4 to authenticate an MVC Client, however, I am trying to get swagger to work alongside the API's especially if they require authentication. Each time I try to authorize swagger with IdentityServer4, I am getting invalid_scope error each time I try authenticate. I have been debugging this problem for many hours an am unable to figure out the issue. I have also used the Microsoft eShopOnContainers as an example but still no luck. Any help would be greatly appreciated. Ill try keep the code examples short, please request any code not shown and ill do my best to respond asap. Thank you.
Identiy.API project startup.cs:
public class Startup {
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentityServer()
.AddInMemoryClients(Config.GetClients())
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryApiScopes(Config.GetApiScopes())
.AddTestUsers(Config.GetTestUsers())
.AddDeveloperSigningCredential(); // #note - demo purposes only. need X509Certificate2 for production)
services.AddControllersWithViews();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseStaticFiles();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints => endpoints.MapDefaultControllerRoute());
}
}
Config.cs (i removed the test users to keep the code shorter, since it is not relevant).
#note -I created a WeatherSwaggerUI client specifically for use with swagger since this was apart of eShopOnContainers example project provided by microsoft:
public static class Config
{
public static List<TestUser> GetTestUsers()
{
return new List<TestUser>(); // removed test users for this post
}
public static IEnumerable<Client> GetClients()
{
// #note - clients can be defined in appsettings.json
return new List<Client>
{
// m2m client credentials flow client
new Client
{
ClientId = "m2m.client",
ClientName = "Client Credentials Client",
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets = { new Secret("SuperSecretPassword".ToSha256())},
AllowedScopes = { "weatherapi.read", "weatherapi.write" }
},
// interactive client
new Client
{
ClientId = "interactive",
ClientSecrets = {new Secret("SuperSecretPassword".Sha256())},
AllowedGrantTypes = GrantTypes.Code,
RedirectUris = {"https://localhost:5444/signin-oidc"},
FrontChannelLogoutUri = "https://localhost:5444/signout-oidc",
PostLogoutRedirectUris = {"https://localhost:5444/signout-callback-oidc"},
AllowOfflineAccess = true,
AllowedScopes = {"openid", "profile", "weatherapi.read"},
RequirePkce = true,
RequireConsent = false,
AllowPlainTextPkce = false
},
new Client
{
ClientId = "weatherswaggerui",
ClientName = "Weather Swagger UI",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = {"https://localhost:5445/swagger/oauth2-redirect.html"},
PostLogoutRedirectUris = { "https://localhost:5445/swagger/" },
AllowedScopes = { "weatherswaggerui.read", "weatherswaggerui.write" },
}
};
}
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("weatherapi", "Weather Service")
{
Scopes = new List<string> { "weatherapi.read", "weatherapi.write" },
ApiSecrets = new List<Secret> { new Secret("ScopeSecret".Sha256()) },
UserClaims = new List<string> { "role" }
},
new ApiResource("weatherswaggerui", "Weather Swagger UI")
{
Scopes = new List<string> { "weatherswaggerui.read", "weatherswaggerui.write" }
}
};
}
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResource
{
Name = "role",
UserClaims = new List<string> { "role" }
},
};
}
public static IEnumerable<ApiScope> GetApiScopes()
{
return new List<ApiScope>
{
// weather API specific scopes
new ApiScope("weatherapi.read"),
new ApiScope("weatherapi.write"),
// SWAGGER TEST weather API specific scopes
new ApiScope("weatherswaggerui.read"),
new ApiScope("weatherswaggerui.write")
};
}
}
Next project is the just the standard weather api when creating a web api project with vs2019
WeatherAPI Project startup.cs (note i created extension methods as found in eShopOnContainers as i liked that flow):
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddCustomAuthentication(Configuration)
.AddSwagger(Configuration);
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger()
.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "Weather.API V1");
options.OAuthClientId("weatherswaggerui");
options.OAuthAppName("Weather Swagger UI");
});
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
public static class CustomExtensionMethods
{
public static IServiceCollection AddSwagger(this IServiceCollection services, IConfiguration configuration)
{
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "Find Scrims - Weather HTTP API Test",
Version = "v1",
Description = "Randomly generates weather data for API testing"
});
options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows()
{
Implicit = new OpenApiOAuthFlow()
{
AuthorizationUrl = new Uri($"{ configuration.GetValue<string>("IdentityUrl")}/connect/authorize"),
TokenUrl = new Uri($"{ configuration.GetValue<string>("IdentityUrl")}/connect/token"),
Scopes = new Dictionary<string, string>()
{
{ "weatherswaggerui", "Weather Swagger UI" }
},
}
}
});
options.OperationFilter<AuthorizeCheckOperationFilter>();
});
return services;
}
public static IServiceCollection AddCustomAuthentication(this IServiceCollection services, IConfiguration configuration)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("sub");
var identityUrl = configuration.GetValue<string>("IdentityUrl");
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Authority = identityUrl;
options.RequireHttpsMetadata = false;
options.Audience = "weatherapi";
});
return services;
}
}
Lastly is the AuthorizeCheckOperationFilter.cs
public class AuthorizeCheckOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var hasAuthorize = context.MethodInfo.DeclaringType.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any() ||
context.MethodInfo.GetCustomAttributes(true).OfType<AuthorizeAttribute>().Any();
if (!hasAuthorize) return;
operation.Responses.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" });
operation.Responses.TryAdd("403", new OpenApiResponse { Description = "Forbidden" });
var oAuthScheme = new OpenApiSecurityScheme
{
Reference = new OpenApiReference {
Type = ReferenceType.SecurityScheme,
Id = "oauth2"
}
};
operation.Security = new List<OpenApiSecurityRequirement>
{
new OpenApiSecurityRequirement
{
[oAuthScheme ] = new [] { "weatherswaggerui" }
}
};
}
}
again, any help, or recommendations on a guide would be greatly appreciated as google has not provided me with any results to fixing this issue. Im quite new to IdentityServer4 and am assuming its a small issue due with clients and ApiResources and ApiScopes. Thank you.
The swagger client needs to access the api and to do so it requires api scopes. What you have for swagger scopes are not doing this. Change the scopes for swagger client ‘weatherswaggerui’ to include the api scopes like this:
AllowedScopes = {"weatherapi.read"}

How to setup Swashbuckle.AspNetCore and Oauth2

I'm trying to figure out where I went wrong.
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new Info { Title = "MySite API", Version = "v1" });
options.OperationFilter<AuthorizeCheckOperationFilter>();
options.OperationFilter<AddSwaggerHeadersOperationFilter>();
options.AddSecurityDefinition("oauth2", new OAuth2Scheme
{
Type = "oauth2",
Flow = "implicit",
AuthorizationUrl = "authorization url",
TokenUrl = "token url",
Scopes = new Dictionary<string, string>()
{
{ "scope", "Scope" }
}
});
});
//Configure Method
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "MySite API V1");
options.OAuthClientId("MyClientId");
options.OAuthAppName("Swagger Api Calls");
//c.RoutePrefix = string.Empty;
});
//AuthorizeCheckOperationFilter
internal class AuthorizeCheckOperationFilter : IOperationFilter
{
public void Apply(Operation operation, OperationFilterContext context)
{
if (context.ApiDescription.TryGetMethodInfo(out var methodInfo))
{
var attributes = methodInfo.DeclaringType.GetTypeInfo().GetCustomAttributes(true);
if (attributes.OfType<AuthorizeAttribute>().Any())
{
operation.Responses.Add("401", new Response { Description = "Unauthorized" });
operation.Responses.Add("403", new Response { Description = "Forbidden" });
operation.Security = new List<IDictionary<string, IEnumerable<string>>>();
operation.Security.Add(new Dictionary<string, IEnumerable<string>>
{
{ "oauth2", new [] { "api1" } }
});
}
}
}
}
//Extra field
internal class AddSwaggerHeadersOperationFilter : IOperationFilter
{
public void Apply(Operation operation, OperationFilterContext context)
{
if (operation.Parameters == null)
operation.Parameters = new List<IParameter>();
operation.Parameters.Add(new NonBodyParameter
{
Name = "SomeField",
In = "header",
Type = "string",
Required = true,
Default = "some value"
});
}
}
Now when I open up the swagger page I get the Authorize button, to which I click and when I fill out the details there I get redirected to my Identity Website which logs me in and redirects right back to swagger. Swagger then says authorized, as if everything is fine.
Then I try to use an API which requires Bearer token to be passed and it doesn't pass it. I don't see it in the header and by my logs from the identity website nothing was passed.
Any idea why or how to fix this? I'm using Swashbuckle.AspNetCore 4.1 package.
You can add DocumentFilter :
public class SecurityRequirementsDocumentFilter : IDocumentFilter
{
public void Apply(SwaggerDocument document, DocumentFilterContext context)
{
document.Security = new List<IDictionary<string, IEnumerable<string>>>()
{
new Dictionary<string, IEnumerable<string>>()
{
{ "oauth2", new string[]{ "openid", "profile", "email" } },
}
};
}
}
And then register the filter in AddSwaggerGen function :
options.DocumentFilter<SecurityRequirementsDocumentFilter>();
Reference : https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/603#issuecomment-368487641
I test with your code sample and it works as expected :

Adding x-logo vendor extension using Swashbuckle Asp.Net Core for ReDoc

I'm using swagger.json file (generated by Swashbuckle) for ReDoc to display API documentation.
What I Need:
Add x-logo vendor extension to swagger json generated using Swashbuckle (Swashbuckle.AspNetCore.SwaggerGen library) so that ReDoc UI shows logo at the top left corner like this
Problem:
I was able to add x-log to the swagger.json file but it is added to wrong section of the file. It needs to be inside info section.
This is what I have done to add the x-logo
Created a document filter like below
public class XLogoDocumentFilter : IDocumentFilter
{
public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
{
swaggerDoc.Extensions["x-logo"] = new { url = "https://URL/of/the/logo", altText = "Company Logo" };
}
}
Added the filter to SwaggerDoc as
services.AddSwaggerGen(options =>
{
options.DocumentFilter<XLogoDocumentFilter>();
});
Actual
{
"swagger": "2.0",
"info": {
"version": "v1",
"title":"Sample REST API"
},
"x-logo": {
"url": "https://rebilly.github.io/ReDoc/petstore-logo.png",
"altText": "Aimia Logo"
}
}
Expected
{
"swagger": "2.0",
"info": {
"version": "v1",
"title":"Sample REST API",
"x-logo": {
"url": "https://rebilly.github.io/ReDoc/petstore-logo.png",
"altText": "Aimia Logo"
}
},
}
Really appreciate any help or suggestions to have the x-logo in the correct section of the swagger.json file.
After typing the question I found the solution myself. Instead of adding extension directly to swaggerDoc, add it to swaggerDoc.Info object.
public class XLogoDocumentFilter : IDocumentFilter
{
public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
{
// need to check if extension already exists, otherwise swagger
// tries to re-add it and results in error
if (!swaggerDoc.Info.Extensions.ContainsKey("x-logo"))
{
swaggerDoc.Info.Extensions.Add("x-logo", new {
url = "https://URL/To/The/Logo",
altText = "Logo",
});
}
}
}
The newer versions of Swashbuckle support this in the SwaggerDoc setup:
c.SwaggerDoc("v1", new OpenApiInfo
{
Title = ApiDescription,
Version = "v1",
Extensions = new Dictionary<string, IOpenApiExtension>
{
{"x-logo", new OpenApiObject
{
{"url", new OpenApiString("https://blah.com/logo")},
{ "altText", new OpenApiString("The Logo")}
}
}
}
});
for .NET core 2.2 and higher
public class XLogoDocumentFilter : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
// need to check if extension already exists, otherwise swagger
// tries to re-add it and results in error
if (!swaggerDoc.Info.Extensions.ContainsKey("x-logo"))
swaggerDoc.Info.Extensions.Add("x-logo", new OpenApiObject
{
{"url", new OpenApiString("https://www.petstore.com/assets/images/logo.png")},
{"backgroundColor", new OpenApiString("#FFFFFF")},
{"altText", new OpenApiString("PetStore Logo")}
});
}
}

User can access resource even if he's not part of "#Secured([role])"

I would like to secure my endpoint so only users with the role READ can access a certain resource. Those are my configurations:
Controller:
#RestController
#RequestMapping("/api/status")
public class StatusController {
#RequestMapping(method = RequestMethod.GET)
#Secured("READ")
Map<String, Object> getSecureStatus() {
Map<String, Object> statusMap = new LinkedHashMap<>();
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
statusMap.put("auth", auth);
return statusMap;
}
}
The WebSecurityConfigurerAdapter:
#Configuration
#EnableGlobalMethodSecurity(securedEnabled = true)
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
// .antMatchers("/").permitAll()
.antMatchers("/api/**").authenticated()
.and()
.httpBasic();
}
}
GlobalAuthenticationConfigurerAdapter:
#Configuration
public class AuthenticationManagerConfig extends
GlobalAuthenticationConfigurerAdapter {
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("teddy").password("password").roles("USER");
}
}
I would assume that Teddy shouldn't be able to access the resource, as his role is USER rather than READ.
But with this call, Teddy gets his information anyway:
curl -u teddy:password 'http://localhost:8080/api/status/':
{
"auth": {
"details": {
"remoteAddress": "127.0.0.1",
"sessionId": null
},
"authorities": [
{
"authority": "ROLE_USER"
}
],
"authenticated": true,
"principal": {
"password": null,
"username": "teddy",
"authorities": [
{
"authority": "ROLE_USER"
}
],
"accountNonExpired": true,
"accountNonLocked": true,
"credentialsNonExpired": true,
"enabled": true
},
"credentials": null,
"name": "teddy"
}
}
What am I missing?
Edit: removed .antMatchers("/").permitAll()
Probably it's because you're using .antMatchers("/").permitAll() it's telling spring that you're allowing every request.
Try removing it from your configuration.
I found the mistake. I overlooked that getSecureStatus() wasn't explicitely defined public. This code fixes it:
#RestController
#RequestMapping("/api/status")
public class StatusController {
#RequestMapping(method = RequestMethod.GET)
#Secured("READ")
public Map<String, Object> getSecureStatus() {
Map<String, Object> statusMap = new LinkedHashMap<>();
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
statusMap.put("auth", auth);
return statusMap;
}
}

Spring Data Rest Neo4j: Template must not be null or empty

I am creating what I believe to be a fairly simple domain model in Spring.
#NodeEntity
class Dependency {
#GraphId
private Long id
String groupId
String artifactId
String version
#Fetch
#RelatedTo(type = "DEPENDS_ON", direction = OUTGOING)
Set<Dependency> dependencies = new HashSet<Dependency>()
}
note* the above is written in groovy.
I have also created a subsequent Repository (all textbook so far!).
#RepositoryRestResource(collectionResourceRel = "dependency", path = "dependency")
interface DependencyRepository extends PagingAndSortingRepository<Dependency, Long> {
List<Dependency> findByArtifactId(#Param("artifactId") String artifactId)
}
And finally the application class....
#EnableNeo4jRepositories(basePackages = "io.byteshifter.depsgraph")
#SpringBootApplication
class Application extends Neo4jConfiguration {
public Application() {
setBasePackage("io.byteshifter.depsgraph")
}
#Bean(destroyMethod = "shutdown")
GraphDatabaseService graphDatabaseService() {
return new GraphDatabaseFactory().newEmbeddedDatabase("target/dependency.db")
}
public static void main(String[] args) throws Exception {
SpringApplication.run(Application, args)
}
}
Now I would expect when I fire the following payload at http://127.0.0.1:8080/dependency that it would create all the objects and relationships..
{
"groupId": "group1",
"artifactId": "artifact1",
"version": "1.0",
"dependencies" : [
{"groupId": "group2", "artifactId": "artifact2", "version": "2.0"},
{"groupId": "group3", "artifactId": "artifact3", "version": "3.0"}
]
}
Instead, I get..
{
"cause": {
"cause": {
"cause": null,
"message": "Template must not be null or empty!"
},
"message": "Template must not be null or empty! (through reference chain: io.byteshifter.depsgraph.domain.Dependency[\"dependencies\"]->java.util.LinkedHashSet[0])"
},
"message": "Could not read JSON: Template must not be null or empty! (through reference chain: io.byteshifter.depsgraph.domain.Dependency[\"dependencies\"]->java.util.LinkedHashSet[0]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Template must not be null or empty! (through reference chain: io.byteshifter.depsgraph.domain.Dependency[\"dependencies\"]->java.util.LinkedHashSet[0])"
}
I have no doubt this is a lack of understanding on my behalf. If anyone could help steer me in the right direction that would be very welcomed.
My REST knowledge has failed me. I should have been using a URI to represent other dependencies. See below:
{
"groupId": "group3",
"artifactId": "artifact3",
"version": "1.0",
"dependencies": ["http://127.0.0.1:8080/dependency/0", "http://127.0.0.1:8080/dependency/1", "http://127.0.0.1:8080/dependency/2"]
}

Resources