Embed object instead of collection in Spring HATEOAS - spring

A very quick question ,to which there seems to be no easy answer.
Is it possible to put an object directly under the embedded resources using Spring HATEOAS? The desired output format in JSON should look like
{
...
_embedded: {
myObject: {
...
}
}
}
Using the code below, I always end up with a colletion for any resource I want to embed.
ArrayList<Resource<?>> embeddedContent = new ArrayList<>();
Resource<MyObject> myObjectResource = new Resource<MyObject>(new MyObject());
embeddedContent.add(myObjectResource );
Resources<Resource<?>> embeddedResources = new Resources<Resource<?>>(embeddedContent);
The embeddedResources are then put on a class, which is later mapped to a resource as well.
But for some reason, even though I'm not adding a collection to the embedded resources, the output still shows the myObject embedded resource as an array:
{
...
_embedded: {
myObject: [
{
...
}
]
}
}

The parameter enforceEmbeddedCollections in this constructor allow represent embedded arrays like a object.
public HalHandlerInstantiator(RelProvider resolver, CurieProvider curieProvider, boolean enforceEmbeddedCollections) {}
So, you should set HalHandlerInstantiator with value false.
There is a small example:
ObjectMapper halObjectMapper = new ObjectMapper();
halObjectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
halObjectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
halObjectMapper
.setHandlerInstantiator(new Jackson2HalModule.
HalHandlerInstantiator(new DefaultRelProvider(), null, false));
Jackson2HalModule jackson2HalModule = new Jackson2HalModule();
halObjectMapper.registerModule(jackson2HalModule);
try {
halObjectMapper.writeValueAsString(new Resources<Album>(Arrays.asList(new Album("1", "title", "1", 1))));
} catch (JsonProcessingException e) {
e.printStackTrace();
}

Add the following snippet to one of your #Configuration classes. The code here looks similar to what can be found in org.springframework.hateoas.config.HypermediaSupportBeanDefinitionRegistrar. We're basically overwriting the HalHandlerInstantiator in the HAL-ObjectMapper where we pass false to the enforceEmbeddedCollections argument. This is a dirty hack, but currently there's no way to configure this aspect of the spring-hateoas machinery.
#Bean
BeanPostProcessor halModuleReconfigurer(BeanFactory beanFactory) {
return new BeanPostProcessor() {
#Override
public Object postProcessBeforeInitialization(final Object bean, final String beanName) throws BeansException {
return bean;
}
#Override
public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
if (bean instanceof RequestMappingHandlerAdapter) {
RequestMappingHandlerAdapter adapter = (RequestMappingHandlerAdapter) bean;
adapter.setMessageConverters(reconfigureObjectMapper(adapter.getMessageConverters()));
}
if (bean instanceof AnnotationMethodHandlerAdapter) {
AnnotationMethodHandlerAdapter adapter = (AnnotationMethodHandlerAdapter) bean;
List<HttpMessageConverter<?>> augmentedConverters = reconfigureObjectMapper(Arrays.asList(adapter
.getMessageConverters()));
adapter
.setMessageConverters(augmentedConverters.toArray(new HttpMessageConverter<?>[augmentedConverters.size()]));
}
if (bean instanceof RestTemplate) {
RestTemplate template = (RestTemplate) bean;
template.setMessageConverters(reconfigureObjectMapper(template.getMessageConverters()));
}
return bean;
}
private List<HttpMessageConverter<?>> reconfigureObjectMapper(final List<HttpMessageConverter<?>> converters) {
for (HttpMessageConverter<?> converter : converters) {
if (converter instanceof MappingJackson2HttpMessageConverter) {
MappingJackson2HttpMessageConverter halConverterCandidate = (MappingJackson2HttpMessageConverter) converter;
ObjectMapper objectMapper = halConverterCandidate.getObjectMapper();
if (Jackson2HalModule.isAlreadyRegisteredIn(objectMapper)) {
final CurieProvider curieProvider = Try.of(() -> beanFactory.getBean(CurieProvider.class)).getOrElse((CurieProvider) null);
final RelProvider relProvider = beanFactory.getBean("_relProvider", RelProvider.class);
final MessageSourceAccessor linkRelationMessageSource = beanFactory.getBean("linkRelationMessageSource", MessageSourceAccessor.class);
objectMapper.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(relProvider, curieProvider, linkRelationMessageSource, false));
}
}
}
return converters;
}
};
}

