How to return a JWTtoken when users registers for the first time in springboot - spring-boot

I pretty new to Springboot with jwt, I need to generate a jwt token when users sign up for the first time and use the token to allow redirect to another page instead of returning user registered successfully message. How can I achieve this? I am able to generate a token when users signin and use the token for authentication. any help will be appreciated.
how am generating on signin method
#PostMapping("/signin")
public ResponseEntity<?> authenticateUser(#Valid #RequestBody LoginRequest loginRequest) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = jwtUtils.generateJwtToken(authentication);
UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
List<String> roles = userDetails.getAuthorities().stream()
.map(item -> item.getAuthority())
.collect(Collectors.toList());
return ResponseEntity.ok(new JwtResponse(jwt,
userDetails.getId(),
userDetails.getUsername(),
userDetails.getEmail(),
roles));
}
my signup method.
#PostMapping("/signup")
public ResponseEntity<?> registerUser(#Valid #RequestBody SignupRequest signUpRequest) {
if (userRepository.existsByUsername(signUpRequest.getUsername())) {
return ResponseEntity
.badRequest()
.body(new MessageResponse("Error: Username is already taken!"));
}
if (userRepository.existsByEmail(signUpRequest.getEmail())) {
return ResponseEntity
.badRequest()
.body(new MessageResponse("Error: Email is already in use!"));
}
// Create new user's account
User user = new User(signUpRequest.getUsername(), signUpRequest.getEmail(),
encoder.encode(signUpRequest.getPassword()),signUpRequest.getFirstname(),
signUpRequest.getSurname(),signUpRequest.getTelephoneno(),
signUpRequest.getWhatsappno());
Set<String> strRoles = signUpRequest.getRole();
Set<Role> roles = new HashSet<>();
if (strRoles == null) {
Role userRole = roleRepository.findByName(ERole.ROLE_USER)
.orElseThrow(() -> new RuntimeException("Error: Role is not found."));
roles.add(userRole);
} else {
strRoles.forEach(role -> {
switch (role) {
case "admin":
Role adminRole = roleRepository.findByName(ERole.ROLE_ADMIN)
.orElseThrow(() -> new RuntimeException("Error: Role is not found."));
roles.add(adminRole);
break;
case "mod":
Role modRole = roleRepository.findByName(ERole.ROLE_MODERATOR)
.orElseThrow(() -> new RuntimeException("Error: Role is not found."));
roles.add(modRole);
break;
default:
Role userRole = roleRepository.findByName(ERole.ROLE_USER)
.orElseThrow(() -> new RuntimeException("Error: Role is not found."));
roles.add(userRole);
}
});
}
user.setRoles(roles);
userRepository.save(user);
return ResponseEntity.ok(new MessageResponse("User registered successfully!"));
//return ResponseEntity.ok(jwt);
}
how can I generate a token when users sign instead of return ResponseEntity.ok(new MessageResponse("User registered successfully!"));

Related

How to make register and login work correctly in spring security, problems with authentication

I'm trying to make authorization in my project. Registration works great and I get access and refresh tokens, but login throws an exception.
Problem is that in registration I create UsernamePasswordAuthenticationToken with UserEntity and password:
#PostMapping("/register")
public ResponseEntity register(#RequestBody UserSignUpModel userSignUpModel) {
UserEntity user = customUserDetailsService.createUser(userSignUpModel);
Authentication authentication = UsernamePasswordAuthenticationToken.authenticated(user, user.getPassword(), Collections.EMPTY_LIST);
return ResponseEntity.ok(tokenGenerator.createToken(authentication));
}
And in login I create UsernamePasswordAuthenticationToken with email and password so that the daoAuthenticationProvider can process it:
#PostMapping("/login")
public ResponseEntity login(#RequestBody UserSignInModel userSignInModel) {
Authentication authentication = daoAuthenticationProvider.authenticate(UsernamePasswordAuthenticationToken.unauthenticated(userSignInModel.getEmail(), userSignInModel.getPassword()));
return ResponseEntity.ok(tokenGenerator.createToken(authentication));
}
So I receive different authentication objects and can't pass the instanceoff check in TokenGenerator:
public TokenModel createToken(Authentication authentication) {
if (!(authentication.getPrincipal() instanceof UserEntity user)) {
throw new BadCredentialsException(
MessageFormat.format("principal {0} is not of UserEntity or UserDetails type", authentication.getPrincipal().getClass())
);
}
TokenModel tokenModel = new TokenModel();
tokenModel.setUserId(user.getUser_id());
tokenModel.setAccessToken(createAccessToken(authentication));
String refreshToken;
if (authentication.getCredentials() instanceof Jwt jwt) {
Instant now = Instant.now();
Instant expiresAt = jwt.getExpiresAt();
Duration duration = Duration.between(now, expiresAt);
long secondsUntilExpired = duration.toSeconds();
if (secondsUntilExpired < 15) {
refreshToken = createRefreshToken(authentication);
} else {
refreshToken = jwt.getTokenValue();
}
} else {
refreshToken = createRefreshToken(authentication);
}
tokenModel.setRefreshToken(refreshToken);
return tokenModel;
}
Should I write custom authentication provider? If so, do I need to download an entity from the repository and check passwords in it?

