How to configure a quarkus custom HttpAuthenticationMechanism? - quarkus

I need a static mechanism to verify my sender knows a static token. That token is hard coded into the sending system.
My API has an endpoint /webhook where I need to have that be verified.
This guides/security-customization gives an example on how to implement a custom mechanism, so I implemented this:
#Singleton
public class FixedTokenAuthenticationMechanism implements HttpAuthenticationMechanism {
#Override
public Uni<SecurityIdentity> authenticate(RoutingContext context, IdentityProviderManager identityProviderManager) {
String authHeader = context.request().headers().get("magic_header");
if (authHeader == "magic_value")
{
return Uni.createFrom().optional(Optional.empty());
}
else
{
return Uni.createFrom().optional(Optional.empty());
}
}
#Override
public Uni<ChallengeData> getChallenge(RoutingContext context) {
return null;
}
#Override
public Set<Class<? extends AuthenticationRequest>> getCredentialTypes() {
return Collections.singleton(AuthenticationRequest.class);
}
#Override
public Uni<Boolean> sendChallenge(RoutingContext context) {
return HttpAuthenticationMechanism.super.sendChallenge(context);
}
#Override
public HttpCredentialTransport getCredentialTransport() {
return HttpAuthenticationMechanism.super.getCredentialTransport();
}
#Override
public Uni<HttpCredentialTransport> getCredentialTransport(RoutingContext context) {
return HttpAuthenticationMechanism.super.getCredentialTransport(context);
}
#Override
public int getPriority() {
return HttpAuthenticationMechanism.super.getPriority();
}
}
I do not know how to configure this to be used in the application properties.
There seems to be a configuration for path-specific-authentication-mechanisms which I can not seem to make work.
what would I need to configure in aplication.properties to use my not so secure security mechanism for the /webhook endpoint?

Right now this implementation is incomplete, but as far as path-based authentication is concerned, you need to have an alias like webhook that you can refer to from the configuration, see https://github.com/quarkusio/quarkus/blob/main/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/BasicAuthenticationMechanism.java#L198 (note, basic is a qualifier) as well the rest of the class on how to implement the custom mechanism. Delegating to the default interface implementation will likely not work

User: Sergey Beryozkin got me on the right Track.
package org.eon.webhookingestor.receiver;
import io.quarkus.arc.Priority;
import io.quarkus.oidc.IdTokenCredential;
import io.quarkus.security.AuthenticationFailedException;
import io.quarkus.security.identity.IdentityProviderManager;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.identity.request.AuthenticationRequest;
import io.quarkus.security.identity.request.TokenAuthenticationRequest;
import io.quarkus.security.runtime.QuarkusPrincipal;
import io.quarkus.security.runtime.QuarkusSecurityIdentity;
import io.quarkus.vertx.http.runtime.security.ChallengeData;
import io.quarkus.vertx.http.runtime.security.HttpAuthenticationMechanism;
import io.quarkus.vertx.http.runtime.security.HttpCredentialTransport;
import io.smallrye.mutiny.Uni;
import io.vertx.ext.web.RoutingContext;
import javax.enterprise.inject.Alternative;
import javax.inject.Singleton;
import java.util.Collections;
import java.util.Set;
#Alternative
#Singleton
#Priority(900)
public class MagicAuthenticationMechanism implements HttpAuthenticationMechanism {
//related docs:
// https://quarkus.io/guides/security#security-architecture
// https://quarkus.io/guides/security-customization
//related so: https://stackoverflow.com/questions/73120309/quarkus-returning-empty-body-response-for-unauthorize
// related on so:
// https://stackoverflow.com/questions/74384603/how-to-configure-a-quarkus-custom-httpauthenticationmechanism
// https://stackoverflow.com/questions/73120309/quarkus-returning-empty-body-response-for-unauthorize
#Override
public Uni<SecurityIdentity> authenticate(RoutingContext context, IdentityProviderManager identityProviderManager) {
String magic_key = "magic_header";
String header_value = context.request().headers().get(magic_key);
String magic_value = "magic_value";
if ( !magic_value.equals(header_value))
{
return Uni.createFrom().failure(new AuthenticationFailedException());
}
return Uni.createFrom().item(
QuarkusSecurityIdentity.builder()
.setPrincipal(new QuarkusPrincipal(magic_key))
.addCredential(new IdTokenCredential(header_value))
.build());
}
#Override
public Uni<ChallengeData> getChallenge(RoutingContext context) {
//https://quarkus.io/guides/security-customization#dealing-with-more-than-one-httpauthenticationmechanism
//If no credentials are provided then the mechanism specific challenge is created
throw new RuntimeException ("no credentials were provided");
}
#Override
public Set<Class<? extends AuthenticationRequest>> getCredentialTypes() {
//needs to inherit from io.quarkus.security.identity.request.AuthenticationRequest
return Collections.singleton(TokenAuthenticationRequest.class);
}
#Override
public Uni<Boolean> sendChallenge(RoutingContext context) {
// a failed authentication will check if a challenge should be sent
return Uni.createFrom().item(Boolean.FALSE);
}
#Override
public Uni<HttpCredentialTransport> getCredentialTransport(RoutingContext context) {
return Uni.createFrom().item(new HttpCredentialTransport(HttpCredentialTransport.Type.OTHER_HEADER, "magic"));
}
#Override
public int getPriority() {
return 900;
}
}
with the configuration:
quarkus.http.auth.permission.magic.paths=/webhook
quarkus.http.auth.permission.magic.policy=authenticated
quarkus.http.auth.permission.magic.auth-mechanism=magic
Results in a magic authentication that works on my machine.
At this point I would advice against implementing and using it since the quarkus authentication is entirely unpredictable to me and maybe you if you read this post.
Excessive Testing of all endpoints is advised.