Related

JwtAuthenticationToken is not in the allowlist, Jackson issue

I have created my authorization server using org.springframework.security:spring-security-oauth2-authorization-server:0.2.2 and my client using org.springframework.boot:spring-boot-starter-oauth2-client. The users are able to sign in and out successfully, however, while testing I noticed that if I log in successfully then restart the client (but not the server) without signing out and try to login in again the server throws the following error in an endless loop of redirects
java.lang.IllegalArgumentException: The class with org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken and name of org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken is not in the allowlist. If you believe this class is safe to deserialize, please provide an explicit mapping using Jackson annotations or by providing a Mixin. If the serialization is only done by a trusted source, you can also enable default typing. See https://github.com/spring-projects/spring-security/issues/4370 for details
I tried to follow this link https://github.com/spring-projects/spring-security/issues/4370 but the solution on it did not work for me. I also tried a different solution described in this link https://github.com/spring-projects/spring-authorization-server/issues/397#issuecomment-900148920 and modified my authorization server code as follows:-
Here is my Jackson Configs
#Configuration
public class JacksonConfiguration {
/**
* Support for Java date and time API.
*
* #return the corresponding Jackson module.
*/
#Bean
public JavaTimeModule javaTimeModule() {
return new JavaTimeModule();
}
#Bean
public Jdk8Module jdk8TimeModule() {
return new Jdk8Module();
}
/*
* Support for Hibernate types in Jackson.
*/
#Bean
public Hibernate5Module hibernate5Module() {
return new Hibernate5Module();
}
/*
* Module for serialization/deserialization of RFC7807 Problem.
*/
#Bean
public ProblemModule problemModule() {
return new ProblemModule();
}
/*
* Module for serialization/deserialization of ConstraintViolationProblem.
*/
#Bean
public ConstraintViolationProblemModule constraintViolationProblemModule() {
return new ConstraintViolationProblemModule();
}
/**
* To (de)serialize a BadCredentialsException, use CoreJackson2Module:
*/
#Bean
public CoreJackson2Module coreJackson2Module() {
return new CoreJackson2Module();
}
#Bean
#Primary
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(coreJackson2Module());
mapper.registerModule(javaTimeModule());
mapper.registerModule(jdk8TimeModule());
mapper.registerModule(hibernate5Module());
mapper.registerModule(problemModule());
mapper.registerModule(constraintViolationProblemModule());
return mapper;
}
}
and here is my Authorization server config
#Configuration(proxyBeanMethods = false)
public class AuthServerConfig {
private final DataSource dataSource;
private final AuthProperties authProps;
private final PasswordEncoder encoder;
public AuthServerConfig(DataSource dataSource, AuthProperties authProps, PasswordEncoder encoder) {
this.dataSource = dataSource;
this.authProps = authProps;
this.encoder = encoder;
}
#Bean
public JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource);
}
#Bean
#Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
authorizationServerConfigurer.tokenRevocationEndpoint(tokenRevocationEndpoint -> tokenRevocationEndpoint
.revocationResponseHandler((request, response, authentication) -> {
Assert.notNull(request, "HttpServletRequest required");
HttpSession session = request.getSession(false);
if (!Objects.isNull(session)) {
session.removeAttribute("SPRING_SECURITY_CONTEXT");
session.invalidate();
}
SecurityContextHolder.getContext().setAuthentication(null);
SecurityContextHolder.clearContext();
response.setStatus(HttpStatus.OK.value());
})
);
RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
http
.requestMatcher(endpointsMatcher)
.authorizeRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated())
.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
.apply(authorizationServerConfigurer);
return http.formLogin(Customizer.withDefaults()).build();
}
#Bean
public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate, TokenSettings tokenSettings) {
JdbcRegisteredClientRepository clientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
RegisteredClient webClient = RegisteredClient.withId("98a9104c-a9c7-4d7c-ad03-ec61bcfeab36")
.clientId(authProps.getClientId())
.clientName(authProps.getClientName())
.clientSecret(encoder.encode(authProps.getClientSecret()))
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.redirectUri("http://127.0.0.1:8000/login/oauth2/code/web-client")
.scope(OidcScopes.OPENID)
.scope(OidcScopes.PROFILE)
.tokenSettings(tokenSettings)
.build();
clientRepository.save(webClient);
return clientRepository;
}
#Bean
public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate,
RegisteredClientRepository registeredClientRepository,
ObjectMapper objectMapper) {
JdbcOAuth2AuthorizationService authorizationService =
new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper rowMapper = new JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper(registeredClientRepository);
ClassLoader classLoader = JdbcOAuth2AuthorizationService.class.getClassLoader();
objectMapper.registerModules(SecurityJackson2Modules.getModules(classLoader));
objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
// You will need to write the Mixin for your class so Jackson can marshall it.
// objectMapper.addMixIn(UserPrincipal .class, UserPrincipalMixin.class);
rowMapper.setObjectMapper(objectMapper);
authorizationService.setAuthorizationRowMapper(rowMapper);
return authorizationService;
}
#Bean
public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate,
RegisteredClientRepository registeredClientRepository) {
return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
}
#Bean
public JWKSource<SecurityContext> jwkSource() {
RSAKey rsaKey = generateRsa();
JWKSet jwkSet = new JWKSet(rsaKey);
return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}
private static RSAKey generateRsa() {
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
return new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
}
private static KeyPair generateRsaKey() {
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
#Bean
public ProviderSettings providerSettings() {
return ProviderSettings.builder()
.issuer(authProps.getIssuerUri())
.build();
}
#Bean
public TokenSettings tokenSettings() {
return TokenSettings.builder()
.accessTokenTimeToLive(Duration.ofDays(1))
.refreshTokenTimeToLive(Duration.ofDays(1))
.build();
}
}
But am still facing the same issue.
How do I solve this? Any assistance is highly appreciated.
After trying out different solutions this was how I was able to solve it.
I changed my OAuth2AuthorizationService bean to look like this.
#Bean
public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate,
RegisteredClientRepository registeredClientRepository) {
JdbcOAuth2AuthorizationService authorizationService =
new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper rowMapper =
new JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper(registeredClientRepository);
JdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper oAuth2AuthorizationParametersMapper =
new JdbcOAuth2AuthorizationService.OAuth2AuthorizationParametersMapper();
ObjectMapper objectMapper = new ObjectMapper();
ClassLoader classLoader = JdbcOAuth2AuthorizationService.class.getClassLoader();
List<Module> securityModules = SecurityJackson2Modules.getModules(classLoader);
objectMapper.registerModules(securityModules);
objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
objectMapper.addMixIn(JwtAuthenticationToken.class, JwtAuthenticationTokenMixin.class);
rowMapper.setObjectMapper(objectMapper);
oAuth2AuthorizationParametersMapper.setObjectMapper(objectMapper);
authorizationService.setAuthorizationRowMapper(rowMapper);
authorizationService.setAuthorizationParametersMapper(oAuth2AuthorizationParametersMapper);
return authorizationService;
}
and here is my JwtAuthenticationTokenMixin configurations
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
#JsonDeserialize(using = JwtAuthenticationTokenDeserializer.class)
#JsonAutoDetect(
fieldVisibility = JsonAutoDetect.Visibility.ANY,
getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
#JsonIgnoreProperties(ignoreUnknown = true)
public abstract class JwtAuthenticationTokenMixin {}
class JwtAuthenticationTokenDeserializer extends JsonDeserializer<JwtAuthenticationToken> {
#Override
public JwtAuthenticationToken deserialize(JsonParser parser, DeserializationContext context) throws IOException {
ObjectMapper mapper = (ObjectMapper) parser.getCodec();
JsonNode root = mapper.readTree(parser);
return deserialize(parser, mapper, root);
}
private JwtAuthenticationToken deserialize(JsonParser parser, ObjectMapper mapper, JsonNode root)
throws JsonParseException {
JsonNode principal = JsonNodeUtils.findObjectNode(root, "principal");
if (!Objects.isNull(principal)) {
String tokenValue = principal.get("tokenValue").textValue();
long issuedAt = principal.get("issuedAt").longValue();
long expiresAt = principal.get("expiresAt").longValue();
Map<String, Object> headers = JsonNodeUtils.findValue(
principal, "headers", JsonNodeUtils.STRING_OBJECT_MAP, mapper);
Map<String, Object> claims = new HashMap<>();
claims.put("claims", principal.get("claims"));
Jwt jwt = new Jwt(tokenValue, Instant.ofEpochMilli(issuedAt), Instant.ofEpochMilli(expiresAt), headers, claims);
return new JwtAuthenticationToken(jwt);
}
return null;
}
}
abstract class JsonNodeUtils {
static final TypeReference<Set<String>> STRING_SET = new TypeReference<Set<String>>() {
};
static final TypeReference<Map<String, Object>> STRING_OBJECT_MAP = new TypeReference<Map<String, Object>>() {
};
static String findStringValue(JsonNode jsonNode, String fieldName) {
if (jsonNode == null) {
return null;
}
JsonNode value = jsonNode.findValue(fieldName);
return (value != null && value.isTextual()) ? value.asText() : null;
}
static <T> T findValue(JsonNode jsonNode, String fieldName, TypeReference<T> valueTypeReference,
ObjectMapper mapper) {
if (jsonNode == null) {
return null;
}
JsonNode value = jsonNode.findValue(fieldName);
return (value != null && value.isContainerNode()) ? mapper.convertValue(value, valueTypeReference) : null;
}
static JsonNode findObjectNode(JsonNode jsonNode, String fieldName) {
if (jsonNode == null) {
return null;
}
JsonNode value = jsonNode.findValue(fieldName);
return (value != null && value.isObject()) ? value : null;
}
}
you don't need to create a Mixin, because it's all ready created by authorization springboot module. juste
#Bean
public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
JdbcOAuth2AuthorizationService authorizationService = new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper rowMapper = new JdbcOAuth2AuthorizationService.OAuth2AuthorizationRowMapper(registeredClientRepository);
ClassLoader classLoader = JdbcOAuth2AuthorizationService.class.getClassLoader();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModules(new CoreJackson2Module());
objectMapper.registerModules(SecurityJackson2Modules.getModules(classLoader));
objectMapper.registerModule(new OAuth2AuthorizationServerJackson2Module());
rowMapper.setObjectMapper(objectMapper);
authorizationService.setAuthorizationRowMapper(rowMapper);
return authorizationService;
}
i think you miss this line and is where the token mixin is registered
objectMapper.registerModules(new CoreJackson2Module());