WebClient: Waiting for blocking call in API to perform a new call

I am currently making a microservice to create test users for our automated test environment. The database is reachable through another API, so in order to create a test user, I need to perform a call to this API.
The test users should be created and then later disposed of when the test has been executed. The identifier of the test users is the SSN (national identifier digit), and it is unique to each citizen. My API/microservice generates a new user with a generated SSN and should post it to a DB over the API to the backend service controlling the database. This backend service is not reactive.
The problem is that in the database, there are already many existing users which are used by other tests executed manually. The existing test users cannot be tampered with, so I need to verify that the generated SSN is not already existing in the DB.
My approach is as follows:
generate a new ssn
while(ssn exists in db){
generate new ssn
}
post generated user to db
However, when placing a .block() on the check if the user exists (bad practice, I know...) the program halts in a deadlock and nothing happens.
My controller:
#ResponseBody
#PostMapping("normal")
public Mono<User> createNormalUser() throws Exception {
return userService.createNormalUser();
}
#ResponseBody
#GetMapping("{ssn}")
public Mono<User> getUserBySSN(#PathVariable String ssn){
return userService.getUserBySsn(ssn);
}
My service:
public Mono<User> createNormalUser(){
String ssn = generateNewSsnNotInDB();
Mono<UserResource> newUserMono = Mono.just(
UserResource.builder()
.ssn(ssn)
.email(ssn + "-test#somedomain.com")
.state("NORMAL")
.preferred2FaMethod("some2FAMethod")
.build()
);
return postUser(newUserMono)
.then(updatePassword(ssn))
.then(setState(ssn, "NORMAL"));
}
private String generateNewSsnNotInDB() {
String ssn;
boolean userExists = false;
do {
ssn = ssnGenerator.generateOneValidSsnOnDate(ssnGenerator.generateRandomSsnDate());
userExists = checkIfUserExists(ssn);
} while (userExists);
return ssn;
}
private boolean checkIfUserExists(String ssn) {
User user;
try {
user = getUserBySsn(ssn).share().block();
return true;
} catch (WebClientResponseException.NotFound exception) {
return false;
}
}
public Mono<User> getUserBySsn(String ssn) {
return webClient.get()
.uri(userBySsnURI(ssn))
.retrieve()
.bodyToMono(User.class);
}
public Mono<User> postUser(Mono<UserResource> userMono) {
return webClient.post()
.uri(setUserURI())
.body(userMono, UserResource.class)
.retrieve()
.bodyToMono(User.class);
}
public Mono<User> postUser(User user) {
user.setPid(generateNewSsnNotInDB());
UserResource res = UserResource.builder()
.ssn(user.getPid())
.email(user.getEmail())
.phoneNumber(user.getPhoneNumber())
.state(user.getState())
.preferred2FaMethod(user.getPreferred2FaMethod())
.password(user.getPassword())
.build();
log.info("Resource generated in post-user is: " + res.toString());
return postUser(Mono.just(res));
}
public Mono<User> updatePassword(String ssn) {
Mono<User> user = Mono.just(User.builder()
.pid(ssn)
.password("password01")
.build());
return webClient.patch()
.uri(setUpdatePasswordURI())
.body(user, User.class)
.retrieve()
.bodyToMono(User.class);
}
private Mono<User> setState(String ssn, String state) {
return webClient.put()
.uri(updateStateURI(ssn, state))
.retrieve()
.bodyToMono(User.class);
}
I have chained the calls in the createNormalUser function because the backend requires this sequence in order to set the required attributes for the user. I am not sure why this is the required sequence, and changing this is not part of my scope.
I have also omitted some functions which probably aren't relevant for this question.
Can somebody please help me in the right direction on how to perform the calls with checkIfUsersExist and then post the user? I have been trying to wrap my head around this for a week now with no luck.
The strangest thing is that if I first call getUser with a valid ssn, then postUser works fine. If I try to call postUser without calling getUser first, it deadlocks on the .block().
Avoid the block() call and user chained calls instead as follows (createNormalUser() and generateNewSsnNotInDB() were updated and checkIfUserExists() deleted):
public Mono<User> createNormalUser(){
Mono<UserResource> newUserMono = generateNewSsnNotInDB().map( ssn ->
UserResource.builder()
.ssn(ssn)
.email(ssn + "-test#somedomain.com")
.state("NORMAL")
.preferred2FaMethod("some2FAMethod")
.build()
);
return postUser(newUserMono)
.then(updatePassword(ssn))
.then(setState(ssn, "NORMAL"));
}
private Mono<String> generateNewSsnNotInDB() {
return Mono.just(ssnGenerator.generateOneValidSsnOnDate(ssnGenerator.generateRandomSsnDate()))
.flatMap(ssn -> getUserBySsn(ssn))
.switchIfEmpty(Mono.defer(() -> generateNewSsnNotInDB()));
}
public Mono<User> getUserBySsn(String ssn) {
return webClient.get()
.uri(userBySsnURI(ssn))
.retrieve()
.bodyToMono(User.class);
}
public Mono<User> postUser(Mono<UserResource> userMono) {
return webClient.post()
.uri(setUserURI())
.body(userMono, UserResource.class)
.retrieve()
.bodyToMono(User.class);
}
public Mono<User> postUser(User user) {
user.setPid(generateNewSsnNotInDB());
UserResource res = UserResource.builder()
.ssn(user.getPid())
.email(user.getEmail())
.phoneNumber(user.getPhoneNumber())
.state(user.getState())
.preferred2FaMethod(user.getPreferred2FaMethod())
.password(user.getPassword())
.build();
log.info("Resource generated in post-user is: " + res.toString());
return postUser(Mono.just(res));
}
public Mono<User> updatePassword(String ssn) {
Mono<User> user = Mono.just(User.builder()
.pid(ssn)
.password("password01")
.build());
return webClient.patch()
.uri(setUpdatePasswordURI())
.body(user, User.class)
.retrieve()
.bodyToMono(User.class);
}
private Mono<User> setState(String ssn, String state) {
return webClient.put()
.uri(updateStateURI(ssn, state))
.retrieve()
.bodyToMono(User.class);
}

