How to configure Spring AMQP to not requeue when AmqpRejectAndDontRequeueException is thrown from the listener? - spring-amqp

I currently have a Spring Boot Application configured to use spring-boot-starter-amqp 2.1.5.RELEASE. I have configured it for retries in the yaml:
rabbitmq:
listener:
simple:
retry:
enabled: true # retrys enabled
max-attempts: 3 # total number number attempts (includes the original one)
multiplier: 1.5 # multiple of initial interval for subsequent retries
initial-interval: 1000 # first interval between attempts
In my listener under an certain scenario I throw a AmqpRejectAndDontRequeueException but this does not prevent the re-queueing.
How can I configure a configuration bean inline with the automatic spring configuration to stop further requeue of a message if this exception is thrown?
Queue A should attempt the processing on the queueA listener 3 times and the logs support this.
Queue B should attempt only once and stop when the AmqpRejectAndDontRequeueException is thrown.
SpringBoot Application class:
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.List;
import javax.annotation.PostConstruct;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.amqp.AmqpRejectAndDontRequeueException;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.annotation.Payload;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsonorg.JsonOrgModule;
import lombok.extern.slf4j.Slf4j;
#SpringBootApplication
#Slf4j
public class AmqpApplication
{
protected static final String X_ATTEMPTS_HEADER = "x-attempts";
protected static final String X_LAST_ATTEMPT_DATE_HEADER = "x-last-attempt-date";
public static void main(String[] args) throws InterruptedException
{
ConfigurableApplicationContext context = SpringApplication.run(AmqpApplication.class, args);
context.close();
System.exit(0);
}
private String host = "localhost";
private Integer port = 5672;
private String vhost = "/";
private String username = "guest";
private String password = "guest";
private String exchangeName = "common-exchange";
#Autowired
private RabbitTemplate rabbitTemplate;
/**
* Configures the connection factory using the configured values
*
* #return Connection factory to use to connect to rabbitmq and send events
**/
private ConnectionFactory connectionFactory()
{
CachingConnectionFactory factory = new CachingConnectionFactory(host, port);
factory.setRequestedHeartBeat(30);
factory.setConnectionTimeout(30000);
factory.setChannelCacheSize(10);
factory.setVirtualHost(vhost);
factory.setUsername(username);
factory.setPassword(password);
return factory;
}
#Bean
public Queue queueA()
{
return QueueBuilder.durable("a").withArgument("x-dead-letter-exchange", "a")
.withArgument("x-dead-letter-routing-key", "a-dead-letter").build();
}
#Bean
public Queue queueB()
{
return QueueBuilder.durable("b").withArgument("x-dead-letter-exchange", "b")
.withArgument("x-dead-letter-routing-key", "b-dead-letter").build();
}
#Bean
Queue DeadLetterQueueA()
{
return QueueBuilder.durable("a-dead-letter").build();
}
#Bean
Queue DeadLetterQueueB()
{
return QueueBuilder.durable("b-dead-letter").build();
}
/**
* Required for executing adminstration functions against an AMQP Broker
*/
#Bean
public AmqpAdmin amqpAdmin(RabbitListenerEndpointRegistry registry)
{
//#// #formatter:off
RabbitAdmin admin = new RabbitAdmin(connectionFactory());
admin.declareQueue(queueA());
admin.declareQueue(queueB());
registry.start();
return admin;
}
/**
* The following is a complete declaration of an exchange, a queue and a
* exchange-queue binding
*/
#Bean
public DirectExchange directExchange()
{
return new DirectExchange(exchangeName, true, false);
}
#Bean
public List<Binding> exchangeBinding()
{
// Important part is the routing key -- this is just an example
return Arrays.asList(
BindingBuilder.bind(queueA()).to(directExchange()).with("a"),
BindingBuilder.bind(DeadLetterQueueA()).to(directExchange())
.with("a"),
BindingBuilder.bind(queueB()).to(directExchange()).with("b"),
BindingBuilder.bind(DeadLetterQueueB()).to(directExchange())
.with("b"));
}
#Bean
public RabbitTemplate rabbitTemplate()
{
// Add the object mapper to the converter
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JsonOrgModule());
// Add the object mapper to the converter
RabbitTemplate template = new RabbitTemplate(connectionFactory());
template.setMessageConverter(new Jackson2JsonMessageConverter(objectMapper));
template.setExchange(exchangeName);
return template;
}
#PostConstruct
public void sendMessages() throws InterruptedException
{
rabbitTemplate.convertAndSend(exchangeName, "a", new BeanObject().setName("a"));
rabbitTemplate.convertAndSend(exchangeName, "b", new BeanObject().setName("b"));
}
#RabbitListener(queues = "a")
public void aListener(#Payload BeanObject payload, Message message,
#Header(required = false, name = X_ATTEMPTS_HEADER, defaultValue = "0") Integer attempts)
{
beforeProcessing(payload,message,attempts);
throw new RuntimeException();
}
#RabbitListener(queues = "b")
public void bListener(#Payload BeanObject payload, Message message,
#Header(required = false, name = X_ATTEMPTS_HEADER, defaultValue = "0") Integer attempts)
{
beforeProcessing(payload,message,attempts);
throw new AmqpRejectAndDontRequeueException("");
}
private void beforeProcessing(BeanObject payload, Message message,
#Header(required = false, name = X_ATTEMPTS_HEADER, defaultValue = "0") Integer attemptNo)
{
//// #formatter:off
attemptNo++;// Increment
message.getMessageProperties().getHeaders().put(X_ATTEMPTS_HEADER, attemptNo);//update attempts header
// #formatter:on
log.info(
"bean: {}, attemptNo: {}",
payload, attemptNo);
}
}
MessageConverter Class:
import java.io.IOException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.support.converter.MessageConversionException;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
#Component
public class MessageConverter implements org.springframework.amqp.support.converter.MessageConverter
{
private final ObjectMapper objectMapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
#Override
public Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException
{
return null;
}
#Override
public Object fromMessage(Message message) throws MessageConversionException
{
if (message.getMessageProperties() == null || message.getMessageProperties().getHeaders() == null
|| !message.getMessageProperties().getHeaders().containsKey("__TypeId__"))
{
throw new MessageConversionException(
"No header exists in the message for [__TypeId__]. This is required to hint the conversion type.");
}
String typeId = message.getMessageProperties().getHeaders().get("__TypeId__").toString();
try
{
return objectMapper.readValue(message.getBody(), Class.forName(typeId));
}
catch (ClassNotFoundException | IOException e)
{
throw new MessageConversionException(
String.format("Unable to convert message payload to type [%s]", typeId));
}
}
}
Lombok Bean class:
package com.amqp;
import lombok.Data;
import lombok.experimental.Accessors;
#Data
#Accessors(chain = true)
public class BeanObject
{
private String name;
}
POM:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.amqp</groupId>
<artifactId>amqp</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>amqp</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.datatype/jackson-datatype-jsr310 -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml/jackson-module-json-org -->
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.datatype/jackson-datatype-json-org -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-json-org</artifactId>
</dependency>
<dependency>
<groupId>javax.interceptor</groupId>
<artifactId>javax.interceptor-api</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Logs:
bean: BeanObject(name=a), attemptNo: 1
bean: BeanObject(name=b), attemptNo: 1
bean: BeanObject(name=a), attemptNo: 2
bean: BeanObject(name=b), attemptNo: 2
bean: BeanObject(name=b), attemptNo: 3
bean: BeanObject(name=a), attemptNo: 3

I found the solution. Amqp wraps the exception thrown inside a ListenerExecutionFailedException. I have overridden the SimpleRabbitListenerContainerFactory and specified my own Retry Policy which extends the SimpleRetryPolicy. I then make sure I pass the cause of the throwable to the retryForException method. I have also made sure that I specify a retryable map of classes in the advice chain:
Here are the logs now, as you can see 'a' attempts 3 times and 'b' only once:
bean: BeanObject(name=b), attemptNo: 1
bean: BeanObject(name=a), attemptNo: 1
bean: BeanObject(name=a), attemptNo: 2
bean: BeanObject(name=a), attemptNo: 3
Here is the new Main Spring boot class:
package com.amqp;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import org.springframework.amqp.AmqpRejectAndDontRequeueException;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.config.RetryInterceptorBuilder;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.ConditionalRejectingErrorHandler;
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry;
import org.springframework.amqp.rabbit.retry.MessageRecoverer;
import org.springframework.amqp.rabbit.retry.RejectAndDontRequeueRecoverer;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.classify.BinaryExceptionClassifier;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.retry.RetryContext;
import org.springframework.retry.backoff.BackOffPolicy;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.util.ErrorHandler;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsonorg.JsonOrgModule;
import lombok.extern.slf4j.Slf4j;
#SpringBootApplication
#Slf4j
public class AmqpApplication
{
protected static final String X_ATTEMPTS_HEADER = "x-attempts";
protected static final String X_LAST_ATTEMPT_DATE_HEADER = "x-last-attempt-date";
public static void main(String[] args) throws InterruptedException
{
ConfigurableApplicationContext context = SpringApplication.run(AmqpApplication.class, args);
context.close();
System.exit(0);
}
private String host = "localhost";
private Integer port = 5672;
private String vhost = "/";
private String username = "guest";
private String password = "guest";
private String exchangeName = "common-exchange";
#Autowired
private RabbitTemplate rabbitTemplate;
#Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory()
{
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
factory.setAdviceChain(retryOperationsInterceptor().build());
factory.setErrorHandler(new ConditionalRejectingErrorHandler());
factory.setAutoStartup(true);
factory.setMessageConverter(new MessageConverter());
return factory;
}
/**
* Configures the connection factory using the configured values
*
* #return Connection factory to use to connect to rabbitmq and send events
**/
private ConnectionFactory connectionFactory()
{
CachingConnectionFactory factory = new CachingConnectionFactory(host, port);
factory.setRequestedHeartBeat(30);
factory.setConnectionTimeout(30000);
factory.setChannelCacheSize(10);
factory.setVirtualHost(vhost);
factory.setUsername(username);
factory.setPassword(password);
return factory;
}
#Bean
public RetryInterceptorBuilder<?> retryOperationsInterceptor()
{
RetryInterceptorBuilder<?> builder = RetryInterceptorBuilder.stateless();
builder.retryPolicy(new MyRetryPolicy(3, retryableClassifier()));
builder.backOffPolicy(backoffPolicy());
MessageRecoverer recoverer = new RejectAndDontRequeueRecoverer();
builder.recoverer(recoverer);
return builder;
}
#Bean
public BackOffPolicy backoffPolicy()
{
ExponentialBackOffPolicy backoffPolicy = new ExponentialBackOffPolicy();
backoffPolicy.setInitialInterval(1000);
backoffPolicy.setMaxInterval(10000);
backoffPolicy.setMultiplier(1.5);
return backoffPolicy;
}
#Bean
public Map<Class<? extends Throwable>, Boolean> retryableClassifier()
{
Map<Class<? extends Throwable>, Boolean> retryableClassifier = new HashMap<>();
retryableClassifier.put(AmqpRejectAndDontRequeueException.class, false);
retryableClassifier.put(Exception.class, true);
return retryableClassifier;
}
#Bean
public Queue queueA()
{
return QueueBuilder.durable("a").withArgument("x-dead-letter-exchange", "a")
.withArgument("x-dead-letter-routing-key", "a-dead-letter").build();
}
#Bean
public Queue queueB()
{
return QueueBuilder.durable("b").withArgument("x-dead-letter-exchange", "b")
.withArgument("x-dead-letter-routing-key", "b-dead-letter").build();
}
#Bean
Queue DeadLetterQueueA()
{
return QueueBuilder.durable("a-dead-letter").build();
}
#Bean
Queue DeadLetterQueueB()
{
return QueueBuilder.durable("b-dead-letter").build();
}
/**
* Required for executing adminstration functions against an AMQP Broker
*/
#Bean
public AmqpAdmin amqpAdmin(RabbitListenerEndpointRegistry registry,
SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory)
{
//#// #formatter:off
RabbitAdmin admin = new RabbitAdmin(connectionFactory());
admin.declareQueue(queueA());
admin.declareQueue(queueB());
registry.start();
return admin;
}
/**
* The following is a complete declaration of an exchange, a queue and a
* exchange-queue binding
*/
#Bean
public DirectExchange directExchange()
{
return new DirectExchange(exchangeName, true, false);
}
#Bean
public List<Binding> exchangeBinding()
{
// Important part is the routing key -- this is just an example
return Arrays.asList(
BindingBuilder.bind(queueA()).to(directExchange()).with("a"),
BindingBuilder.bind(DeadLetterQueueA()).to(directExchange())
.with("a"),
BindingBuilder.bind(queueB()).to(directExchange()).with("b"),
BindingBuilder.bind(DeadLetterQueueB()).to(directExchange())
.with("b"));
}
#Bean
public RabbitTemplate rabbitTemplate()
{
// Add the object mapper to the converter
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JsonOrgModule());
// Add the object mapper to the converter
RabbitTemplate template = new RabbitTemplate(connectionFactory());
template.setMessageConverter(new Jackson2JsonMessageConverter(objectMapper));
template.setExchange(exchangeName);
return template;
}
#PostConstruct
public void sendMessages() throws InterruptedException
{
rabbitTemplate.convertAndSend(exchangeName, "a", new BeanObject().setName("a"));
rabbitTemplate.convertAndSend(exchangeName, "b", new BeanObject().setName("b"));
}
#RabbitListener(queues = "a")
public void aListener(#Payload BeanObject payload, Message message,
#Header(required = false, name = X_ATTEMPTS_HEADER, defaultValue = "0") Integer attempts)
{
beforeProcessing(payload,message,attempts);
throw new RuntimeException();
}
#RabbitListener(queues = "b")
public void bListener(#Payload BeanObject payload, Message message,
#Header(required = false, name = X_ATTEMPTS_HEADER, defaultValue = "0") Integer attempts)
{
beforeProcessing(payload,message,attempts);
throw new AmqpRejectAndDontRequeueException("");
}
private void beforeProcessing(BeanObject payload, Message message,
#Header(required = false, name = X_ATTEMPTS_HEADER, defaultValue = "0") Integer attemptNo)
{
//// #formatter:off
attemptNo++;// Increment
message.getMessageProperties().getHeaders().put(X_ATTEMPTS_HEADER, attemptNo);//update attempts header
// #formatter:on
log.info(
"bean: {}, attemptNo: {}",
payload, attemptNo);
}
private static class MyRetryPolicy extends SimpleRetryPolicy
{
private BinaryExceptionClassifier retryableClassifier;
private int maxAttempts;
#Override
public boolean canRetry(RetryContext context)
{
Throwable t = context.getLastThrowable();
return (t == null || retryForException(t.getCause())) && context.getRetryCount() < maxAttempts;
}
public MyRetryPolicy(int maxAttempts, Map<Class<? extends Throwable>, Boolean> retryableExceptions)
{
this.maxAttempts = maxAttempts;
this.retryableClassifier = new BinaryExceptionClassifier(retryableExceptions, false);
}
private boolean retryForException(Throwable ex)
{
return this.retryableClassifier.classify(ex);
}
}
public static class MyErrorHandler implements ErrorHandler
{
#Override
public void handleError(Throwable t)
{
if (!this.causeChainContainsARADRE(t))
{
throw new AmqpRejectAndDontRequeueException("Error Handler converted exception to fatal", t);
}
}
private boolean causeChainContainsARADRE(Throwable t)
{
Throwable cause = t.getCause();
while (cause != null)
{
if (cause instanceof AmqpRejectAndDontRequeueException)
{
return true;
}
cause = cause.getCause();
}
return false;
}
}
}

Related

Testing Controller with Spring-security

In the OpenApi30Config configuration file I have:
import org.springframework.context.annotation.Configuration;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.annotations.security.SecurityScheme;
#Configuration
#OpenAPIDefinition(info = #Info(title = "Users API", version = "2.0", description = "Users Information"))
#SecurityScheme(name = "bearerAuth", type = SecuritySchemeType.HTTP, bearerFormat = "JWT", scheme = "bearer")
public class OpenApi30Config {
}
In the WebSecurityConfig
#EnableWebSecurity
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private Environment environment;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.addFilterAfter(new JWTAuthorizationFilter(environment), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/auth").permitAll()
.antMatchers("/open-api/**").permitAll()
.anyRequest().authenticated();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("someUser")
.password(passwordEncoder().encode("somePassword"))
.authorities("ADMIN");
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Controllers:
AuthController
#RestController
public class AuthController implements EnvironmentAware {
private Environment environment;
#PostMapping("auth")
public User login(#RequestParam("user") String name, #RequestParam("password") String pwd) {
String token = getJWTToken(name);
User user = new User();
user.setName(name);
user.setToken(token);
return user;
}
private String getJWTToken(String username) {
List<GrantedAuthority> grantedAuthorities = AuthorityUtils
.commaSeparatedStringToAuthorityList("ROLE_USER");
String token = Jwts
.builder()
.setId("softtekJWT")
.setSubject(username)
.claim("authorities",
grantedAuthorities.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList()))
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + Long.parseLong(environment.getProperty("millisecond.expiration"))))
.signWith(SignatureAlgorithm.HS512,
environment.getProperty("secret.key").getBytes()).compact();
return "Bearer " + token;
}
#Override
public void setEnvironment(final Environment environment) {
this.environment = environment;
}
}
The UserController:
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
#RestController
#RequestMapping("/api/users")
public class UserController {
#Autowired
private UserServiceIface userService;
#Autowired UserPasswordValidator userPasswordValidator;
#Operation(summary = "list Users", security = #SecurityRequirement(name = "bearerAuth"))
#GetMapping
public ResponseEntity<?> list(){
return ResponseEntity.ok().body(userService.findAll());
}
#Operation(summary = "view User", security = #SecurityRequirement(name = "bearerAuth"))
#GetMapping("/{id}")
public ResponseEntity<?> view(#PathVariable Long id) {
Optional<User> optionalStoredUser = userService.findById(id);
if (!optionalStoredUser.isPresent()) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok().body(optionalStoredUser.get());
}
#Operation(summary = "create User", security = #SecurityRequirement(name = "bearerAuth"))
#PostMapping
public ResponseEntity<?> create(#RequestHeader(value="Authorization") String token,
#Valid #RequestBody User user, BindingResult result) {
userPasswordValidator.validate(user, result);
throwExceptionIfErrors(result);
Optional<User> optionalStoredUser = userService.findByEmail(user.getEmail());
if (optionalStoredUser.isPresent()) {
throw new ExistingMailException(user.getEmail());
}
user.setIsactive(true);
user.setToken(token);
User createdUser = userService.save(user);
return ResponseEntity.status(HttpStatus.CREATED).body(createdUser);
}
#Operation(summary = "edit User", security = #SecurityRequirement(name = "bearerAuth"))
#PutMapping("/{id}")
public ResponseEntity<?> edit(#RequestHeader(value="Authorization") String token,
#Valid #RequestBody User user, BindingResult result, #PathVariable Long id) {
userPasswordValidator.validate(user, result);
throwExceptionIfErrors(result);
if (user.getEmail() != null && !user.getEmail().isEmpty()) {
boolean emailUsed = userService.findByEmailAndIdNot(user.getEmail(), id).size() > 0;
if (emailUsed) {
throw new ExistingMailException(user.getEmail());
}
}
Optional<User> optionalStoredUser = userService.findById(id);
if (!optionalStoredUser.isPresent()) {
return ResponseEntity.notFound().build();
}
User editedUser = optionalStoredUser.get();
editedUser.setName(user.getName());
editedUser.setEmail(user.getEmail());
editedUser.setPassword(user.getPassword());
editedUser.setPhones(user.getPhones());
editedUser.setModified(new Date());
editedUser.setIsactive(user.isIsactive());
editedUser.setToken(token);
try {
User updatedUser = userService.save(editedUser);
return ResponseEntity.status(HttpStatus.CREATED).body(updatedUser);
} catch (Exception exp) {
throw new DefaultException(exp.getLocalizedMessage());
}
}
#Operation(summary = "delete User", security = #SecurityRequirement(name = "bearerAuth"))
#DeleteMapping("/{id}")
public ResponseEntity<?> delete(#PathVariable Long id) {
userService.deleteById(id);
return ResponseEntity.noContent().build();
}
private void throwExceptionIfErrors(BindingResult result) {
if (result.hasErrors()) {
FieldError passwordFieldError = result.getAllErrors()
.stream().map(e -> (FieldError) e)
.filter(f -> f.getField().equals("password"))
.findFirst().orElse(null);
if (passwordFieldError != null) {
throw new PatternPasswordException(passwordFieldError.getDefaultMessage());
}
FieldError emailFieldError = result.getAllErrors()
.stream().map(e -> (FieldError) e)
.filter(f -> f.getField().equals("email"))
.findFirst().orElse(null);
if (emailFieldError != null) {
throw new PatternEmailException(emailFieldError.getDefaultMessage());
}
}
}
}
In my pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.5.13</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<!-- <version>5.8.2</version> -->
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<!-- <version>5.8.2</version> -->
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<!-- <version>5.8.2</version> -->
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<!-- <version>4.2.0</version> -->
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<!-- <version>4.2.0</version> -->
</dependency>
</dependencies>
Now UserControllerTest in the test:
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
#WebMvcTest(UserController.class)
class UserControllerTest {
#Autowired
private MockMvc mvc;
#MockBean
private UserServiceIface userService;
#MockBean
UserPasswordValidator userPasswordValidator;
private List<User> users;
#SuppressWarnings("serial")
#BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
users = new ArrayList<User>() {
{
User user = new User();
user.setId(1L);
user.setName("First");
user.setEmail("i#country.com");
user.setPassword("abc1");
add(user);
user = new User();
user.setId(2L);
user.setName("Second");
user.setEmail("you#country.com");
user.setPassword("abc2");
add(user);
user = new User();
user.setId(3L);
user.setName("Third");
user.setEmail("he#country.com");
user.setPassword("abc3");
add(user);
}
};
}
#Test
void testList() throws Exception {
// given
when(userService.findAll()).thenReturn(users);
// then
mvc.perform(MockMvcRequestBuilders.get("/api/users").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.[0].name").value("First"))
;
}
Running I get:
MockHttpServletResponse:
Status = 403
Error message = Access Denied
Headers = [X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
EDIT 1:
I changed my test class!
package org.bz.ms.app.usuarios.controllers;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import org.junit.jupiter.api.*;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
#WebMvcTest(UserController.class)
class UserControllerTest {
#Autowired
private WebApplicationContext context;
#Autowired
private MockMvc mvc;
#MockBean
private UserServiceIface userService;
#MockBean
UserPasswordValidator userPasswordValidator;
private List<User> users;
#SuppressWarnings("serial")
#BeforeEach
void setUp() {
this.mvc = MockMvcBuilders.webAppContextSetup(this.context).apply(springSecurity()).build();
// userDao = mock(UserDao.class); // Manually
MockitoAnnotations.openMocks(this);
users = new ArrayList<User>() {
{
User user = new User();
user.setId(1L);
user.setName("First");
user.setEmail("i#country.com");
user.setPassword("abc1");
add(user);
user = new User();
user.setId(2L);
user.setName("Second");
user.setEmail("you#country.com");
user.setPassword("abc2");
add(user);
user = new User();
user.setId(3L);
user.setName("Third");
user.setEmail("he#country.com");
user.setPassword("abc3");
add(user);
}
};
}
#Test
#WithMockUser
void testList() throws Exception {
// given
when(userService.findAll()).thenReturn(users);
// then
mvc.perform(MockMvcRequestBuilders.get("/api/users").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
// .andExpect(content().contentType(MediaType.APPLICATION_JSON))
// .andExpect(jsonPath("$.[0].name").value("First"))
;
}
}
I have doubts about this injection, but I think that I have: WebSecurityConfig and OpenApi30Config.
#Autowired
private WebApplicationContext context;
and this on setUp method:
this.mvc = MockMvcBuilders.webAppContextSetup(this.context).apply(springSecurity()).build();
How to test my controller including the security?
It is because you configure the API can only be accessed by an authenticated user but now you are sending a request as an anonymous user and hence it return 403 access denied error.
The most simplest way is to add #WithMockUser to the test method. It will emulate the test are running with an authenticated user . It also allows to configure what the authorities or role do this user has :
#Test
#WithMockUser
void testList() throws Exception {
// given
when(userService.findAll()).thenReturn(users);
// then
mvc.perform(MockMvcRequestBuilders.get("/api/users").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.[0].name").value("First"))
;
}
Please note that #WithMockUser requires to add the spring-security-test dependency :
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>

EventBus in Reactor 3.x

I know that EventBus is deprecated in Reactor3.x, and the suggested solution is ReplayProcessor. I have read https://github.com/reactor/reactor-core/issues/375. But the code here is too draft. I created a demo project to prove the idea here. Can someone give some comments?
======== Application.java
package hello;
import org.reactivestreams.Subscription;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Flux;
import reactor.core.publisher.ReplayProcessor;
import reactor.core.publisher.BaseSubscriber;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
#Configuration
#EnableAutoConfiguration
#ComponentScan
public class Application implements CommandLineRunner {
private static final int NUMBER_OF_QUOTES = 10;
#Bean
ReplayProcessor createReplayProcessor() {
ReplayProcessor<MyEvent> rp = ReplayProcessor.create();
Flux<MyEvent> interest1 = rp.filter(ev -> filterInterest1(ev));
Flux<MyEvent> interest2 = rp.filter(ev -> filterInterest2(ev));
interest1.subscribe(new BaseSubscriber<MyEvent>() {
#Override
protected void hookOnSubscribe(Subscription subscription) {
requestUnbounded();
}
#Override
protected void hookOnNext(MyEvent value) {
//todo: call service method
System.out.println("event 1 handler -> event name:" + value.getEventName());
}
});
interest2.subscribe(new BaseSubscriber<MyEvent>() {
#Override
protected void hookOnSubscribe(Subscription subscription) {
requestUnbounded();
}
#Override
protected void hookOnNext(MyEvent value) {
//todo: call service method
System.out.println("event2 handler -> event name:" + value.getEventName());
}
});
return rp;
}
public boolean filterInterest1(MyEvent myEvent) {
if (myEvent != null && myEvent.getEventName() != null
&& myEvent.getEventName().equalsIgnoreCase("event1")) {
return true;
}
return false;
}
public boolean filterInterest2(MyEvent myEvent) {
if (myEvent != null && myEvent.getEventName() != null
&& myEvent.getEventName().equalsIgnoreCase("event2")) {
return true;
}
return false;
}
#Autowired
private Publisher publisher;
#Bean
public CountDownLatch latch() {
return new CountDownLatch(NUMBER_OF_QUOTES);
}
#Override
public void run(String... args) throws Exception {
publisher.publishQuotes(NUMBER_OF_QUOTES);
}
public static void main(String[] args) throws InterruptedException {
ApplicationContext app = SpringApplication.run(Application.class, args);
app.getBean(CountDownLatch.class).await(10, TimeUnit.SECONDS);
}
}
==========MyEvent.java=============
package hello;
public class MyEvent {
private String eventName = "";
public String getEventName() {
return eventName;
}
public void setEventName(String eventName) {
this.eventName = eventName;
}
public MyEvent(String eventName) {
this.eventName = eventName;
}
public void filterInterest1(MyEvent myEvent) {
}
}
=============Publisher.java ===========
package hello;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import reactor.core.publisher.ReplayProcessor;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
#Service
public class Publisher {
#Autowired
ReplayProcessor rp;
#Autowired
CountDownLatch latch;
public void publishQuotes(int numberOfQuotes) throws InterruptedException {
long start = System.currentTimeMillis();
rp.onNext(new MyEvent("event1"));
rp.onNext(new MyEvent("event2"));
rp.onNext(new MyEvent("event3"));
long elapsed = System.currentTimeMillis() - start;
System.out.println("Elapsed time: " + elapsed + "ms");
System.out.println("Average time per quote: " + elapsed / numberOfQuotes + "ms");
}
}
The whole code is https://github.com/yigubigu/reactor-start-sample.git
IMHO you can relay in Spring event handlers. Matt Raible and Josh Long use that in this couple of tutorials:
https://developer.okta.com/blog/2018/09/24/reactive-apis-with-spring-webflux
https://developer.okta.com/blog/2018/09/25/spring-webflux-websockets-react
Key takeaways:
#Component
class ProfileCreatedEventPublisher implements
ApplicationListener<ProfileCreatedEvent>,
Consumer<FluxSink<ProfileCreatedEvent>>
Uses an event loop to take events from a LinkedBlockingQueue.
#Override
public void onApplicationEvent(ProfileCreatedEvent event)
Queue the events that can be published anywhere within your app.
ProfileCreatedEventPublisher is used in ServerSentEventController to create a Flux of events (that can be chained with a filter), it transforms and sends them to a web client.

Can't implement Phase Listener in jsf

I am trying to use phase Listener in one of my beans in jsf but it's not working.
Class:
package com.mycompany.creditcard1;
import java.io.Serializable;
import javax.annotation.PostConstruct;
import javax.faces.context.FacesContext;
import javax.faces.context.Flash;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import javax.faces.view.ViewScoped;
import javax.inject.Named;
#Named(value = "userDetailsLogin1")
#ViewScoped
public class UserDetailsLogin1 implements Serializable, PhaseListener {
private UserDetails userDetails;
Flash flash = FacesContext.getCurrentInstance().getExternalContext().getFlash();
#PostConstruct
public void init() {
System.out.println("inti");
userDetails = (UserDetails) flash.get("userDetails");
if (userDetails == null) {
userDetails = new UserDetails();
}
}
public UserDetailsLogin1() {
}
public UserDetails getUserDetails() {
return userDetails;
}
public String action() {
flash.put("userDetails", userDetails);
return "UserDetailsLogin2?faces-redirect=true";
}
#Override
public void afterPhase(PhaseEvent pe) {
System.out.println("after phase");
}
#Override
public void beforePhase(PhaseEvent pe) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
#Override
public PhaseId getPhaseId() {
return PhaseId.RESTORE_VIEW;
}
}
faces-config file:
<?xml version='1.0' encoding='UTF-8'?>
<faces-config version="2.2"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd">
<lifecycle>
<phase-listener>com.mycompany.creditcard1.MyPhaseListener</phase-listener>
</lifecycle>
Error:
Unable to create a new instance of 'com.mycompany.creditcard1.MyPhaseListener': javax.faces.FacesException: com.mycompany.creditcard1.MyPhaseListener
Don't understand why it is showing this?
Any help!!!
Well start by replacing <phase-listener>com.mycompany.creditcard1.MyPhaseListener</phase-listener> by <phase-listener>com.mycompany.creditcard1.UserDetailsLogin1</phase-listener>... you have the wrong class name!
Also, I don't know if it is a good practice to mixup Bean and PhaseListener into the same class...

Spring Boot OAuth 2.0 UserDetails user not found

I am new to Spring Boot, and I am trying to configure OAuth 2.0. The problem I am having at this moment is that I keep getting the following message when I attempt to request for an access token:
{
"error": "invalid_grant",
"error_description": "Bad credentials"
}
The error message in the Spring Boot console says that the user cannot be found.
: Authentication attempt using org.springframework.security.authentication.dao.DaoAuthenticationProvider
: User 'stromero' not found
: Returning cached instance of singleton bean 'authenticationAuditListener'
I have implemented a custom user that has already been saved to a database using JPA, I am unable to figure why Spring Security cannot find this user, it may an issue with my logic or configuration. If someone with more experience can look at my code and perhaps guide me to the right direction, that would be greatly appreciated.
This is the HTTP Request:
POST /oauth/token HTTP/1.1
Host: localhost:8181
Authorization: Basic YnJvd3NlcjpzZWNyZXQ=
Cache-Control: no-cache
Content-Type: application/x-www-form-urlencoded
username=stromero&password=password&client_id=browser&client_secret=secret&grant_type=password
These are the classes that I used to implement my custom user and OAuth 2.0
#Repository
public interface UserRepository extends CrudRepository<CustomUser, String> {
public CustomUser findByUsername(String name);
}
Below is the custom user I have created
#Entity
#Table (name = "custom_user")
public class CustomUser {
#Id
#Column(name = "id", nullable = false, updatable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "username", unique=true, nullable = false)
private String username;
#Column(name = "password", nullable = false)
private String password;
#ElementCollection
private List<String> roles = new ArrayList<>();
public List<String> getRoles() {
return roles;
}
public void setRoles(List<String> roles) {
this.roles = roles;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Below is a customdetails service that reads the user information from the database and returns it as a UserDetails Object
#Service
#Transactional(readOnly = true)
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
CustomUser customUser = userRepository.findByUsername(s);
boolean enabled = true;
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
return new User(
customUser .getUsername(),
customUser .getPassword().toLowerCase(),
enabled,
accountNonExpired,
credentialsNonExpired,
accountNonLocked,
getAuthorities(customUser.getRoles()));
}
public Collection<? extends GrantedAuthority> getAuthorities(List<String> roles) {
List<GrantedAuthority> authList = getGrantedAuthorities(roles);
return authList;
}
public static List<GrantedAuthority> getGrantedAuthorities(List<String> roles) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
for (String role : roles) {
authorities.add(new SimpleGrantedAuthority(role));
}
return authorities;
}
}
The class below is a data structure that holds both the UserDetailsService and ClientDetailsService
public class ClientAndUserDetailsService implements UserDetailsService,
ClientDetailsService {
private final ClientDetailsService clients;
private final UserDetailsService users;
private final ClientDetailsUserDetailsService clientDetailsWrapper;
public ClientAndUserDetailsService(ClientDetailsService clients,
UserDetailsService users) {
super();
this.clients = clients;
this.users = users;
clientDetailsWrapper = new ClientDetailsUserDetailsService(this.clients);
}
#Override
public ClientDetails loadClientByClientId(String clientId)
throws ClientRegistrationException {
return clients.loadClientByClientId(clientId);
}
#Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
UserDetails user = null;
try{
user = users.loadUserByUsername(username);
}catch(UsernameNotFoundException e){
user = clientDetailsWrapper.loadUserByUsername(username);
}
return user;
}
}
The class below is my configuration for OAuth 2.0 using Spring Boot
#Configuration
public class OAuth2SecurityConfiguration {
#Configuration
#EnableWebSecurity
protected static class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
protected void registerAuthentication(
final AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
}
#Configuration
#EnableResourceServer
protected static class ResourceServer extends
ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests().antMatchers("/oauth/token").anonymous();
// Require all GET requests to have client "read" scope
http.authorizeRequests().antMatchers(HttpMethod.GET, "/**")
.access("#oauth2.hasScope('read')");
// Require all POST requests to have client "write" scope
http.authorizeRequests().antMatchers(HttpMethod.POST,"/**")
.access("#oauth2.hasScope('write')");
}
}
#Configuration
#EnableAuthorizationServer
#Order(Ordered.LOWEST_PRECEDENCE - 100)
protected static class AuthorizationServer extends
AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
private ClientAndUserDetailsService combinedService;
public AuthorizationServer() throws Exception {
ClientDetailsService clientDetailsService = new InMemoryClientDetailsServiceBuilder()
.withClient("browser")
.secret("secret")
.authorizedGrantTypes("password")
.authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")
.scopes("read","write")
.resourceIds("message")
.accessTokenValiditySeconds(7200)
.and()
.build();
// Create a series of hard-coded users.
UserDetailsService userDetailsService = new CustomUserDetailsService();
combinedService = new ClientAndUserDetailsService(clientDetailsService, userDetailsService);
}
#Bean
public ClientDetailsService clientDetailsService() throws Exception {
return combinedService;
}
#Bean
public UserDetailsService userDetailsService() {
return combinedService;
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints.authenticationManager(authenticationManager);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception {
clients.withClientDetails(clientDetailsService());
}
}
}
Below is my pom.xml file
<properties>
<tomcat.version>8.0.8</tomcat.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- Postgres JDBC Driver -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.2-1002-jdbc4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Hibernate validator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.0.3.RELEASE</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>17.0</version>
</dependency>
</dependencies>
Yeah, I had the same issue... wanted to use JPA's UserDetailsService but the same problem - user couldn't be found... got resolved it eventually, thanks to Dave Syer's OAuth2 samples on GitHub.
The problem seem to be in authenticationManager instance autowired in #EnableAuthorizationServer AuthorizationServer class. AuthenticationManager is autowired there and seems to initialize with default DAOAuthenticationProvider, and for some reason it doesn't use custom JPA UserDetailsService we initialize authenticationManager with in WebSecurityConfiguration.
In Dave Syer samples authenticationManager is exposed as a bean in WebSecurityConfiguration:
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
then in AuthorizationServer we autowire authenticationManager as follows:
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
Once I did it I finally managed to get my user authenticated against my customer JPA user repository.
I faced the same issue and spent hours investigating the case. As a workaround, if you are using Spring Boot version 1.1.8.RELEASE, downgrade it to 1.0.2.RELEASE. Things went fine that way but I did not investigate yet the reasons of compatibility issue with Spring Boot version 1.1.8.RELEASE.
InitializeUserDetailsBeanManagerConfigurer has default order as
static final int DEFAULT_ORDER = Ordered.LOWEST_PRECEDENCE - 5000;
So it Initializee DaoAuthenticationProvider before custom one.
#Order(-5001)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { ... }

Primefaces autocomplete widget getting error: Property 'label' not found on type java.lang.String

I am unable to successfully get the "p:autocomplete" widget to work...
Using the autocomplete widget, as shown here...
<p:autoComplete id="abc" dropdown="true" value="#{testBean.parmMap['inputval']}" completeMethod="#{testBean.testList}" var="items" itemLabel="#{items.label}" itemValue="#{items}" converter="#{itemConverter}" ></p:autoComplete>
I am receiving the following error message...
javax.el.PropertyNotFoundException: /index.xhtml #18,245 itemLabel="#{items.label}": Property 'label' not found on type java.lang.String
I have not been able to get past this error. Not certain where the problem lies
--I've included most of the relevant code shown below. Thank you for any guidance you can provide me!
Here is the entire facelets page - index.xhtml...
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:util="http://java.sun.com/jsf/composite/util"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:p="http://primefaces.org/ui">
<f:view contentType="text/html">
<h:head>
<title>testprimeac</title>
<meta charset="utf-8" />
</h:head>
<h:body>
<h:form id="form1">
<p:autoComplete id="abc" dropdown="true" value="#{testBean.parmMap['inputval']}" completeMethod="#{testBean.testList}" var="items" itemLabel="#{items.label}" itemValue="#{items}" converter="#{itemConverter}" ></p:autoComplete>
</h:form>
</h:body>
</f:view>
</html>`
Here is the "Item" class...
package aaa.bbb.ccc.war;
public class Item
{
private String label;
private String value;
public String getLabel()
{
return label;
}
public void setLabel(String label)
{
this.label = label;
}
public String getValue()
{
return value;
}
public void setValue(String value)
{
this.value = value;
}
}
Here is the ItemConverter class...
package aaa.bbb.ccc.war;
import java.util.List;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
public class ItemConverter implements Converter
{
#Override
public Object getAsObject(FacesContext facesContext, UIComponent component, String submittedValue)
{
if (submittedValue.trim().equals(""))
{
return null;
}
else
{
itemList = getItemList();
try
{
for (Item item : itemList)
{
if (item.getValue().equalsIgnoreCase(submittedValue))
{
return item;
}
}
}
catch (Exception e)
{
throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "Conversion Error", "Not a valid item object"));
}
}
return null;
}
#Override
public String getAsString(FacesContext facesContext, UIComponent component, Object value)
{
if (value == null || value.equals(""))
{
return "";
}
else
{
return String.valueOf(((Item) value).getValue());
}
}
private static List<Item> itemList;
private static List<Item> getItemList()
{
if (null == itemList)
{
refData = getRefData();
itemList = refData.getTestList();
}
return itemList;
}
private static RefData refData;
private static RefData getRefData()
{
refData = (RefData) FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("refData");
if (null == refData)
{
refData = new RefData();
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("refData", refData);
}
return refData;
}
}
Here is the TestBean class...
package aaa.bbb.ccc.war;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.faces.context.FacesContext;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
#Component("testBean")
#Scope("request")
public class TestBean implements Serializable
{
private RefData refData;
private LinkedHashMap<String, String> parmMap;
public TestBean()
{
}
public Map<String, String> getParmMap()
{
refData = getRefData();
return refData.getParmMap();
}
public void setParmMap(LinkedHashMap<String, String> m)
{
refData = getRefData();
refData.setParmMap(m);
storeRefData(refData);
}
public void setTestList(List<Item> list) throws Exception
{
try
{
refData = getRefData();
refData.setTestList(list);
storeRefData(refData);
}
catch (Exception e)
{
e.printStackTrace();
}
}
public List<Item> getTestList()
{
refData = getRefData();
return refData.getTestList();
}
private static RefData getRefData()
{
RefData refData = (RefData) FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("refData");
if (null == refData)
{
refData = new RefData();
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("refData", refData);
}
return refData;
}
private static void storeRefData(RefData r)
{
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("refData", r);
}
}
Here is the RefData class (referred to in TestBean)...
package aaa.bbb.ccc.war;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
public class RefData implements Serializable
{
public RefData() //(String key)
{
}
private static final Map<String, String> listBoxEntryMap;
static
{
Map<String, String> m = new LinkedHashMap<String, String>();
m.put("aaavalue", "aaalabel");
m.put("bbbvalue", "aablabel");
m.put("cccvalue", "abblabel");
m.put("dddvalue", "bbblabel");
m.put("eeevalue", "bbclabel");
m.put("fffvalue", "bcclabel");
m.put("gggvalue", "ccclabel");
m.put("hhhvalue", "ccalabel");
m.put("iiivalue", "caalabel");
m.put("jjjvalue", "aaclabel");
m.put("kkkvalue", "acclabel");
m.put("lllvalue", "bbalabel");
m.put("mmmvalue", "baalabel");
listBoxEntryMap = Collections.unmodifiableMap(m);
}
private Map<String, String> parmMap;
public Map getParmMap()
{
if (null == this.parmMap)
{
this.parmMap = new LinkedHashMap<String, String>();
this.parmMap.put("inputval", "");
}
return this.parmMap;
}
public void setParmMap(Map m)
{
this.parmMap = m;
}
List<Item> testList = new ArrayList<Item>();
public void setTestList(List<Item> data) throws IOException
{
testList = data;
}
public List<Item> getTestList()
{
try
{
if (null == testList || testList.isEmpty())
{
testList = getListOfItems();
}
return testList; //(list);
}
catch (Exception ex)
{
java.util.logging.Logger.getLogger(RefData.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
public static List<Item> getListOfItems()
{
List<Item> list = null;
try
{
Map<String, String> map = listBoxEntryMap;
Iterator iter = map.keySet().iterator();
list = new ArrayList<Item>();
Item item = null;
while (iter.hasNext())
{
String key = (String) iter.next();
String val = (String) map.get(key);
item = new Item();
item.setLabel(key);
item.setValue(val);
list.add(item);
}
}
catch (Exception e)
{
System.out.println("Exception during query call..." + e.getMessage());
e.printStackTrace();
}
return list;
}
}
FWIW - Here is the pom.xml (which includes the primefaces dependency)...
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>aaa.bbb.ccc</groupId>
<artifactId>testprimeac-war</artifactId>
<packaging>war</packaging>
<version>1</version>
<name>testprimeac-war</name>
<url>http://maven.apache.org</url>
<properties>
<org.springframework-version>3.1.1.RELEASE</org.springframework-version>
<jsf-version>2.1.11</jsf-version>
</properties>
<dependencies>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>2.1.11</version>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-impl</artifactId>
<version>2.1.11</version>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>el-api</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>el-impl</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<dependency>
<groupId>org.primefaces</groupId>
<artifactId>primefaces</artifactId>
<version>3.4.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
</plugin>
</plugins>
<finalName>testprimeac-${project.version}</finalName>
</build>
</project>
+1 for the assertions you made in your first paragraph: You do have too much information on here and the converter is unnecessary since you're binding to a basic String type.
The two main issues I see here Are
your choice to not use a type-safe collection in your value binding. Using a plain LinkedHashMap to hold String values will most likely cause problems as they will be stored as objects, I don't think the compiler is obliged to do any autoboxing for you here.
Your backing completeMethod implementation is essentially returninglkp a store of String objects. This is what provides the variable for var in the autocomplete . To state the obvious, you can't call getLabel() on that.
You can proceed either of 2 ways
Change your data store to a typesafe LinkedList of Strings , Lose the converter and you should be fine. Your var will be plain item and itemLabel and itemValue will both be #{item}.
Implement a POJO to encapsulate the selection object, keep the converter and then your backing data store becomes a simple list of your POJO and also your selection becomes an instance of the POJO instead of a String
EDIT: With your clarifications, the problem is as a result of the discrepancy in the type returned by the autocomplete dropdown (Type item) and the value binding of the autocomplete to the backing bean (String type). The type returned by the dropdown selection must be the same as the type you're binding to in the backing bean.

Resources