How to implement multitenancy for redis in spring boot

I am working on making my whole application multi-tenanted but stuck on redis. So far I created a map of JedisConnectionFactory and tried to pass it to RedisTemplate but it throwing java.lang.IllegalArgumentException: template not initialized; call afterPropertiesSet() before using it.
Below are code snippets:
#Component
public class RedisConfiguration {
#Autowired
private DSConfig dsConfig;
private Map<String,JedisConnectionFactory> jedisConnectionFactoryMap = new HashMap<>();
private static Logger LOGGER = LoggerFactory.getLogger(RedisConfiguration.class);
#PostConstruct
public void initializeJedisConnectionFactories() {
for(DatasourceDetail datasourceDetail : dsConfig.getDatasources()) {
RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration();
redisConfig.setHostName(datasourceDetail.getRedisHost());
redisConfig.setPassword(RedisPassword.of(datasourceDetail.getRedisPassword()));
redisConfig.setPort(Integer.parseInt(datasourceDetail.getRedisPort()));
JedisClientConfiguration configuration = JedisClientConfiguration.builder().usePooling().
poolConfig(new JedisPoolConfig()).build();
jedisConnectionFactoryMap.put(datasourceDetail.getTenantId()
,new JedisConnectionFactory(redisConfig,configuration));
}
LOGGER.info("Connection factory count " + jedisConnectionFactoryMap.size());
}
public RedisTemplate< String, Object > redisTemplate() throws Exception {
final RedisTemplate< String, Object > template = new RedisTemplate< String, Object >();
template.setConnectionFactory( jedisConnectionFactory() );
template.setKeySerializer( new StringRedisSerializer() );
template.setHashValueSerializer( new GenericToStringSerializer< Object >( Object.class ) );
template.setValueSerializer( new GenericToStringSerializer< Object >( Object.class ) );
return template;
}
JedisConnectionFactory jedisConnectionFactory() throws Exception {
if(TenantContext.getCurrentTenant()==null) {
throw new Exception("No tenant context found");
}
LOGGER.info("Returning redis connection for tenant" + TenantContext.getCurrentTenant());
return jedisConnectionFactoryMap.get(TenantContext.getCurrentTenant());
}
}
And I am using redis template as below:
#CrossOrigin("*")
#RestController
#RequestMapping("/redis")
public class RedisController {
#Autowired
private RedisConfiguration redisConfiguration;
#GetMapping("/set")
#ResponseBody
public Object set(#RequestParam(value = "key", required = true) String key,
#RequestParam(value = "value", required = true) String value) throws Exception {
redisConfiguration.redisTemplate().opsForValue().set( key,value );
return true;
}
#GetMapping("/get")
#ResponseBody
public Object get(#RequestParam(value = "key", required = true) String key) throws Exception {
redisConfiguration.redisTemplate().opsForValue().get(key);
return true;
}
}
Is there anyway I can implement this in a better way or is there any other way which spring redis provides?
Finally I was able to resolve by calling afterPropertiesSet() explicitly.

