Springfox 3 OpenAPI 3 does not send authorization header with request - java

I run the following configuration to enable basic authentication for my (non-spring boot) project
#Configuration
#EnableOpenApi
#EnableWebMvc
public class SpringFoxConfig implements WebMvcConfigurer {
#Bean
public Docket api() {
return new Docket(DocumentationType.OAS_30)
.select()
.apis(RequestHandlerSelectors.withClassAnnotation(PublicAPI.class))
.paths(PathSelectors.any())
.build()
.securityContexts(Arrays.asList(securityContext()))
.securitySchemes(Arrays.asList(securityScheme())));
}
private SecurityScheme securityScheme() {
return new HttpAuthenticationBuilder()
.name("basic")
.scheme("basic")
.build();
}
private SecurityContext securityContext() {
return SecurityContext
.builder()
.securityReferences(securityReferences())
.operationSelector(operationContext -> true)
.build();
}
private List<SecurityReference> securityReferences() {
return singletonList(new SecurityReference("Authorization", new AuthorizationScope[] {new AuthorizationScope("global", "global")}));
}
}
this allows me to authorize my requests
but when testing the call, the authorization header isn't built nor sent with the request:
curl -X GET "https://localhost:8443/foo/rest/ws/info/get-master/code/awd" -H "accept: application/json"

Security reference name was different from the one defined in the securityScheme
private SecurityScheme securityScheme() {
return new HttpAuthenticationBuilder()
.name("basic")
.scheme("basic")
.build();
}
private SecurityContext securityContext() {
return SecurityContext.builder()
.securityReferences(defaultAuth())
.forPaths(PathSelectors.any())
.build();
}
private List<SecurityReference> defaultAuth() {
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = new AuthorizationScope("global", "accessEverything");
return singletonList(new SecurityReference("basic", authorizationScopes));
}

Related

Java - Getting "serverWebExchange cannot be null" when sending request using Spring Security WebClient

I am getting "serverWebExchange cannot be null" when sending request using Spring Security WebClient. Here is my configuration class:
#Configuration
public class myonfig {
#Bean
ReactiveClientRegistrationRepository clientRegistrations() {
ClientRegistration registration =
ClientRegistration.withRegistrationId("authProvider")
.tokenUri(tokenUri)
.clientId(clientId)
.clientSecret(clientSecret)
.scope(scope)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
.build();
return new InMemoryReactiveClientRegistrationRepository(registration);
}
#Bean(name = "mybean")
WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations) {
InMemoryReactiveOAuth2AuthorizedClientService clientService =
new InMemoryReactiveOAuth2AuthorizedClientService(clientRegistrations);
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new
ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations,
new AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository(clientService));
oauth.setDefaultClientRegistrationId("authProvider");
return WebClient.builder()
.filter(oauth)
.defaultHeaders(httpHeaders -> {
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
httpHeaders.setAccept(List.of(MediaType.APPLICATION_JSON));
})
.build();
}
And the class making the call:
{
ResponseEntity<String> responseEntity;
try {
responseEntity = webClient.post()
.uri(url)
.body(BodyInserters.fromValue(valueString))
.retrieve()
.toEntity(String.class)
.block();
}
Tried to do some research on serverWebExchange, but didn't find how to use it. Can someone please let me know why I am getting this error. Thanks.

Adding Authorization in requests made using Swagger UI

I have been trying to add authorization in requests that I try from swagger-ui, but in the request, the authorization header is always coming as null.
This is what I have done.
private ApiKey apiKey() {
return new ApiKey("apiKey", "Authorization",
"header"); //`apiKey` is the name of the APIKey, `Authorization` is the key in the request header
}
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select().apis(RequestHandlerSelectors.basePackage("com.example.app"))
.paths(PathSelectors.any()).build().apiInfo(apiInfo()).securitySchemes(Arrays.asList(apiKey()));
}
Can anyone please give some pointers? Thanks.
You can try this SwaggerConfig
#Configuration
#EnableSwagger2
public class SwaggerConfig {
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2).select()
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)).paths(PathSelectors.any())
.build().securitySchemes(Lists.newArrayList(apiKey()));
}
private ApiKey apiKey() {
return new ApiKey("AUTHORIZATION", "api_key", "header");
}
}

Spring Boot2 Oauth2 Implicit Flow - http://localhost:8080/oauth/authorize getting Access Denied

