Does UserDetailsService username must be the same as UserDetails username by contract? - java

public interface UserDetailsService {
/**
* Locates the user based on the username. In the actual implementation, the search
* may possibly be case sensitive, or case insensitive depending on how the
* implementation instance is configured. In this case, the <code>UserDetails</code>
* object that comes back may have a username that is of a different case than what
* was actually requested..
* #param username the username identifying the user whose data is required.
* #return a fully populated user record (never <code>null</code>)
* #throws UsernameNotFoundException if the user could not be found or the user has no
* GrantedAuthority
*/
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;}
loadUserByUsername param is username
public interface UserDetails extends Serializable {
/**
* Returns the username used to authenticate the user. Cannot return
* <code>null</code>.
* #return the username (never <code>null</code>)
*/
String getUsername()}
getUsername return username
Does this username must be the same value in both by interface contract?
For example i want loadUserByUsername(String username) username be an email and String getUsername() be an UserID. Load by email, but return UserDetails with username as UserID.

You cannot, if you load user by username you will search it form DB by username.

this is the way I implement it:
https://docs.spring.io/spring-security/site/docs/5.4.1/reference/html5/#servlet-authentication-jdbc
AppUserServices class
#Service
public class AppUserServices implements AppUserService{
#Autowired
private AppUserRepository repo;
#Override
public AppUser fetchByUsername(String username) {
return repo.findByUsername(username);
}
#Override
public void persist(AppUser appUser) {
repo.save(appUser);
}
}
AppUserDetailServices class
#Service
public class AppUserDetailServices implements UserDetailsService{
#Autowired
private AppUserService service;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<AppUser> user = Optional.ofNullable(service.fetchByUsername(username));
if(user.isPresent()) {
User dbuser = new User(
user.get().getUsername(),
user.get().getPassword(),
user.get().getAuthorities());
return dbuser;
} else {
throw new UsernameNotFoundException(username);
}
}
}

Related

Built in Spring-Boot BCrypt matches method doesn't work

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.

DELETE and PUT endpoints don't work after implementing Spring Security