How to configure the default ObjectMapper used by Spring to use a custom deserializer when deserializing spring classes

I want to use my own custom deserializer in Spring's default ObjectMapper whenever I have a class of type OAuth2AccessToken. The interface is annotated with
JsonDeserialize(using = OAuth2AccessTokenJackson2Deserializer.class)
and this is what it's using at the moment to deserialize but I want to use my own.
So far I have created my own custom deserializer
public class MyCustomDeserializer extends StdDeserializer<OAuth2AccessToken> {
public MyCustomDeserializer() {
super(OAuth2AccessToken.class);
}
#Override
public OAuth2AccessToken deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String tokenValue = null;
String tokenType = null;
String refreshToken = null;
Long expiresIn = null;
Set<String> scope = null;
Map<String, Object> additionalInformation = new LinkedHashMap<String, Object>();
while (jp.nextToken() != JsonToken.END_OBJECT) {
String name = jp.getCurrentName();
jp.nextToken();
if (OAuth2AccessToken.ACCESS_TOKEN.equals(name)) {
tokenValue = jp.getText();
} else if (OAuth2AccessToken.TOKEN_TYPE.equals(name)) {
tokenType = jp.getText();
} else if (OAuth2AccessToken.REFRESH_TOKEN.equals(name)) {
refreshToken = jp.getText();
} else if (OAuth2AccessToken.EXPIRES_IN.equals(name)) {
try {
expiresIn = jp.getLongValue();
} catch (JsonParseException e) {
expiresIn = Long.valueOf(jp.getText());
}
} else if (OAuth2AccessToken.SCOPE.equals(name)) {
scope = parseScope(jp);
} else {
additionalInformation.put(name, jp.readValueAs(Object.class));
}
}
DefaultOAuth2AccessToken accessToken = new DefaultOAuth2AccessToken(tokenValue);
accessToken.setTokenType(tokenType);
if (expiresIn != null) {
accessToken.setExpiration(new Date(System.currentTimeMillis() + (expiresIn * 1000)));
}
if (refreshToken != null) {
accessToken.setRefreshToken(new DefaultOAuth2RefreshToken(refreshToken));
}
accessToken.setScope(scope);
accessToken.setAdditionalInformation(additionalInformation);
return accessToken;
}
private Set<String> parseScope(JsonParser jp) throws JsonParseException, IOException {
Set<String> scope;
if (jp.getCurrentToken() == JsonToken.START_ARRAY) {
scope = new TreeSet<String>();
while (jp.nextToken() != JsonToken.END_ARRAY) {
scope.add(jp.getValueAsString());
}
} else {
String text = jp.getText();
scope = OAuth2Utils.parseParameterList(text);
}
return scope;
}
}
My own custom class by extending DefaultOAuth2AccessToken
#com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = MyCustomDeserializer.class)
public class MyCustomOAuth2AccessToken extends DefaultOAuth2AccessToken {
public MyCustomOAuth2AccessToken(String value) {
super(value);
}
public MyCustomOAuth2AccessToken(OAuth2AccessToken accessToken) {
super(accessToken);
}
}
and at the moment I am registering a bean of type Jackson2ObjectMapperBuilderCustomizer like this
#Bean
public Jackson2ObjectMapperBuilderCustomizer addCustomDeserialization() {
return new Jackson2ObjectMapperBuilderCustomizer() {
#Override
public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
SimpleModule m = new SimpleModule();
m.addDeserializer(OAuth2AccessToken.class, new MyCustomDeserializer());
jacksonObjectMapperBuilder.modules(m);
}
};
}
#Bean
public OAuth2ClientContext getOAuth2ClientContext() {
DefaultOAuth2ClientContext defaultOAuth2ClientContext = new DefaultOAuth2ClientContext();
defaultOAuth2ClientContext.setAccessToken(new MyCustomOAuth2AccessToken("test"));
return defaultOAuth2ClientContext;
}
You can simply annotate your deserialization classes with #JsonComponent.
The annotation allows us to expose an annotated class to be a Jackson serializer and/or deserializer without the need to add it to the ObjectMapper manually.
To configure ObjectMapper globally just create a bean of type Jackson2ObjectMapperBuilder and use deserializerByType method :
#Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
return new Jackson2ObjectMapperBuilder()
.deserializerByType(OAuth2AccessToken.class, new MyCustomDeserializer());
}
Reference of configuring ObjectMapper in SpringBoot can be found here.

