I want to show right away with an example. There is such a repository:
#Repository
public interface UserRepository extends JpaRepository<User, Integer> {
String USER_CACHE = "users";
#Override
#CachePut(value = USER_CACHE, key = "#user.email", unless = "#result == null")
<S extends User> S save(S user);
#Override
#CacheEvict(value = USER_CACHE, key = "#user.email")
void delete(User user);
#Cacheable(value = USER_CACHE, key = "#email", unless = "#result == null")
User findByEmailIgnoreCase(String email);
}
And there is such a service that saves the user's changes and sends a confirmation code to the mail:
#Service
public class UserServiceImpl implements UserService, UserDetailsService {
private final UserRepository userRepository;
#Override
public User getUserByEmail(String email) {
return userRepository.findByEmailIgnoreCase(email);
}
#Override
#Transactional(isolation = Isolation.SERIALIZABLE)
public void createAppUser(RegistrationAppRequestDto registrationRequest) throws EmailSendingException {
User user = getUserByEmail(registrationRequest.getEmail());
user.setPassword(registrationRequest.getPassword());
user.setApp(true);
user.setActivated(false);
user.setActivationCode(UUID.randomUUID().toString());
user.setLastVisit(LocalDateTime.now());
if (Strings.isEmpty(user.getImg())) {
user.setImg(DEFAULT_IMG);
}
mailSender.sendWelcomeMessage(user);
userRepository.save(user);
}
}
And the problem is that in case of an error (For example, when sending a message to the mail), the changes that were made with this user will remain in the cache, and these changes will not get into the database (which is correct). Is there any practice of working with such a case? Alternatively, i can use object cloning, but I think this is a bad practice. I will be grateful for any help.
Related
I have a UserController that receives a UserDTO and creates/updates the user in the DB. The problem I'm getting is that I also have a login, and when I insert the username and password on the login form, I always get the 'Wrong Password.' exception, despite the credentials being correctly inserted.
One thing I suspect is that BCrypt is to blame, since due to the fact that it generates random salt while encoding, maybe, just maybe, the cipher text ends up being different and stuff, which is weird, since I assume that it should work. I want to know how can I fix this problem of the hashing being different & not being able to validate the userCredentials
I have tried for example encoding the received password and using the matches method via my autowired passwordEncoder, and I'm using my own authProvider.
Here's the code, let me know if you need anything else.
CustomAuthProvider.java
#Service
public class CustomAuthProvider implements AuthenticationProvider {
private final UserServiceImpl userServiceImpl;
private final BCryptPasswordEncoder passwordEncoder;
#Autowired
public CustomAuthProvider(UserServiceImpl userServiceImpl, BCryptPasswordEncoder passwordEncoder) {
this.userServiceImpl = userServiceImpl;
this.passwordEncoder = passwordEncoder;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
UserDetails userDetails = userServiceImpl.loadUserByUsername(username);
if (!passwordEncoder.matches(password, userDetails.getPassword())) { //The problem is here evidently.
throw new BadCredentialsException("Wrong password.");
}
return new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
Also, here's the loadUserByUsername method:
UserServiceImpl.java
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDTO user = this.getUserByUsername(username);
User anUser = convertToUser(user);
ModelMapper modelMapper = new ModelMapper();
return modelMapper.map(anUser,UserPrincipal.class);
}
}
And here is the save method I use to save and update users, as well as the LoginController;
#Override
public void save(UserDTO user) {
User aUser = this.convertToUser(user);
aUser.setPassword(passwordEncoder.encode(aUser.getPassword()));
this.userRepository.save(aUser); }
LoginController.java:
#RestController
public class LoginController{
private final CustomAuthProvider providerManager;
#Autowired
public LoginController(CustomAuthProvider providerManager) {
this.providerManager = providerManager;
}
#GetMapping("/login")
public String login() {
return "login";
}
#PostMapping("/login")
public String login(#RequestParam("username") #NotBlank String username,
#RequestParam("password") #NotBlank String password, Model model) {
if(username == null || password == null) { //This is probably not necessary
model.addAttribute("error", "Invalid credentials");
return "login";
}
try {
Authentication auth = providerManager.authenticate(
new UsernamePasswordAuthenticationToken(username, password)
);
SecurityContextHolder.getContext().setAuthentication(auth);
return "redirect:/notes";
} catch (AuthenticationException e) {
model.addAttribute("error", "Invalid credentials");
return "login";
}
}
}
UserPrincipal.java
#Data
public class UserPrincipal implements Serializable , UserDetails {
int id;
private String username;
private String password;
private Date accountCreationDate = new Date();
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
#Override
public boolean isAccountNonExpired() {
return false;
}
#Override
public boolean isAccountNonLocked() {
return false;
}
#Override
public boolean isCredentialsNonExpired() {
return false;
}
#Override
public boolean isEnabled() {
return false;
}
}
UserDTO.java
#Data
public class UserDTO implements Serializable {
int id;
private String username;
private String password;
private List<Note> notes = new ArrayList<>();
}
I read several issues related to this topic, like
Spring Boot PasswordEncoder.matches always false
Spring Security - BcryptPasswordEncoder
Inconsistent hash with Spring Boot BCryptPasswordEncoder matches() method
How can bcrypt have built-in salts?
Decode the Bcrypt encoded password in Spring Security to deactivate user account
but none of those helped me solve my issue and there was no real solution to the problem since most of them don't even have an accepted answer.
EDIT: Found out that the 'matches' method only works if I insert the hashed password, not the raw password.
Found out my mistake:
The setPassword method in the User class was re-hashing the hashed password which was already being hashed on the save method, thus the modelMapper.map() method used that setPassword method, therefore the passwords never matched and the password I got from the user class never matched the actual password I could see on my database.
I have below working code using for loop to iterate all the id in the user collection. Although this post could help me to this question, it returns specific value as well. I wonder how to get the same result without using it because I can't complete the return statement.
#Override
public User get(Object userId) {
FindIterable<User> userTbl = database.getCollection("User", User.class).find();
for (User doc : userTbl) {
String id = doc.getId().toHexString();
System.out.println("_id = " + id);
if (id.equals(userId)) {
return doc;
}
}
return null;
}
Well, if you just want to return one field, just do so.
#Override
public String get(Object userId) {
FindIterable<User> userTbl = database.getCollection("User", User.class).find();
for (User doc : userTbl) {
String id = doc.getId().toHexString();
System.out.println("_id = " + id);
if (id.equals(userId)) {
return doc.getUser();
}
}
return null;
}
But it should be easier to use a MongoRepository to get easier access to your data.
Repository example class (with Spring):
#Repository
public interface UserRepository extends MongoRepository<User, String> {
}
And your get() method:
#Autowired
private UserRepository userRepository;
#Override
public Optional<String> get(String userId) {
Optional<User> user = userRepository.findById(userId);
if (user.isPresent()) {
return Optional.of(user.get().getId());
}
return Optional.empty();
}
i've got a microservice which implements some optimization function by calling many times another microservice (the second one calculates so called target function value and the first micriservice changes paramters of this tagrget function)
It leads to necessity of writing some logic in Rest Controller layer. To be clear some simplified code will be represented below
#RestController
public class OptimizerController {
private OptimizationService service;
private RestTemplate restTemplate;
#GetMapping("/run_opt")
public DailyOptResponse doOpt(){
Data iniData = service.prepareData(null);
Result r = restTemplate.postForObject(http://calc-service/plain_calc", iniData, Result.class);
double dt = service.assessResult(r);
while(dt > 0.1){
Data newData = service.preapreData(r);
r = restTemplate.postForObject(http://calc-service/plain_calc", newData , Result.class);
dt = service.assessResult(r);
}
return service.prepareResponce(r);
}
As i saw in examples all people are striving to keep rest controller as simple as possible and move all logic to service layer. But what if i have to call some other microservices from service layer? Should i keep logic of data formin in service layer and return it to controller layer, use RestTemplate object in service layer or something else?
Thank you for your help
It is straightforward.
The whole logic is in the service layer (including other services).
Simple example:
Controller:
#RestController
#RequestMapping("/api/users")
public class UserController {
private final UserManager userManager;
#Autowired
public UserController(UserManager userManager) {
super();
this.userManager = userManager;
}
#GetMapping()
public List<UserResource> getUsers() {
return userManager.getUsers();
}
#GetMapping("/{userId}")
public UserResource getUser(#PathVariable Integer userId) {
return userManager.getUser(userId);
}
#PutMapping
public void updateUser(#RequestBody UserResource resource) {
userManager.updateUser(resource);
}
}
Service:
#Service
public class UserManager {
private static final Logger log = LoggerFactory.getLogger(UserManager.class);
private final UserRepository userRepository;
private final UserResourceAssembler userResourceAssembler;
private final PictureManager pictureManager;
#Autowired
public UserManager(
UserRepository userRepository,
UserResourceAssembler userResourceAssembler,
PictureManager pictureManager
) {
super();
this.userRepository = userRepository;
this.userResourceAssembler = userResourceAssembler;
this.pictureManager= pictureManager;
}
public UserResource getUser(Integer userId) {
User user = userRepository.findById(userId).orElseThrow(() -> new NotFoundException("User with ID " + userId + " not found!"));
return userResourceAssembler.toResource(user);
}
public List<UserResource> getUsers() {
return userResourceAssembler.toResources(userRepository.findAll());
}
public void updateUser(UserResource resource) {
User user = userRepository.findById(resource.getId()).orElseThrow(() -> new NotFoundException("User with ID " + resource.getId() + " not found!"));
PictureResource pictureResource = pictureManager.savePicture(user);
user = userResourceAssembler.fromResource(user, resource);
user = userRepository.save(user);
log.debug("User {} updated.", user);
}
}
Service 2:
#Service
public class PictureManager {
private static final Logger log = LoggerFactory.getLogger(PictureManager.class);
private final RestTemplate restTemplate;
#Autowired
public PictureManager(RestTemplate restTemplate) {
super();
this.restTemplate = restTemplate;
}
public PictureResource savePicture(User user) {
//do some logic with user
ResponseEntity<PictureResource> response = restTemplate.exchange(
"url",
HttpMethod.POST,
requestEntity,
PictureResource.class);
return response.getBody();
}
}
Repository:
public interface UserRepository extends JpaRepository<User, Integer> {
User findByUsername(String username);
}
I have class
public class CloneUserService {
private final UserRepository userRepository;
private final PersonRepository personRepository;
private final OrderRepository orderRepository;
public CloneUserService(UserRepository userRepository, PersonRepository personRepository, OrderRepository orderRepository) {
this.userRepository = userRepository;
this.personRepository = personRepository;
this.orderRepository = orderRepository;
}
public void createFromTemplate(String templateUserId) {
User templateUser = userRepository.getUserById(templateUserId);
Person templatePerson = personRepository.getPersonByUserId(templateUserId);
List<Order> templateOrders = orderRepository.getOrdersByUserId(templateUserId);
User newUser = cloneUserFromTemplate(templateUser);
newUser.setId("newId");
userRepository.save(newUser);
Person newPerson = clonePersonFromTemplate(templatePerson);
newPerson.setUser(newUser);
newPerson.setId("newId");
personRepository.save(newPerson);
for (Order templateOrder : templateOrders) {
Order newOrder = cloneOrderFromTemplate(templateOrder);
newOrder.setId("newId");
newOrder.setUSer(newUser);
orderRepository.save(newOrder);
}
}
private Order cloneOrderFromTemplate(Order templateOrder) {
//logic
return null;
}
private Person clonePersonFromTemplate(Person templatePerson) {
//logic
return null;
}
private User cloneUserFromTemplate(User templateUser) {
//logic
return null;
}
}
I need to test this method createFromTemplate.
I create this test. I create stabs for each repository and store saved object into this stub. And I add the additional method for getting this object for the assertion.
It works. But I have 2 problems:
My template object is mutable. It is not a big problem but it is a fact.
If I add new methods to repository interface I must implement it in stubs.
Mu question - How can I test cloned objects like theses from my example?
I don't use spring and H2DB or another in-memory database.
I have a MongoDB database.
If I use mockito I will not understand how to assert new objects in void method.
class CloneUserServiceTest {
private CloneUserService cloneUserService;
private UserRepositoryStub userRepository;
private PersonRepositoryStub personRepository;
private OrderRepositoryStub orderRepository;
#Before
public void setUp() {
User templateUser = new User();
Person templatePerson = new Person();
List<Order> templateOrders = Collections.singletonList(new Order());
userRepository = new UserRepositoryStub(templateUser);
personRepository = new PersonRepositoryStub(templatePerson);
orderRepository = new OrderRepositoryStub(templateOrders);
cloneUserService = new CloneUserService(userRepository, personRepository, orderRepository);
}
#Test
void createFromTemplate() {
cloneUserService.createFromTemplate("templateUserId");
User newUser = userRepository.getNewUser();
// assert newUser
Person newPerson = personRepository.getNewPerson();
// assert newPerson
Order newOrder = orderRepository.getNewOrder();
// assert newOrder
}
private static class UserRepositoryStub implements UserRepository {
private User templateUser;
private User newUser;
public UserRepositoryStub(User templateUser) {
this.templateUser = templateUser;
}
public User getUserById(String templateUserId) {
return templateUser;
}
public void save(User newUser) {
this.newUser = newUser;
}
public User getNewUser() {
return newUser;
}
}
private static class PersonRepositoryStub implements PersonRepository {
private Person templatePerson;
private Person newPerson;
public PersonRepositoryStub(Person templatePerson) {
this.templatePerson = templatePerson;
}
public Person getPersonByUserId(String templateUserId) {
return templatePerson;
}
public void save(Person newPerson) {
this.newPerson = newPerson;
}
public Person getNewPerson() {
return newPerson;
}
}
private static class OrderRepositoryStub implements OrderRepository {
private List<Order> templateOrders;
private Order newOrder;
public OrderRepositoryStub(List<Order> templateOrders) {
this.templateOrders = templateOrders;
}
public List<Order> getOrdersByUserId(String templateUserId) {
return templateOrders;
}
public void save(Order newOrder) {
this.newOrder = newOrder;
}
public Order getNewOrder() {
return newOrder;
}
}
}
In your scenario I would consider using mocking framework like Mockito.
Some main advantages:
Adding new methods to repository interface doesn't require implementing it in stubs
Supports exact-number-of-times and at-least-once verification
Allows flexible verification in order (e.g: verify in order what you want, not every single interaction)
Very nice and simple annotation syntax - #Mock, #InjectMocks, #Spy
Here is an example - maybe it will interest you:
// arrange
Warehouse mock = Mockito.mock(Warehouse.class);
//act
Order order = new Order(TALISKER, 50);
order.fill(warehouse); // fill will call remove() implicitly
// assert
Mockito.verify(warehouse).remove(TALISKER, 50); // verify that remove() method was actually called
When I execute this code, an order object remains in the DB at the end, in which the label is test. So the changing of the object is rolled back, but not the creation. My expectation is that the DB contains no order or chat channel entry after the call
#Autowired
private OrderRepository orderRepository;
#Autowired
private ChatChannelRepository chatChannelRepository;
#RequestMapping(value = "/order/price", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
#Transactional
public double calculatePrice() throws CustomException {
Order order = new Order();
order.setLabel("Test");
orderRepository.save(order);
ChatChannel channel = new ChatChannel();
channel.setOrder(order);
chatChannelRepository.save(channel);
order.setLabel("Test2");
orderRepository.save(order);
throw new RuntimeException("Test");
}
#Transactional
public interface ChatChannelRepository extends
CrudRepository<ChatChannel, Long> {
public ChatChannel findById(long id);
}
#Transactional
public interface OrderRepository extends CrudRepository<Order, Long>
{
public Order findById(long id);
public List<Order> findByBudgetIn(List<Budget> budgets);
public List<Order> findByBudgetInAndCurrentChainPosPhaseEquals(List<Budget> budgets, Phase phase);
public List<Order> findByProcessors(User currentUser);
}