Related

Sticky session Ribbon rule in Zuul always has null request

I am attempting to implement a sticky session load balancer rule in a Zuul proxy service. I am using the code from this example: https://github.com/alejandro-du/vaadin-microservices-demo/blob/master/proxy-server/src/main/java/com/example/StickySessionRule.java
I seem to have everything configured correctly, and the rule is triggering in my debugger, but the call to RequestContext.getCurrentContext().getResponse() always returns null, so the cookie is never found, so the rule never takes effect.
The rest of the Zuul config is working 100%. My traffic is proxied and routed and I can use the app fine, only the sticky session rule is not working.
Is there another step I am missing to get the request wired in to this rule correctly?
My route config:
zuul.routes.appname.path=/appname/**
zuul.routes.appname.sensitiveHeaders=
zuul.routes.appname.stripPrefix=false
zuul.routes.appname.retryable=true
zuul.add-host-header=true
zuul.routes.appname.service-id=APP_NAME
hystrix.command.APP_NAME.execution.isolation.strategy=THREAD
hystrix.command.APP_NAME.execution.isolation.thread.timeoutInMilliseconds=125000
APP_NAME.ribbon.ServerListRefreshInterval=10000
APP_NAME.ribbon.retryableStatusCodes=500
APP_NAME.ribbon.MaxAutoRetries=5
APP_NAME.ribbon.MaxAutoRetriesNextServer=1
APP_NAME.ribbon.OkToRetryOnAllOperations=true
APP_NAME.ribbon.ReadTimeout=5000
APP_NAME.ribbon.ConnectTimeout=5000
APP_NAME.ribbon.EnablePrimeConnections=true
APP_NAME.ribbon.NFLoadBalancerRuleClassName=my.package.name.StickySessionRule
The app:
#EnableZuulProxy
#SpringBootApplication
public class ApplicationGateway {
public static void main(String[] args) {
SpringApplication.run(ApplicationGateway.class, args);
}
#Bean
public LocationRewriteFilter locationRewriteFilter() {
return new LocationRewriteFilter();
}
}
EDIT: As requested, the code:
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ZoneAvoidanceRule;
import com.netflix.zuul.context.RequestContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
/**
* #author Alejandro Duarte.
*/
public class StickySessionRule extends ZoneAvoidanceRule {
public static final String COOKIE_NAME_SUFFIX = "-" + StickySessionRule.class.getSimpleName();
#Override
public Server choose(Object key) {
Optional<Cookie> cookie = getCookie(key);
if (cookie.isPresent()) {
Cookie hash = cookie.get();
List<Server> servers = getLoadBalancer().getReachableServers();
Optional<Server> server = servers.stream()
.filter(s -> s.isAlive() && s.isReadyToServe())
.filter(s -> hash.getValue().equals("" + s.hashCode()))
.findFirst();
if (server.isPresent()) {
return server.get();
}
}
return useNewServer(key);
}
private Server useNewServer(Object key) {
Server server = super.choose(key);
HttpServletResponse response = RequestContext.getCurrentContext().getResponse();
if (response != null) {
String cookieName = getCookieName(server);
Cookie newCookie = new Cookie(cookieName, "" + server.hashCode());
newCookie.setPath("/");
response.addCookie(newCookie);
}
return server;
}
private Optional<Cookie> getCookie(Object key) {
HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
if (request != null) {
Server server = super.choose(key);
String cookieName = getCookieName(server);
Cookie[] cookies = request.getCookies();
if (cookies != null) {
return Arrays.stream(cookies)
.filter(c -> c.getName().equals(cookieName))
.findFirst();
}
}
return Optional.empty();
}
private String getCookieName(Server server) {
return server.getMetaInfo().getAppName() + COOKIE_NAME_SUFFIX;
}
}
I think you are missing a PreFilter, like this:
import com.netflix.zuul.context.RequestContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
public class PreFilter extends com.netflix.zuul.ZuulFilter {
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
RequestContext.getCurrentContext().set(FilterConstants.LOAD_BALANCER_KEY, ctx.getRequest());
return null;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public int filterOrder() {
return FilterConstants.SEND_RESPONSE_FILTER_ORDER;
}
#Override
public String filterType() {
return "pre";
}
}
Mark as Bean
#Bean
public PreFilter preFilter() {
return new PreFilter();
}
And use it in your rule
#Override
public Server choose(Object key) {
javax.servlet.http.HttpServletRequest request = (javax.servlet.http.HttpServletRequest) key;
RequestContext not working cause "hystrix.command.APP_NAME.execution.isolation.strategy=THREAD"

Authentication issue in Spring Security (checking only username not password?)

this is my first project with Spring and I have just started to create the login with Spring Security. I want some pages to be accessible only for the admin and not for the players. I've found some examples on the web and this mechanism works pretty well, I have this secured page that is protected by the login and it's forbidden when the user has no ROLE_ADMIN.
#PreAuthorize("hasAuthority('ROLE_ADMIN')")
#GetMapping("/secured/all")
public String securedHello() {
return "Secured Hello";
}
The problem is that testing my code I found out that Spring authenticates the admin (and the user as well) only checking the username. If I put the wrong password it allows me to enter anyway. I don't understand how this is possible, shouldn't Spring Security do all the authentication work by itself? I've seen somebody suggested to implement an authentication manager or something like that, but I don't understand why and how to insert it in my code. I'm stuck on this since two days, please any advice wuold be really appreciated.
These are my classes:
package model;
import java.io.IOException;
import javax.naming.AuthenticationException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import com.fasterxml.jackson.databind.ObjectMapper;
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
#EnableJpaRepositories(basePackageClasses = PlayersRepository.class)
#ComponentScan(basePackageClasses= CustomUserDetailsService.class)
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(getPasswordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
//http.csrf().disable();
http.authorizeRequests()
.antMatchers("**/secured/**").access("hasAuthority('ROLE_ADMIN')")
.anyRequest().permitAll()
.and()
.formLogin().permitAll();
}
private PasswordEncoder getPasswordEncoder() {
return new PasswordEncoder() {
#Override
public String encode(CharSequence charSequence) {
return charSequence.toString();
}
#Override
public boolean matches(CharSequence charSequence, String s) {
return true;
}
};
}
}
package model;
import java.util.ArrayList;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private PlayersRepository usersRepository;
#Autowired
private RoleRepository rolesRepository;
public CustomUserDetailsService(PlayersRepository usersRepository, RoleRepository rolesRepository) {
this.usersRepository=usersRepository;
this.rolesRepository=rolesRepository;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<Player> optionalUser = usersRepository.findByUsername(username);
optionalUser
.orElseThrow(() -> new UsernameNotFoundException("Username not found"));
Player user= optionalUser.get();
System.out.println(user);
return toUserDetails(new UserObject(user.getUsername(),user.getPassword(),user.getRole()));
}
private UserDetails toUserDetails(UserObject userObject) {
return User.withUsername(userObject.name)
.password(userObject.password)
.roles(userObject.role).build();
}
private static class UserObject {
private String name;
private String password;
private String role;
public UserObject(String name, String password, String role) {
this.name = name;
this.password = password;
this.role = role;
}
}
}
package model;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class CustomUserDetails extends Player implements UserDetails {
String role;
public CustomUserDetails(final Player user) {
super(user);
}
public CustomUserDetails(Optional<Player> user, String role) {
super(user);
this.role=role;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> list = new ArrayList<GrantedAuthority>();
list.add(new SimpleGrantedAuthority("ROLE_"+ role));
System.out.println(list);
return list;
}
#Override
public String getPassword() {
return super.getPassword();
}
#Override
public String getUsername() {
return super.getUsername();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
}
Shouldn't Spring Security do all the authentication work by itself?
Yes, Spring Security does that for you using an AuthenticationManager.
I've seen somebody suggested to implement an authentication manager or something like that, but I don't understand why and how to insert it in my code.
You actually already have an AuthenticationManager, since you built one within the configure() method:
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(getPasswordEncoder());
}
So, what is exactly the reason this isn't working you may ask. Well, the AuthenticationManager you provided contains two parts:
A part that fetches the user information (CustomUserDetailsService)
Another part that checks the password (the getPasswordEncoder()).
What happens behind the screens is that Spring calls your CustomUserDetailsService to fetch your user information, including your (hashed) password. After fetching that information, it calls your PasswordEncoder.matches() function to verify if the raw entered password matches your hashed password provided by the CustomUserDetailsService.
In your case, your PasswordEncoder.matches() function looks like this:
#Override
public boolean matches(CharSequence charSequence, String s) {
return true;
}
This means that regardless of what password you provide, it will return true. This is exactly what you're experiencing since any password will work.
So, how do you solve this? Well, your PasswordEncoder should actually hash your raw password and compare it to the hashed password that is being passed, for example:
#Override
public boolean matches(CharSequence rawPassword, String hashedPassword) {
String hashedPassword2 = null; // hash your rawPassword here
return hashedPassword2.equals(hashedPassword);
}
The implementation of this method depends on how you store your password in your database. Spring Security already comes with a few implementation including BcryptPasswordEncoder, StandardPasswordEncoder, MessageDigestPasswordEncoder, ... . Some of these implementations are deprecated, mostly to indicate that the hashing mechanisms used by those encoders are considered unsafe. There are no plans at the moment of writing to remove those encoders, as mentioned by the Javadoc:
Digest based password encoding is not considered secure. Instead use an adaptive one way function like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or SCryptPasswordEncoder. Even better use DelegatingPasswordEncoder which supports password upgrades. There are no plans to remove this support. It is deprecated to indicate that this is a legacy implementation and using it is considered insecure.
(Emphasis is my own)
If you are free to choose which implementation you pick, then Spring recommends using BCryptPasswordEncoder as mentioned by the Javadoc:
Service interface for encoding passwords. The preferred implementation is BCryptPasswordEncoder.
I just had a Quick scan I found this
private PasswordEncoder getPasswordEncoder() {
return new PasswordEncoder() {
#Override
public String encode(CharSequence charSequence) {
return charSequence.toString();
}
#Override
public boolean matches(CharSequence charSequence, String s) {
return true;
}
};
}
In your matches you are returning always true.
I guess here you should put logic for checking password for equality something like this
#Override
public boolean matches(CharSequence charSequence, String s) {
return charSequence.toString.equals(s);
}
I would suggest you use something like this
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