How i can pass new Object between Zuul filters, using spring context?

Currently, I have AuthFilter and here I received an UserState. I need to pass it to the next Filter. But how to do it right? Or exists other practices to resolve it?
public class AuthFilter extends ZuulFilter {
#Autowired
private AuthService authService;
#Autowired
private ApplicationContext appContext;
#Override
public String filterType() {
return PRE_TYPE;
}
#Override
public int filterOrder() {
return PRE_DECORATION_FILTER_ORDER - 2;
}
#Override
public boolean shouldFilter() {
RequestContext context = RequestContext.getCurrentContext();
String requestURI = context.getRequest().getRequestURI();
for (String authPath : authPaths) {
if (requestURI.contains(authPath)) return true;
}
return false;
}
#Override
public Object run() throws ZuulException {
try {
UserState userState = authService.getUserData();
DefaultListableBeanFactory context = new DefaultListableBeanFactory();
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(UserState.class);
beanDefinition.setPropertyValues(new MutablePropertyValues() {
{
add("user", userState);
}
});
context.registerBeanDefinition("userState", beanDefinition);
} catch (UndeclaredThrowableException e) {
if (e.getUndeclaredThrowable().getClass() == UnauthorizedException.class) {
throw new UnauthorizedException(e.getMessage());
}
if (e.getUndeclaredThrowable().getClass() == ForbiddenException.class) {
throw new ForbiddenException(e.getMessage(), "The user is not allowed to make this request");
}
}
return null;
}
}
I pretty sure filters are chained together and the request/response are passed through them. You can add the data to the request, and have the next filter look for it.