I have created a Spring Boot 2 Application, integrated SpringFox Swagger 2.8.0 with Implicit Oauth2 Grant for Authentication and Authorization.
The Code is working fine but when I click Authorize button it redirects to the
http://localhost:8080/oauth/authorize?response_type=token&client_id=test-app-client-id&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fwebjars%2Fspringfox-swagger-ui%2Foauth2-redirect.html&scope=read&state=U3VuIE9jdCAxNCAyMDE4IDIwOjQyOjUwIEdNVCswNTMwIChJbmRpYSBTdGFuZGFyZCBUaW1lKQ%3D%3D
but shows Access Denied like as shown below.
My complete project is available in GitHub
MainApplication.java
#EnableSwagger2
#SpringBootApplication
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
#RestController
public class MainApplication /*extends WebMvcConfigurerAdapter*/
{
public static void main(String[] args)
{
SpringApplication.run(MainApplication.class, args);
}
#RequestMapping("/user")
public Principal user(Principal user) {
return user;
}
#Bean
SecurityConfiguration security() {
return SecurityConfigurationBuilder.builder()//<19>
.clientId("test-app-client-id")
.build();
}
#Bean
SecurityScheme oauth() {
List<GrantType> grantTypes = new ArrayList<>();
ImplicitGrant implicitGrant = new ImplicitGrant(new LoginEndpoint("http://localhost:8080/oauth/authorize"),"access_code");
grantTypes.add(implicitGrant);
List<AuthorizationScope> scopes = new ArrayList<>();
scopes.add(new AuthorizationScope("read","Read access on the API"));
return new OAuthBuilder()
.name("SECURITY_SCHEME_OAUTH2")
.grantTypes(grantTypes)
.scopes(scopes)
.build();
}
#Bean
public Docket docket()
{
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage(getClass().getPackage().getName()))
.paths(PathSelectors.any())
.build()
.securitySchemes(Collections.singletonList(oauth()))
.apiInfo(generateApiInfo());
}
private ApiInfo generateApiInfo()
{
return new ApiInfo("Sample Service", "This service is to check Sample Service.", "Version 1.0",
"Sample Service", "123#test.com", "Apache 2.0", "http://www.apache.org/licenses/LICENSE-2.0");
}
}
Update 1
I have added the security and the passwordencoder configure suggested from #AlexanderPetrov. Things are working fine, when I add #EnableResourceServer my login screen is showing Full authentication is required to access this resource like as shown below
Can anyone please help me on this
You need to do the following changes in your code
Form login configuration is necessary for implicit flow.
Also if we use implicit flow token will be generated through authorization url instead of token url. So you need to change "/oauth/token" to "oauth/authorize". configure method below
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/oauth/authorize").authenticated()
.and()
.authorizeRequests().anyRequest().permitAll()
.and()
.formLogin().permitAll()
.and()
.csrf().disable();
}
Add password encoder in SecurityConfig class and invoke it to encode user password in globalUserDetails method. Encoder is necessary because you use in memory passwords. So without password encoder application fails with an error:
Encoded password does not look like BCrypt
Code fragment below
#Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
PasswordEncoder passwordEncoder = passwordEncoder();
auth.inMemoryAuthentication().passwordEncoder(passwordEncoder()).
withUser("bill").password(passwordEncoder.encode("abc123")).roles("ADMIN").and()
.withUser("$2a$10$TT7USzDvMxMZvf0HUVh9p.er1GGnjNQzlcGivj8CivnaZf9edaz6C")
.password("$2a$10$TT7USzDvMxMZvf0HUVh9p.er1GGnjNQzlcGivj8CivnaZf9edaz6C").roles("USER");
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
Hope it helps. I've created branch for your project but couldn't push it because of 403. So all necessary code is here in my answer.
When you enable a resource server, you need to configure the check_token URL, so that it can reach the OAuth2 authorization server and validate the given access_token.
You could do something like:
#Configuration
#EnableResourceServer
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class OAuth2ResourceServerConfig extends GlobalMethodSecurityConfiguration {
#Value("${oauth.url.internal}") // e.g. http://localhost:8082/oauth
private String oauthUrl;
#Value("${oauth.client}")
private String oauthClient;
#Value("${oauth.secret}")
private String oauthSecret;
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
#Primary
#Bean
public RemoteTokenServices tokenService() {
RemoteTokenServices tokenService = new RemoteTokenServices();
tokenService.setCheckTokenEndpointUrl(oauthUrl + "/check_token");
tokenService.setClientId(oauthClient);
tokenService.setClientSecret(oauthSecret);
return tokenService;
}
}
Besides this, you may want to ignore Swagger-specific endpoints:
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/v2/api-docs", "/configuration/ui", "/swagger-resources", "/configuration/security", "/swagger-ui.html", "/webjars/**");
}
}
Just in case, this is the class I implemented for Swagger w/ OAuth2 authorization:
#EnableSwagger2
#Configuration
public class SwaggerConfig implements WebMvcConfigurer {
private static final String BASE_PACKAGE = "com.somepackage.api";
#Value("${oauth.url}") // Make sure this is an external URL, i.e. accessible from Swagger UI
private String oauthUrl;
#Value("${swagger.scopes}")
private String swaggerScopes;
#Value("${swagger.urls}")
private String swaggerUrls; // Your v2/api-docs URL accessible from the UI
#Bean
public Docket api(){
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage(BASE_PACKAGE))
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
.securitySchemes(Collections.singletonList(securitySchema()))
.securityContexts(Collections.singletonList(securityContext()));
}
private OAuth securitySchema() {
List<AuthorizationScope> authorizationScopeList = new ArrayList<>();
authorizationScopeList.add(new AuthorizationScope(swaggerScopes, ""));
List<GrantType> grantTypes = new ArrayList<>();
GrantType creGrant = new ResourceOwnerPasswordCredentialsGrant(oauthUrl + "/token");
grantTypes.add(creGrant);
return new OAuth("oauth2schema", authorizationScopeList, grantTypes);
}
private SecurityContext securityContext() {
return SecurityContext.builder().securityReferences(defaultAuth()).forPaths(PathSelectors.ant(swaggerUrls)).build();
}
private List<SecurityReference> defaultAuth() {
final AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = new AuthorizationScope(swaggerScopes, "");
return Collections.singletonList(new SecurityReference("oauth2schema", authorizationScopes));
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
Versions:
springSecurityVersion = '2.0.5.RELEASE'
swaggerVersion = '2.8.0'
springBootVersion = '2.0.5.RELEASE'

Swagger anonymousUser

I have configured swagger to use login / password as followed:
#Configuration
#EnableSwagger2
public class SwaggerConfiguration {
#Bean
public Docket SwaggerApi() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("cms")
.select().apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
.securitySchemes(Collections.singletonList(securitySchema()))
.securityContexts(Collections.singletonList(securityContext())).pathMapping("/")
.useDefaultResponseMessages(false)
.apiInfo(apiInfo());
}
private SecurityContext securityContext() {
return SecurityContext.builder().securityReferences(defaultAuth()).forPaths(PathSelectors.ant("/**"))
.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("app", "app-secret", "", "", "", ApiKeyVehicle.HEADER, "", " ");
}
private OAuth securitySchema() {
List<AuthorizationScope> authorizationScopeList = new ArrayList<>();
authorizationScopeList.add(new AuthorizationScope("read", "read all"));
authorizationScopeList.add(new AuthorizationScope("trust", "trust all"));
authorizationScopeList.add(new AuthorizationScope("write", "access all"));
List<GrantType> grantTypes = new ArrayList<>();
GrantType creGrant = new ResourceOwnerPasswordCredentialsGrant("http://localhost/swaggerAuth");
grantTypes.add(creGrant);
return new OAuth("oauth2schema", authorizationScopeList, grantTypes);
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Application")
.version("1.0")
.build();
}
And this is my method for autorization:
#RequestMapping(value = "/swaggerAuth", method = RequestMethod.POST,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = {MediaType.APPLICATION_ATOM_XML_VALUE, MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<?> authenticate(#RequestBody MultiValueMap<String, String> formData) {
String username = formData.get("username").get(0);
String password = formData.get("password").get(0);
final Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(username, password)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
final UserDetails userDetails = userDetailsService.loadUserByUsername(username );
return jwtTokenUtil.generateToken(userDetails);
}
When I login via Swagger all is fine. User gets authneticated and Authentication is set to SecurityContextHolder.
But with every next swagger request the user is anonymousUser and not the one I authenticated with.
What is wrong with my configuration?
EDIT:
The token I return from authorization controller is not beeing sent in swagger request headers...
The problem was that in authenticate() method I was returning plain string. Instead I should return an object with access_token string field:
public class SwaggerAuthenticationResponse {
private final String access_token;
public SwaggerAuthenticationResponse(String access_token) {
this.access_token = access_token;
}
public String getAccess_token() {
return this.access_token;
}
}

Swagger UI Authorize button does not appear

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));
}

Categories