Is there a way to automatically propagate an incoming HTTP header in a JAX-RS request to an outgoing JAX-RS request?

I'm looking for the proper way—in a Jersey application—to read a header from an incoming request and automatically install it in any outgoing requests that might be made by a JAX-RS client that my application is using.
Ideally I'd like to do this without polluting any of my classes' inner logic at all, so via various filters and interceptors.
For simple use cases, I can do this: I have a ClientRequestFilter implementation that I register on my ClientBuilder, and that filter implementation has:
#Context
private HttpHeaders headers;
...which is a context-sensitive proxy (by definition), so in its filter method it can refer to headers that were present on the inbound request that's driving all this, and install them on the outgoing request. For straightforward cases, this appears to work OK.
However, this fails in the case of asynchronicity: if I use the JAX-RS asynchronous client APIs to spawn a bunch of GETs, the filter is still invoked, but can no longer invoke methods on that headers instance variable; Jersey complains that as far as it knows we're no longer in request scope. This makes sense if request scope is defined to be per-thread: the spawned GETs are running in some Jersey-managed thread pool somewhere, not on the same thread as the one with which the headers proxy is associated, so that proxy throws IllegalStateExceptions all over the place when my filter tries to talk to it.
I feel like there's some combination of ContainerRequestFilter and ClientRequestFilter that should be able to get the job done even in asynchronous cases, but I'm not seeing it.
What I would do is make a WebTarget injectable that is preconfigured with a ClientRequestFilter to add the headers. It's better to configure the WebTarget this way, as opposed to the Client, since the Client is an expensive object to create.
We can make the WebTarget injectable using a custom annotation and an InjectionResolver. In the InjectionResolver, we can get the ContainerRequest and get the headers from that, which we will pass to the ClientRequestFilter.
Here it is in action
Create the custom annotation
#Target({ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface WithHeadersTarget {
String baseUri();
String[] headerNames() default {};
}
Make the InjectionResolver with the custom ClientRequestFilter
private static class WithHeadersTargetInjectionResolver
implements InjectionResolver<WithHeadersTarget> {
private final Provider<ContainerRequest> requestProvider;
private final Client client;
#Inject
public WithHeadersTargetInjectionResolver(Provider<ContainerRequest> requestProvider) {
this.requestProvider = requestProvider;
this.client = ClientBuilder.newClient();
}
#Override
public Object resolve(Injectee injectee, ServiceHandle<?> handle) {
if (injectee.getRequiredType() == WebTarget.class
&& injectee.getParent().isAnnotationPresent(WithHeadersTarget.class)) {
WithHeadersTarget anno = injectee.getParent().getAnnotation(WithHeadersTarget.class);
String uri = anno.baseUri();
String[] headersNames = anno.headerNames();
MultivaluedMap<String, String> requestHeaders = requestProvider.get().getRequestHeaders();
return client.target(uri)
.register(new HeadersFilter(requestHeaders, headersNames));
}
return null;
}
#Override
public boolean isConstructorParameterIndicator() {
return false;
}
#Override
public boolean isMethodParameterIndicator() {
return false;
}
private class HeadersFilter implements ClientRequestFilter {
private final MultivaluedMap<String, String> headers;
private final String[] headerNames;
private HeadersFilter(MultivaluedMap<String, String> headers, String[] headerNames) {
this.headers = headers;
this.headerNames = headerNames;
}
#Override
public void filter(ClientRequestContext requestContext) throws IOException {
// if headers names is empty, add all headers
if (this.headerNames.length == 0) {
for (Map.Entry<String, List<String>> entry: this.headers.entrySet()) {
requestContext.getHeaders().put(entry.getKey(), new ArrayList<>(entry.getValue()));
}
// else just add the headers from the annotation
} else {
for (String header: this.headerNames) {
requestContext.getHeaders().put(header, new ArrayList<>(this.headers.get(header)));
}
}
}
}
}
One thing about this implementation is that it checks for an empty headerNames in the #WithHeadersTarget annotation. If it is empty, then we just forward all headers. If the user specifies some header names, then it will only forward those
Register the InjectionResolver
new ResourceConfig()
.register(new AbstractBinder() {
#Override
protected void configure() {
bind(WithHeadersTargetInjectionResolver.class)
.to(new TypeLiteral<InjectionResolver<WithHeadersTarget>>() {
}).in(Singleton.class);
}
})
Use it
#Path("test")
public static class TestResource {
#WithHeadersTarget(
baseUri = BASE_URI
headerNames = {TEST_HEADER_NAME})
private WebTarget target;
#GET
public String get() {
return target.path("client").request().get(String.class);
}
}
In this example if, the headerNames is left out, then it will default to an empty array, which will cause all the request headers to be forwarded.
Complete test using Jersey Test Framework
import org.glassfish.hk2.api.Injectee;
import org.glassfish.hk2.api.InjectionResolver;
import org.glassfish.hk2.api.ServiceHandle;
import org.glassfish.hk2.api.TypeLiteral;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.filter.LoggingFilter;
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.Path;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.ClientRequestContext;
import javax.ws.rs.client.ClientRequestFilter;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import static org.assertj.core.api.Assertions.assertThat;
public class ForwardHeadersTest extends JerseyTest {
private static final String BASE_URI = "http://localhost:8000";
private static final String TEST_HEADER_NAME = "X-Test-Header";
#Target({ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface WithHeadersTarget {
String baseUri();
String[] headerNames() default {};
}
#Path("test")
public static class TestResource {
#WithHeadersTarget(
baseUri = BASE_URI
)
private WebTarget target;
#GET
public String get() {
return target.path("client").request().get(String.class);
}
}
#Path("client")
public static class ClientResource {
#GET
public String getReversedHeader(#HeaderParam(TEST_HEADER_NAME) String header) {
System.out.println(header);
return new StringBuilder(header).reverse().toString();
}
}
private static class WithHeadersTargetInjectionResolver
implements InjectionResolver<WithHeadersTarget> {
private final Provider<ContainerRequest> requestProvider;
private final Client client;
#Inject
public WithHeadersTargetInjectionResolver(Provider<ContainerRequest> requestProvider) {
this.requestProvider = requestProvider;
this.client = ClientBuilder.newClient();
}
#Override
public Object resolve(Injectee injectee, ServiceHandle<?> handle) {
if (injectee.getRequiredType() == WebTarget.class
&& injectee.getParent().isAnnotationPresent(WithHeadersTarget.class)) {
WithHeadersTarget anno = injectee.getParent().getAnnotation(WithHeadersTarget.class);
String uri = anno.baseUri();
String[] headersNames = anno.headerNames();
MultivaluedMap<String, String> requestHeaders = requestProvider.get().getRequestHeaders();
return client.target(uri)
.register(new HeadersFilter(requestHeaders, headersNames));
}
return null;
}
#Override
public boolean isConstructorParameterIndicator() {
return false;
}
#Override
public boolean isMethodParameterIndicator() {
return false;
}
private class HeadersFilter implements ClientRequestFilter {
private final MultivaluedMap<String, String> headers;
private final String[] headerNames;
private HeadersFilter(MultivaluedMap<String, String> headers, String[] headerNames) {
this.headers = headers;
this.headerNames = headerNames;
}
#Override
public void filter(ClientRequestContext requestContext) throws IOException {
// if headers names is empty, add all headers
if (this.headerNames.length == 0) {
for (Map.Entry<String, List<String>> entry: this.headers.entrySet()) {
requestContext.getHeaders().put(entry.getKey(), new ArrayList<>(entry.getValue()));
}
// else just add the headers from the annotation
} else {
for (String header: this.headerNames) {
requestContext.getHeaders().put(header, new ArrayList<>(this.headers.get(header)));
}
}
}
}
}
#Override
public ResourceConfig configure() {
return new ResourceConfig()
.register(TestResource.class)
.register(ClientResource.class)
.register(new AbstractBinder() {
#Override
protected void configure() {
bind(WithHeadersTargetInjectionResolver.class)
.to(new TypeLiteral<InjectionResolver<WithHeadersTarget>>() {
}).in(Singleton.class);
}
})
.register(new LoggingFilter(Logger.getAnonymousLogger(), true))
.register(new ExceptionMapper<Throwable>() {
#Override
public Response toResponse(Throwable t) {
t.printStackTrace();
return Response.serverError().entity(t.getMessage()).build();
}
});
}
#Override
public URI getBaseUri() {
return URI.create(BASE_URI);
}
#Test
public void testIt() {
final String response = target("test")
.request()
.header(TEST_HEADER_NAME, "HelloWorld")
.get(String.class);
assertThat(response).isEqualTo("dlroWolleH");
}
}

