Spring Boot Admin: custom header authentication - spring-boot-admin

My app has a custom authentication mechanism based on a custom HTTP header. AFAIK, Spring Boot Admin supports only Basic auth and OAuth. But maybe there's a way for clients to supply some custom header along with their requests?

You can add the custom headers into existing headers by injecting the Bean as following.
#Bean
public HttpHeadersProvider customHttpHeadersProvider() {
return instance -> {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("X-CUSTOM", "My Custom Value");
return httpHeaders;
};
}

Alright, so if both SBA Server and SBA Client are launched along with the monitored application itself, and it has custom-headers security, we need to take care of 3 things:
As Nitin mentioned, one needs to register HttpHeadersProvider bean:
#Bean
public HttpHeadersProvider customHttpHeadersProvider() {
return instance -> {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("X-CUSTOM", "My Custom Value");
return httpHeaders;
};
}
Note, that these headers are not applied to OPTIONS requests to the Actuator endpoints, so one would either need to customize ProbeEndpointsStrategy, or disable Spring Security for OPTIONS calls to the management URL. Also, for some reason, I had to disable security for /actuator/health/**, although it should've been accessible with custom header provided:
#Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/actuator/**").antMatchers(HttpMethod.GET, "/actuator/health/**");
}
Finally, one needs to instantiate ApplicationRegistrator with a custom RestTemplate that would be pre-populated with a custom header:
#Bean
public ApplicationRegistrator registrator(ClientProperties client, ApplicationFactory applicationFactory) {
RestTemplateBuilder builder = new RestTemplateBuilder()
.setConnectTimeout(client.getConnectTimeout())
.setReadTimeout(client.getReadTimeout())
.additionalInterceptors((request, body, execution) -> {
request.getHeaders().set("X-CUSTOM", "My Custom Value");
return execution.execute(request, body);
});
if (client.getUsername() != null) {
builder = builder.basicAuthentication(client.getUsername(), client.getPassword());
}
return new ApplicationRegistrator(builder.build(), client, applicationFactory);
}

Related

API Key implementation in Apache Shiro

I have Java web application which implemented Apache shiro Authentication & Authorization.
Now i need to implement API Key to the existing project (which has apache shiro).
Please help me on implementation part. Even i could not find any documentation
PS:: We have already implemented 3 different types of Custom Realm(jdbc,ldap,Pac4jRealm) but now struggling to implement the API key concept with Apache Shiro.
I resolved the above issue by extending the JDBCRealm,see the below example code
public class APIRealm extends JdbcRealm {
#Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo info = null;
AuthAPIInfo authInfo = null;
try {
String apiKey= (String) principals.getPrimaryPrincipal();
authInfo=fetchAPIKeyInfo(apiName);
// Do all the other stuff like checking for Authorization and setting it to token
} catch (Exception e) {
insertAPILogActivity(authInfo, "User not authorized");
}
return info;
}
private AuthAPIInfo fetchAPIKeyInfo(String apiKeyName) {
//Connect to Database using JDBC connection and validate the API Key and return the AuthAPIInfo
}
}
Add the above realm in shiro.ini
apiRealm=com.example.APIRealm
securityManager.realms=$apiRealm

How to exclude RequestInterceptor for an specific Spring Cloud Feign client?

I have a number of clients for which a "global" RequestInterceptor has been defined. For one of the clients I need this "global" interceptor to be excluded. Is it possible to override the full set of RequestInterceptors for a particular FeignClient?
#FeignClient(value = "foo", configuration = FooClientConfig.class)
public interface FooClient {
//operations
}
#Configuration
public class FooClientConfig{
//How do I exclude global interceptors from this client configuration?
}
The spring-cloud-netflix version in use is 1.1.0 M5
It seems there is no easy way to override the global interceptor.
I think you could do it like this:
#Configuration
public class FooClientConfig{
#Bean
RequestInterceptor globalRequestInterceptor() {
return template -> {
if (template.url().equals("/your_specific_url")) {
//don't add global header for the specific url
return;
}
//add header for the rest of requests
template.header(AUTHORIZATION, String.format("Bearer %s", token));
};
}
}
Based on the issue stated here. Instead of excluding interceptors, you need to define different feign clients for each API. Add your interceptors based on your needs.
public class ConfigOne {
#Bean
public InterceptorOne interceptorOne(AdditionalDependency ad) {
return new InterceptorOne(ad);
}
}
Just make sure you don't use #Configuration annotation on above class.
Instead, importing this bean on client definition would be a working solution.
#FeignClient(name = "clientOne", configuration = ConfigOne.class)
public interface ClientOne { ... }
An enhanced way of solving this is to pass a custom header to your request like:
#PostMapping("post-path")
ResponseEntity<Void> postRequest(#RequestHeader(HEADER_CLIENT_NAME) String feignClientName, #RequestBody RequestBody requestBody);
I want to set the header in interceptor for only this feign client. Before setting the header, first, the interceptor checks HEADER_CLIENT_NAME header if exists and have the desired value:
private boolean criteriaMatches(RequestTemplate requestTemplate) {
Map<String, Collection<String>> headers = requestTemplate.headers();
return headers.containsKey(HEADER_CLIENT_NAME)
&& headers.get(HEADER_CLIENT_NAME).contains("feign-client-name");
}
Thus, you can check before setting the basic authentication. In interceptor:
#Override
public void apply(RequestTemplate template) {
if (criteriaMatches(template)) {
/*apply auth header*/
}
}
In this way, other feign client's requests won't be manipulated by this interceptor.
Finally, I set the feignClientName to the request:
feignClient.postRequest("feign-client-name", postBody);
One way to do this to remove the #Configuration annotation from the FooClientConfig class as in the current situation it is applied globally.
And then use
#FeignClient(value = "foo", configuration = FooClientConfig.class)
on all of the feign clients you want to use the config with.