I'm currently developing a fullstack web application with a React Frontend and Spring Boot backend. I've implemented Spring security and JWT for authentication, but I can't access my API endpoints (see Controller) ever since. I've managed to access the GET request endpoints, but none of the PUT or DELETE requests seem to work despite logging in on the backend before starting a request.
I've seen that disabling csrf solved the problem in another post, but I've never enabled it anyway, so that wouldn't do the trick for me.
WebSecurityConfig file:
#Configuration
#AllArgsConstructor
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/v*/registration/**")
.permitAll()
.anyRequest()
.authenticated().and()
.formLogin();
}
Controller (REST API)
#RestController
#RequestMapping(path = "/question")
#CrossOrigin("*")
public class QuestionController {
private final QuestionService questionService;
#Autowired
public QuestionController(QuestionService questionService) {
this.questionService = questionService;
}
#CrossOrigin("*")
#GetMapping("/all")
public ResponseEntity<List<Question>> getAllQuestions() {
List<Question> questions = questionService.findAllQuestions();
return new ResponseEntity<>(questions, HttpStatus.OK);
}
#CrossOrigin("*")
#GetMapping("/find/{id}")
public ResponseEntity<Question> getQuestionById(#PathVariable("id") Long id) {
Question question = questionService.findQuestionById(id);
return new ResponseEntity<>(question, HttpStatus.OK);
}
#CrossOrigin("*")
#PostMapping("/add")
public ResponseEntity<Question> addQuestion(#RequestBody Question question) {
Question newQuestion = questionService.addQuestion(question);
return new ResponseEntity<>(newQuestion, HttpStatus.CREATED);
}
#CrossOrigin("*")
#PutMapping("/update/{id}")
public ResponseEntity<Question> updateQuestion(#RequestBody Question question) {
Question updateQuestion = questionService.updateQuestion(question);
return new ResponseEntity<>(updateQuestion, HttpStatus.OK);
}
#CrossOrigin("*")
#DeleteMapping("(/delete/{id}")
public ResponseEntity<Question> deleteQuestion(#PathVariable("id") Long id) {
questionService.deleteQuestion(id);
return new ResponseEntity<>(HttpStatus.OK);
}
}
Example for the GET request endpoint that works:
Example for the DELETE request endpoint that doesn't work:
Edit: This is the Code for implementing UserDetailsService
#Service
#Autowired can be left out by using this annotation.
#AllArgsConstructor
public class BenutzerkontoService implements UserDetailsService {
private final static String USER_NOT_FOUND_MSG = "User with email %s not found";
private final BenutzerkontoRepository benutzerkontoRepository;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
private final ConfirmationTokenService confirmationTokenService;
public List<Benutzerkonto> findAllBenutzerkonto() {
// findAll() returns a list of all user objects
return benutzerkontoRepository.findAll();
}
/**
* This method is responsible for identifying the given email inside the database.
*
* #param email
* #return
* #throws UsernameNotFoundException
*/
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
return benutzerkontoRepository.findByEmail(email).orElseThrow(() -> new UsernameNotFoundException(String.format(USER_NOT_FOUND_MSG, email)));
}
/**
* The following function checks, whether the user already exists (by email) and registers the user with an
* encoded password, if the email address does not exist already.
*
* The user also gets a random JSON Web Token assigned
*
* #param benutzerkonto
* #return
*/
public String signUpUser(Benutzerkonto benutzerkonto) {
// Check whether user exists
boolean userExists = benutzerkontoRepository.findByEmail(benutzerkonto.getEmail()).isPresent();
if (userExists) {
throw new IllegalStateException("Email is already taken");
}
// Encode the user password
String encodedPassword = bCryptPasswordEncoder.encode(benutzerkonto.getPassword());
// Replace the plain text password with the encoded version
benutzerkonto.setPasswort(encodedPassword);
// Save user to database
benutzerkontoRepository.save(benutzerkonto);
// Create random String via the UUID class for using it as token
String token = UUID.randomUUID().toString();
// Instantiate ConfirmationToken class, which defines the token for account confirmation
ConfirmationToken confirmationToken = new ConfirmationToken(
token,
LocalDateTime.now(),
// Make token invalid after 15 minutes
LocalDateTime.now().plusMinutes(15),
benutzerkonto
);
// Save token to database
// TODO: Shouldn't it be saved by a confirmationTokenRepository object? Why does this also work?
confirmationTokenService.saveConfirmationToken(confirmationToken);
return token;
}
/**
* This function takes the email address as a parameter and enables/activates the email for logging in.
*
* #param email
* #return
*/
public int enableAppUser(String email) {
return benutzerkontoRepository.enableAppUser(email);
}
/**
* This method adds a new user account to the database, but it searches for the passed value of email
* inside the database first. The user object "benutzerkonto" will only be saved in the database repository,
* if the email does not exist already.
*
* #param benutzerkonto
*/
public void addNewUser(Benutzerkonto benutzerkonto) {
// userEmailPresence can be null, if the email does not exist in the database yet, which is why it's an Optional.
Optional<Benutzerkonto> userEmailPresence = benutzerkontoRepository.findBenutzerkontoByEmail(benutzerkonto.getUsername());
if (userEmailPresence.isPresent()) {
throw new IllegalStateException("Email already taken.");
} else {
benutzerkontoRepository.save(benutzerkonto);
}
}
}
Edit2: This is the user class
#Getter
#Setter
#EqualsAndHashCode
#NoArgsConstructor
#Entity
#Table
public class Benutzerkonto implements Serializable, UserDetails {
#SequenceGenerator(
name = "student_sequence",
sequenceName = "student_sequence",
allocationSize = 1
)
#Id
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "student_sequence"
)
#Column(nullable = false)
private Long id;
private String email;
private String passwort;
#Column(nullable = false, updatable = false)
#Enumerated(EnumType.STRING)
private UserRole rolle;
private Boolean locked = false;
// false by default, because user has to confirm via email first
private Boolean enabled = false;
// Constructor
public Benutzerkonto(String email, String passwort, UserRole rolle) {
this.email = email;
this.passwort = passwort;
this.rolle = rolle;
}
#Override
public String toString() {
return "Benutzerkonto{" +
"id=" + id +
", email='" + email + '\'' +
", passwort='" + passwort + '\'' +
", rolle=" + rolle +
", locked=" + locked +
", enabled=" + enabled +
'}';
}
// Methods of UserDetails interface
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(rolle.name());
return Collections.singletonList(authority);
}
#Override
public String getPassword() {
return passwort;
}
#Override
public String getUsername() {
return email;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return !locked;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return enabled;
}
}
So apart from requests coming to /api/v*/registration/** others are secured. What does that mean?, it means until you have authorized users having authorized roles cannot access any other endpoint. So you need to do some things like:
implement UserDetails of package org.springframework.security.core.userdetails and implement the method:
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles == null?null:roles.stream().map(m->new SimpleGrantedAuthority(m.getAuthority())).collect(Collectors.toSet());
}
Add roles to your entity class:
#OneToMany(fetch = FetchType.EAGER,cascade = CascadeType.PERSIST)
#JoinTable(
name = "user_role",
joinColumns = #JoinColumn(
name = "user_id",
referencedColumnName = "id"
),
inverseJoinColumns = #JoinColumn(
name = "role_id",
referencedColumnName = "id"
))
private List<Role> roles;
Use those roles in your endpoint:
#PreAuthorize(hasRole('ROLE_role_name'))
#GetMapping(path = EndPoint.PATIENT_HOME, consumes = "application/json", produces = "application/json")
public ResponseEntity<YourDTO> Home(Principal principal) {
return new ResponseEntity<YourDTO>(yourDTO, HttpStatus.OK);
}

Spring Security : Could access to current user principle when using UserDetailesService

I have a AuthenticationProvider with type userDetailsService as follow:
public class CustomUDS implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String clientId) throws UsernameNotFoundException, DataAccessException {
ClientDetails client=clientDetailsService.loadClientByClientId(clientId);
String password=client.getClientSecret();
boolean enabled=true;
boolean accountNonExpired=true;
boolean credentialsNonExpired=true;
boolean accountNonLocked=true;
List<GrantedAuthority> authorities=new ArrayList<GrantedAuthority>();
GrantedAuthority roleClient=new SimpleGrantedAuthority("ROLE_CLIENT");
authorities.add(roleClient);
return new User(clientId,password,enabled,accountNonExpired,credentialsNonExpired,accountNonLocked,authorities);
}
}
when user is logged and I use follow statement to fetch user's username, it returns string value of User class:
Object object = SpringSecurityContext.getcontext().getAuthentication.getPrinciple();
object value will be string
"org.springframework.security.core.userdetails.User#db343434: Username
...."
and I could not convert return object to User or UserDetails.
now, how can I access to user's username?
So try this to get username:
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String name = userDetails.getUsername();

Ensure that users can only access their data in a REST API request

I'm working REST API right now with Jersey, Spring. Which shall be accessed with an Android/iOS later.
If I have for example user settings like this #Path("/user/{userID}/settings") in a Jersey Resource. How can i ensure that every user can access his/her settings only? I have read a lot about spring-security-oauth2. But as far as i understand you can only verify that the user is really the user but not make a difference if he/she can access other users settings?!
What you need is called authorization.
Authorization can distingush level of accesses of each users.
It's major topic actually. You can use roles, permissions, ACL for securing the service.
The simplest way for solving your task is to use path /user/settings and find the authentication yourseft, like:
public Settings find() {
UserDetails customUserDetails = (CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
return setingsService.findByUser(customUserDetails.getUsername());
}
Create your CustomUserDetails:
public class CustomUserDetails implements UserDetails {
private User user;
private Collection<? extends GrantedAuthority> authorities;
public CustomUserDetails(User user) {
this.user = user;
}
public User getUser() {
return user;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorities = new ArrayList<>();
for (Authority authority : user.getAuthorities()) {
authorities.add(new SimpleGrantedAuthority(authority.getName()));
}
return authorities;
}
#Override
public String getPassword() {
return user.getPassword();
}
#Override
public String getUsername() {
return user.getUsername();
}
// implement other methods
}
And CustomUserDetailsService:
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UserService userService;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.findByUsername(username);
// check if exist
}
return new CustomUserDetails(user);
}
}

java.lang.ClassCastException: org.springframework.security.core.userdetails.User cannot be cast to model.User

I am using Spring Security in my application. I need loggedIn user details in the controllers of my application.
For that I am using this code
User loggedInUser = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
But on running this code I get a classcastexception
java.lang.ClassCastException: org.springframework.security.core.userdetails.User cannot be cast to model.User
To fix this I referred to this article
Initially I used a CustomUserServiceDetails class
#Service("myUserDetailService")
#Transactional
public class CustomUserDetailsService implements UserDetailsService {
private static final Logger logger = Logger.getLogger(CustomUserDetailsService.class);
#Autowired
private UserDAO userDAO;
public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException, DataAccessException {
// returns the get(0) of the user list obtained from the db
User domainUser = userDAO.getUser(name);
logger.debug("User fetched from database in loadUserByUsername method " + domainUser);
Set<Role> roles = domainUser.getRole();
logger.debug("role of the user" + roles);
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
for(Role role: roles){
authorities.add(new SimpleGrantedAuthority(role.getRole()));
logger.debug("role" + role + " role.getRole()" + (role.getRole()));
}
boolean credentialNonExpired = true;
return new org.springframework.security.core.userdetails.User(domainUser.getProfileName(), domainUser.getPassword(), domainUser.isAccountEnabled(),
domainUser.isAccountNonExpired(), credentialNonExpired, domainUser.isAccountNonLocked(),authorities);
}
}
But after referring to the article I removed the setting of GrantedAuthorities from here and moved it to my User class. Implemented spring-security UserDetails class in my User class
Now I have an extra property in my User class
#Entity
#Table(name = "user")
public class User implements UserDetails {
private Collection<GrantedAuthority> authorities;
with a setMethod
public void setAuthorities(Set<Role> roles) {
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
for(Role role: roles){
authorities.add(new SimpleGrantedAuthority(role.getRole()));}
}
A. I am not sure how to map this property to the database. The existing User table schema doesn't contain a GrantedAuthority column besides It's not even a primitive type. I am using Hibernate for object mapping. Can anyone advice me the correct approach to obtain the user class info in the controllers?
B. I also considered the approach of extending the spring's User class and overloading the constructor of my User class. But then every time I initialize my User anywhere in the code I have to provide all the constructors parameters which is not good at all.
Instead of using
User loggedInUser = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
try this
Authentication loggedInUser = SecurityContextHolder.getContext().getAuthentication();
String username = loggedInUser.getName();
References:
https://www.mkyong.com/spring-security/get-current-logged-in-username-in-spring-security/
Fixed the issue
Solution
Created a CustomUserDetail class which implements Spring's UserDetails interface. Injected my model User class in it.
public class CustomUserDetail implements UserDetails{
private static final long serialVersionUID = 1L;
private User user;
Set<GrantedAuthority> authorities=null;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
public void setAuthorities(Set<GrantedAuthority> authorities)
{
this.authorities=authorities;
}
public String getPassword() {
return user.getPassword();
}
public String getUsername() {
return user.getProfileName();
}
public boolean isAccountNonExpired() {
return user.isAccountNonExpired();
}
public boolean isAccountNonLocked() {
return user.isAccountNonLocked();
}
public boolean isCredentialsNonExpired() {
return user.isCredentialsNonExpired();
}
public boolean isEnabled() {
return user.isAccountEnabled();
}
}
CustomUserServiceDetails
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UserDAO userDAO;
public CustomUserDetail loadUserByUsername(String name) throws UsernameNotFoundException, DataAccessException {
// returns the get(0) of the user list obtained from the db
User domainUser = userDAO.getUser(name);
Set<Role> roles = domainUser.getRole();
logger.debug("role of the user" + roles);
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
for(Role role: roles){
authorities.add(new SimpleGrantedAuthority(role.getRole()));
logger.debug("role" + role + " role.getRole()" + (role.getRole()));
}
CustomUserDetail customUserDetail=new CustomUserDetail();
customUserDetail.setUser(domainUser);
customUserDetail.setAuthorities(authorities);
return customUserDetail;
}
}
In my controller method
CustomUserDetail myUserDetails = (CustomUserDetail) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Integer userId=myUserDetails.getUser().getUserId(); //Fetch the custom property in User class
The method .getPrincipal() returns the object created and returned it in the method loadUserByUsername.
If you want an User you must return in the method loadUserByUsername an User, not an org.springframework.security.core.userdetails.User

Categories