Use pivotal cloud foundry redis and rabbitmq service using as vcap service in spring boot application

I am able to use REDIS and RABBITMQ services which is on pivotal.While binding services I am able to get the Credentials and using that credentials in my application.properties for spring boot project.
But this configuration that I am using is hard-coded in application.Properties
To Make this configuration dynamically I came to know that we can use vcap services provided by pivotal.
So want to use run-time credentials for redis and rabbimq.
My Code is below for reference.
application.propeties
rabbitmq.host=hostname
rabbitmq.virtual-host=vhostanme
rabbitmq.username=username
rabbitmq.password=password
rabbit.mainqueue=abhi
rabbit.errorqueue=abc
redis.host=redishostname
redis.port=port
redis.password=password
My Config class:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
#Component
public class Config {
static String rabbitMqHost;
static String rabbitMqVHost;
static String rabbitMqUsername;
static String rabbitMqPassword;
static String rabbitMqMainQueue;
static String rabbitMqErrorQueue;
static String redisHost;
static int redisPort;
static String redisPassword;
Config() {
}
public static String getRedisHost() {
return redisHost;
}
public static void setRedisHost(String redisHost) {
Config.redisHost = redisHost;
}
public static int getRedisPort() {
return redisPort;
}
public static void setRedisPort(int redisPort) {
Config.redisPort = redisPort;
}
public static String getRedisPassword() {
return redisPassword;
}
public static void setRedisPassword(String redisPassword) {
Config.redisPassword = redisPassword;
}
public static String getRabbitMqMainQueue() {
return rabbitMqMainQueue;
}
public static void setRabbitMqMainQueue(String rabbitMqMainQueue) {
Config.rabbitMqMainQueue = rabbitMqMainQueue;
}
public static String getRabbitMqErrorQueue() {
return rabbitMqErrorQueue;
}
public static void setRabbitMqErrorQueue(String rabbitMqErrorQueue) {
Config.rabbitMqErrorQueue = rabbitMqErrorQueue;
}
public static String getRabbitMqHost() {
return rabbitMqHost;
}
public static void setRabbitMqHost(String rabbitMqHost) {
Config.rabbitMqHost = rabbitMqHost;
}
public static String getRabbitMqVHost() {
return rabbitMqVHost;
}
public static void setRabbitMqVHost(String rabbitMqVHost) {
Config.rabbitMqVHost = rabbitMqVHost;
}
public static String getRabbitMqUsername() {
return rabbitMqUsername;
}
public static void setRabbitMqUsername(String rabbitMqUsername) {
Config.rabbitMqUsername = rabbitMqUsername;
}
public static String getRabbitMqPassword() {
return rabbitMqPassword;
}
public static void setRabbitMqPassword(String rabbitMqPassword) {
Config.rabbitMqPassword = rabbitMqPassword;
}
#Value("${rabbitmq.host}")
public void setRabbitMqHosts(String url) {
setRabbitMqHost(url);
}
#Value("${rabbitmq.virtual-host}")
public void setRabbitMqVHosts(String url) {
setRabbitMqVHost(url);
}
#Value("${rabbitmq.username}")
public void setRabbitUsernames(String url) {
setRabbitMqUsername(url);
}
#Value("${rabbitmq.password}")
public void setRabbitPasswords(String url) {
setRabbitMqPassword(url);
}
#Value("${rabbit.mainqueue}")
public void setRabbitMainQueues(String url) {
setRabbitMqMainQueue(url);
}
#Value("${rabbit.errorqueue}")
public void setRabbitErrorQueues(String url) {
setRabbitMqErrorQueue(url);
}
#Value("${redis.host}")
public void setRedisHosts(String url) {
setRedisHost(url);
}
#Value("${redis.port}")
public void setRedisPorts(int url) {
setRedisPort(url);
}
#Value("${redis.password}")
public void setRedisPasswords(String url) {
setRedisPassword(url);
}
}
My MessagesConsumer class in which I am using that Conguration to take message from rabbitmq jms queue and save to redis:
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.jms.ConnectionFactory;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.Session;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import com.es.consumer.config.Config;
import com.rabbitmq.jms.admin.RMQConnectionFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisShardInfo;
#Component
public class MessagesConsumer {
#Autowired
JmsTemplate jmsTemplate;
final Logger logger = LoggerFactory.getLogger(MessagesConsumer.class);
Jedis jedis;
JedisShardInfo shardInfo;
#PostConstruct
public void init() {
shardInfo = new JedisShardInfo(Config.getRedisHost(), Config.getRedisPort());
shardInfo.setPassword(Config.getRedisPassword());
jedis = new Jedis(shardInfo);
jedis.connect();
jedis.select(2);
}
#Bean
ConnectionFactory connectionFactory() {
RMQConnectionFactory connectionFactory = new RMQConnectionFactory();
connectionFactory.setUsername(Config.getRabbitMqUsername());
connectionFactory.setPassword(Config.getRabbitMqPassword());
connectionFactory.setVirtualHost(Config.getRabbitMqVHost());
connectionFactory.setHost(Config.getRabbitMqHost());
return connectionFactory;
}
#SuppressWarnings("rawtypes")
#Scheduled(fixedRate = 1)
public void readQueueAndSaveData() {
// take message process it and save to redis as hmset
}}
Any Help would be appreciable.
There are two ways of doing this.
1) Remove all the properties from application.properties, and write a configuration bean that will create RedisTemplate and RabbitTemplate beans for you. The factory properties required for these are to be obtained from the VCAP_SERVICES. On CF, the VCAP_SERVICES env variable, will have the services information that are binded to the app. When u push the app, with redis, rabbit service binded into your space, then their properties are available in VCAP_SERVICES. So just do System.getEnv("VCAP_SERVICES") in your code and then parse the json to get the service details to create the templates.
2) Remove the properties from application.properties, and use spring cloud connector. Spring cloud has a subproject called Spring cloud connectors that provide utilities to connect to various cloud services.
http://cloud.spring.io/spring-cloud-connectors/spring-cloud-spring-service-connector.html#_rabbitmq
Just you need to define a class that extends AbstractCloudConfig as below
class CloudConfig extends AbstractCloudConfig {
#Bean
public RabbitConnectionFactory rabbitFactory() {
return connectionFactory().rabbitConnectionFactory("rabbit-servicename");
}
#Bean
public RedisConnectionFactory redisFactory() {
return connectionFactory().redisConnectionFactory("redis-servicename");
}
}
The second approach is prefered if you are using Spring, as this needs very less coding and can be switched, to different cloud providers with no much effort.