Swagger UI passing authentication token to API call in header

I am new to Swagger.
I am using Swagger UI to generate swagger documentation. I have two API calls. First call is to generate token based on user name and password. Second call needs token generated by first call.
How I set that token for second call using Swagger UI?
#ApiImplicitParams and #ApiImplicitParam should do the trick:
#GET
#Produces("application/json")
#ApiImplicitParams({
#ApiImplicitParam(name = "Authorization", value = "Authorization token",
required = true, dataType = "string", paramType = "header") })
public String getUser(#PathParam("username") String userName) {
...
}
From the documentation:
You may wish you describe operation parameters manually. This can be for various reasons, for example:
Using Servlets which don't use JAX-RS annotations.
Wanting to hide a parameter as it is defined and override it with a completely different definition.
Describe a parameter that is used by a filter or another resource prior to reaching the JAX-RS implementation.
The Swagger UI will be updated so you can send your token from there. No changes to HTML will be necessary.
Note: A while ago, when documenting a REST API with Swagger, I realized that just adding #ApiImplicitParam is not enough (even if you have only one parameter). Anyway, you must add #ApiImplicitParams too.
My configuration for 2.9.2 Swagger version to add Authorization on Swagger UI and send the Bearer token
#Configuration
public class SwaggerConfiguration{
//...
#Bean
public Docket api(ServletContext servletContext) {
return new Docket(DocumentationType.SWAGGER_2)...
.securitySchemes(Arrays.asList(apiKey()))
.securityContexts(Collections.singletonList(securityContext()));
}
private SecurityContext securityContext() {
return SecurityContext.builder().securityReferences(defaultAuth()).forPaths(PathSelectors.regex("/.*")).build();
}
private List<SecurityReference> defaultAuth() {
final AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
final AuthorizationScope[] authorizationScopes = new AuthorizationScope[]{authorizationScope};
return Collections.singletonList(new SecurityReference("Bearer", authorizationScopes));
}
private ApiKey apiKey() {
return new ApiKey("Bearer", "Authorization", "header");
}
}
Another option is to add globalOperationParameters. It will add a field for authorization in every endpoint.
Define authorization header parameter:
Parameter authHeader = new ParameterBuilder()
.parameterType("header")
.name("Authorization")
.modelRef(new ModelRef("string"))
.build();
Add it to Docket configuration:
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(...)
.paths(...)
.build()
.apiInfo(...)
.globalOperationParameters(Collections.singletonList(authHeader));
And it will look like this:
There is a hack that might work by using responseInterceptor and requestInterceptor
First capture response of the the first API call using responseInterceptor and save the token (in the example in local storage), then use requestInterceptor to add the Authorization header with the saved token.
const ui = SwaggerUIBundle({
...
responseInterceptor:
function (response) {
if (response.obj.access_token) {
console.log(response.obj.access_token)
const token = response.obj.access_token;
localStorage.setItem("token", token)
}
return response;
},
requestInterceptor:
function (request) {
console.log('[Swagger] intercept try-it-out request');
request.headers.Authorization = "Bearer " + localStorage.getItem("token");
return request;
}
}
You would have to customise the swagger index page to accomplish that I believe.
You can make the input 'input_apiKey' hidden and add two inputs for username and password. Then you make an ajax call to update the hidden input with your token.
This is an old question but this is how I solved it recently with version 2.7.0 for my JWT tokens
In your swagger configuration, add below SecurityConfiguration bean. Important part being leaving fifth argument empty or null.
#Bean
public SecurityConfiguration securityInfo() {
return new SecurityConfiguration(null, null, null, null, "", ApiKeyVehicle.HEADER,"Authorization","");
}
Add securitySchemes(Lists.newArrayList(apiKey())) to your main Docket bean.
#Bean
public Docket docket()
{
return new Docket(DocumentationType.SWAGGER_2).select()
.....build().apiInfo(...).securitySchemes(Lists.newArrayList(apiKey()));
}
private ApiKey apiKey() {
return new ApiKey("Authorization", "Authorization", "header");
}
Then in UI , you need to click on Authorize button and input "Bearer access_token" (for Authorization text box )where access_token is token provided by jWT token server.
Once this authorization is saved,that will become effective for all end points. Adding a separate text field for each end point looks very cumbersome.
Using SpringDoc with the springdoc-openapi-maven-plugin my option is to use a SwaggerConfig.Java:
#Configuration
public class SwaggerConfiguration {
#Bean
public OpenAPI customOpenAPI(#Value("${project.version}") String appVersion) {
OpenAPI openApi = new OpenAPI();
openApi.info(
new Info()
.title("Title Example")
.version(appVersion)
.description("Swagger server created using springdocs - a library for OpenAPI 3 with spring boot.")
);
openApi.components(
new Components().addSecuritySchemes("bearer-jwt",
new SecurityScheme().type(SecurityScheme.Type.HTTP).scheme("bearer").bearerFormat("JWT")
.in(SecurityScheme.In.HEADER).name("Authorization"))
);
openApi.addSecurityItem(
new SecurityRequirement().addList("bearer-jwt", Arrays.asList("read", "write"))
);
return openApi;
}
}

Rest Client with RestyGWT and custom headers

I would try to create an API Rest client for a code that I have in a GWT project.
In order to arrive to the server and obtain a response I need to attach some custom headers in my request.
I saw, there were some bugs some years ago when write headers in the request.
Actually, I have this code, and I don't know exactly where and when put my custom header in the request.
final Map<String, String> headers = new HashMap<String, String>();
headers.put("X-USER", "super_admin_key");
Resource resource = new Resource("http://localhost:9998/api/v1/", headers);
ServicesAPI service = GWT.create(ServicesAPI.class);
((RestServiceProxy)service).setResource(resource);
REST.withCallback(new MethodCallback<String>(){
#Override
public void onFailure(org.fusesource.restygwt.client.Method method, Throwable exception) {
domainsCombo.addItem("ERROR");
}
#Override
public void onSuccess(org.fusesource.restygwt.client.Method method, String response) {
domainsCombo.addItem("ok");
}
}).call(service).getServices();
Add headers to the resource object creation is one of the options, but it doesn't work.
Any sugestion?
Thanks.

Using WebServiceGatewaySupport to handle requests to multiple webservices

I'm using spring-ws-core to build a SOAP client. For this I'm extending WebServiceGatewaySupport to make the service calls.
public class WeatherClient extends WebServiceGatewaySupport {
...
public WeatherResponse getCityForecastByZip(String zipCode) {
GetCityForecastByZIP request = new GetCityForecastByZIP();
request.setZIP(zipCode);
GetCityForecastByZIPResponse response = (GetCityForecastByZIPResponse) this.getWebServiceTemplate().marshalSendAndReceive(request,
new SoapActionCallback("http://ws.cdyne.com/WeatherWS/GetCityForecastByZIP"));
return response;
}
...
}
Spring configuration is pretty straightforward
#Configuration
public class WebServicesConfiguration {
private static final String WEATHER_SERVICE_DEFAULT_URI = "...";
#Bean(name = "servicesMarshaller")
public Jaxb2Marshaller servicesMarshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setContextPath("some.package");
return marshaller;
}
#Bean
public WeatherClient weatherService(#Qualifier("servicesMarshaller") Jaxb2Marshaller marshaller) {
WeatherClient client = new WeatherClient(WEATHER_SERVICE_DEFAULT_URI);
client.setMarshaller(marshaller);
client.setUnmarshaller(marshaller);
return client;
}
}
This works just fine for a single web service. Now, suppose that I have many similar web services, but each one has it's own .wsdl specification and URI. I know that I can make a service call through the spring WebServiceTemplate and specify the URI to use. So my idea was to use a single WebServiceGatewaySupport to handle all the calls to the different services. In each call, I would pass the soap action, the corresponding request, if any, and the web service URL. My application is suppose to run in a multi-threaded environment.
Is this a good practice to use a single WebServiceGatewaySupport to handle concurrent calls to different URIs?
Looking to the WebServiceGatewaySupport source code, the short asnwer: yes, it is OK to use it for different URLs, as well as the underlying WebServiceTemplate is thread-safe.
Your implementation will be thread-safe too, if you don't save some state between requests.