How to test a reset password request with spring boot and postman?

I'm currently working on a reset password for users and I had a problem testing the method. This is the code below:
#PostMapping("/user/resetPassword")
public GenericResponse resetPassword(HttpServletRequest request, #RequestBody String userEmail) {
System.out.println("Here we are in the method of the reset *********");
User user = userRepository.findByEmail1(userEmail);
System.out.println ( user.getId());
System.out.println("This is my user"+user);
if (user == null) {
throw new UserNotFoundException();
}
String token = UUID.randomUUID().toString();
userService.createPasswordResetTokenForUser(user, token);
mailSender.send(constructResetTokenEmail(getAppUrl(request), request.getLocale(), token, user));
return new GenericResponse(messages.getMessage("message.resetPasswordEmail", null, request.getLocale()));
}
The problem is in the findByEmail it is not returning the user from the email that I am inserting in the JSON in the Postman payload:
User findByEmail(String email);
the result of the execution is UserNotFoundException.

How to modify spring SecurityContextHolder.getContext().getAuthentication() object after successful login?

I'm working on spring boot application where i've created a CustomUserDetails class by extending UserDetails as follows..
public class CustomUserDetails
extends org.springframework.security.core.userdetails.User {
private static final long serialVersionUID = 1L;
/**
* The extra field in the login form is for the tenant name
*/
private String tenant;
private Long userId;
private String firstName;
private String middleName;
private String lastName;
private String email;
private String role;
i need to modify tenant details in UserDetails object. For this i've checked following
How to update Spring Security UserDetails impls after successful login?
https://stackanswers.net/questions/how-to-immediately-enable-the-authority-after-update-user-authority-in-spring-security
https://dev.to/onlineinterview/user-account-loginregistration-feature-with-spring-boot--3fc3
And Controller is here where i'm updating authentication object:
#PreAuthorize("hasRole('SUPER_ADMIN')")
#GetMapping(path = "/useTenant/{tenantId}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<ResponseDTO> useTenant(#PathVariable Long tenantId) {
HttpStatus status = HttpStatus.OK;
boolean error = false;
String message = languageMessageService.getMessage(MultiLanguageKey.SUCCESS);
// fetch master tenant by id
Optional<MasterTenant> optional = masterTenantService.findById(tenantId);
if (optional.isPresent()) {
CustomUserDetails customUserDetails = customUserDetailsService.getUserDetail();
//Changing Tenant ID
customUserDetails.setTenant(optional.get().getTenantId());
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication instanceof UsernamePasswordAuthenticationToken) {
// Update Current user by changing tenant id in SecurityContextHolder
UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken) authentication;
auth.setDetails(customUserDetails);
SecurityContextHolder.getContext().setAuthentication(auth);
}
} else {
error = false;
message = languageMessageService.getMessage(MultiLanguageKey.TENANT_NOT_FOUND);
}
return new ResponseEntity<>(new ResponseDTO(error, message), status);
}
My problem is that when i'm hitting another request to perform particular action, i didn't find tenant detail in CustomUserDetails object which is fetched from
SecurityContextHolder.getContext().getAuthentication()
Please let me know how can i update or modify UserDetails object of Authentication and save back so another request get updated CustomUserDetails.
The UserDetails should be set to the Principal of the UsernamePasswordAuthenticationToken rather than Details as suggested by the java docs :
The AuthenticationManager implementation will often return an
Authentication containing richer information as the principal for use
by the application. Many of the authentication providers will create a
UserDetails object as the principal.
Details in UsernamePasswordAuthenticationToken is normally stored user 's IP address or certificate serial number etc.
So change it to :
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication instanceof UsernamePasswordAuthenticationToken) {
// Update Current user by changing tenant id in SecurityContextHolder
UsernamePasswordAuthenticationToken currentAuth = (UsernamePasswordAuthenticationToken) authentication;
CustomUserDetails userDetail = currentAuth.getPrincipal();
customUserDetails.updateTenanet("blablalb");
UsernamePasswordAuthenticationToken updateAuth = new UsernamePasswordAuthenticationToken(userDetail ,
currentAuth.getCredentials(),
currentAuth.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(updateAuth);
}

Spring MVC variable resets for no reason

For some reason when I execute a GET request to a certain URI the variable that I need to access in that method loses its memory or points to null.
I have a form where a user can update his personal information. But when he enters a duplicate, it redirects him to a page that lets him know
I have : private static volatile User currentUser;
This field is set when a user logs in and the server performs a GET request to a REST API, which I programmed myself, and returns the User containing his info. This works as expected and the user info is displayed on his home screen.
Code for the above:
#RequestMapping(value = "/login", method = RequestMethod.POST)
public String login(#ModelAttribute Credentials credentials,
RedirectAttributes redirect) {
RestTemplate restTemplate = new RestTemplate();
RoleInfo roleInfo = restTemplate.postForObject(
"http://localhost:9090/users/login", credentials,
RoleInfo.class);
if (roleInfo != null) {
if (roleInfo.isAdmin()) {
redirect.addFlashAttribute("credentials", credentials);
return "redirect:/adminHome";
} else {
redirect.addFlashAttribute("credentials", credentials);
return "redirect:/getBasicUser";
}
} else {
return "login_fail";
}
}
#RequestMapping(value = "/getBasicUser", method = RequestMethod.GET)
public <T> String getBasicUser(#ModelAttribute Credentials credentials,
Model model, RedirectAttributes redirect) {
RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:9090/users/getBasicUser?username="
+ credentials.getUsername();
ResponseEntity<User> responseEntity = restTemplate.exchange(
url,
HttpMethod.GET,
new HttpEntity<T>(createHeaders(credentials.getUsername(),
credentials.getPassword())), User.class);
User user;
user = responseEntity.getBody();
currentUser = user;
System.out.println("current user: " + currentUser.getUsername());
if (user != null) {
userName = credentials.getUsername();
passWord = credentials.getPassword();
redirect.addFlashAttribute("credentials", credentials);
redirect.addFlashAttribute("user", user);
return "redirect:/basicHome";
} else {
return "register_fail";
}
}
So on "basicHome" he can view his information. Also on that page is a link to a form where he can edit the information:
#RequestMapping(value = "/edit", method = RequestMethod.GET)
public String getEditProfilePage(Model model) {
model.addAttribute("currentUser", currentUser);
System.out.println("current use firstname: " + currentUser.getFirstname());
model.addAttribute("user", new User());
return "edit_profile";
}
If an edit is successful he is returned back to his home page with the updated information.
The problem comes when he enters invalid info. He should be redirected back to the "/edit" URI and the currentUserfield should still hold his information but is actually null.
Here is the "/edit" PUT function:
#RequestMapping(value = "/edit", method = RequestMethod.PUT)
public <T> String editProfile(#ModelAttribute("user") User user,
#ModelAttribute("credentials") Credentials credentials,
RedirectAttributes redirect) {
RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:9090/users/update?username=" + userName;
HttpHeaders headers = createHeaders(userName,
passWord);
#SuppressWarnings({ "unchecked", "rawtypes" })
HttpEntity<T> entity = new HttpEntity(user, headers);
ResponseEntity<User> responseEntity = restTemplate.exchange(url,
HttpMethod.PUT, entity, User.class);
User returnedUser = responseEntity.getBody();
currentUser = returnedUser;
if (returnedUser != null) {
redirect.addFlashAttribute("user", returnedUser);
redirect.addFlashAttribute("credentials", credentials);
return "redirect:/basicHome";
} else {
return "redirect:/editFail";
}
}
I figured out what I had to do. I basically made "user" a session object in: #SessionAttributes("user")

Resources