How to set jersey in spring boot 1.4.1-RELEASE to convert dates to ISO 8601?

In Spring boot 1.3.6-RELEASE I had the below class registered to jersey. Every java.util.Date field would be read and returned as ISO8601 format. However, when updating to 1.4.1-RELEASE it now sometimes works and sometimes doesn't. What's the new proper way to enable this?
package com.mypackage;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.text.ParseException;
import java.text.ParsePosition;
import java.util.Date;
import javax.ws.rs.ext.ParamConverter;
import javax.ws.rs.ext.ParamConverterProvider;
import javax.ws.rs.ext.Provider;
import com.fasterxml.jackson.databind.util.ISO8601Utils;
#Provider
public class DateTimeParamConverterProvider implements ParamConverterProvider {
#SuppressWarnings("unchecked")
#Override
public <T> ParamConverter<T> getConverter(Class<T> clazz, Type type, Annotation[] annotations) {
if (type.equals(Date.class)) {
return (ParamConverter<T>) new DateTimeParamConverter();
} else {
return null;
}
}
static class DateTimeParamConverter implements ParamConverter<Date> {
#Override
public java.util.Date fromString(String value) {
if (value == null) {
return null;
}
try {
return ISO8601Utils.parse(value, new ParsePosition(0));
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
#Override
public String toString(Date value) {
return ISO8601Utils.format(value);
}
}
}
I register this provider like this:
#Component
#ApplicationPath("/")
public class JerseyConfiguration extends ResourceConfig {
private static final Logger log = Logger.getLogger(JerseyConfiguration.class.getName());
#Autowired
public JerseyConfiguration(LogRequestFilter lrf) {
register(new ObjectMapperContextResolverNonNull());
register(RestServiceImpl.class);
property(ServletProperties.FILTER_FORWARD_ON_404, true);
register(DateTimeParamConverterProvider.class, 6000);
...
Just define this in your application.properties:
spring.jackson.date-format=com.fasterxml.jackson.databind.util.ISO8601DateFormat

Resources