Spring Boot cache doesn't work

I trying to configure spring cache, but the method is executed still. I have the below code, and the civilStatus cache is not working. The method getCivilStatus() is executed always. Does Anybody know the reason?
#Configuration
#EnableCaching
public class ApplicationConfig {
#Autowired
private SocioDemographicInfoService socioDemographicInfo;
#Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(
new ConcurrentMapCache("civilStatus");
return cacheManager;
}
}
#Service
public class SocioDemographicInfoService {
#Cacheable(value="civilStatus")
public Map<String, String> getCivilStatus(){
log.info("Retrieving civilStatus");
Map<String, String> civilStatus = new HashMap<String, String>();
BufferedReader br = null;
String line = "";
String cvsSplitBy = ",";
try {
ClassLoader classLoader = getClass().getClassLoader();
File file = new File(classLoader.getResource("CatalogoEstadoCivil.csv").getFile());
br = new BufferedReader(new FileReader(file));
while ((line = br.readLine()) != null) {
String[] cod = line.split(cvsSplitBy);
civilStatus.put(cod[0].trim(), cod[1]);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return civilStatus;
}
}
}
I believe you are using spring boot and setting up a server using a class something like this (given below). Add EnableCaching annotation on the same class and define CacheManager as given below, instead of a separate configuration class. That will make sure caching is enabled before your class get initialized.
#Configuration
#EnableAutoConfiguration
#ComponentScan
#EnableCaching
#PropertySource(ignoreResourceNotFound = true, value = {"classpath:application.properties"})
#ImportResource(value = { "classpath*:spring/*.xml" })
public class MyBootServer{
public static void main(String args[]){
ApplicationContext ctx = SpringApplication.run(MyBootServer.class, args);
}
#Bean(name="cacheManager")
public CacheManager getCacheManager() {
...// Your code
}
}
Nothing wrong in your over all code. I tested your configuration in my spring boot sample code and it works
You don't need the AOP and caching complexity your usecase is a lot simpler. Just create a method that loads the file at startup and let your getCivilStatus return that map. A lot simpler.
#Service
public class SocioDemographicInfoService implements ResourceLoaderAware {
private final Map<String, String> civilStatus = new HashMap<String, String>();
private ResourceLoader loader;
#PostConstruct
public void init() {
log.info("Retrieving civilStatus");
Map<String, String> civilStatus = new HashMap<String, String>();
BufferedReader br = null;
String line = "";
String cvsSplitBy = ",";
Resource input = loader.getResource("classpath:CatalogoEstadoCivil.csv"));
if (input.isReadable() ) {
File file = input.getFile();
br = new BufferedReader(new FileReader(file));
try {
while ((line = br.readLine()) != null) {
String[] cod = line.split(cvsSplitBy);
civilStatus.put(cod[0].trim(), cod[1]);
}
} catch (IOException e) {
logger.error("Error reading file", e_;
} finally {
if (br != null) {
try { br.close() } catch( IOException e) {}
}
}
}
}
public Map<String, String> getCivilStatus() {
return this.civilStatus;
}
public void setResourceLoader(ResourceLoader loader) {
this.loader=loader;
}
}
Something like this should work. It loads your after the bean is constructed (this code can probably be optimized by using something like commons-io). Note I used Springs ResourceLoader to load the file.

Resources