2

I'm creating a website using Spring Boot. I have a template (thymeleaf) in which users can change their password. But when the user writes a new password the password doesn't change. It redirects to my homepage but nothing happens. There is no error.

My back end for changing passwords:

@Controller

@GetMapping("/reset") public String infoLogin(Model model){ 
model.addAttribute("users",new Users()); return "login/reset";
}

@PostMapping("/reset/{username}") public String pass(@PathVariable("username") String username){ 
userService.findUsername(username); return "redirect:/index"; 
}

@Service

@Transactional @PreAuthorize("#username == authentication.name") public Users findUsername(String username){ 
return this.usersRepo.findByUsername(username); 
}

@Repository

@Repository public interface UsersRepo extends JpaRepository<Users,Long> {

Users findByUsername(String username);
}

security configs:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter  {
    
    private final UserService userService;
    
    @Autowired
    public SecurityConfig( UserService userService) {
        this.userService = userService;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/guest/**","/index/**","/images/**","/css/**","/fonts/**","/js/**","/scripts/**","/server/**","/src/**")
                .permitAll()
                .antMatchers("/admin/*","/user/*").hasAuthority("ADMIN")
                .antMatchers("/user/*").hasAuthority("USER")
                .anyRequest().authenticated()
                          .and().formLogin().loginPage("/guest/login").defaultSuccessUrl("/index",true).permitAll()
                .and().rememberMe().rememberMeCookieName("remember").rememberMeParameter("remember")
                .and().logout().deleteCookies("remember").permitAll()
                .and()
                .exceptionHandling().accessDeniedPage("/guest/403");
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
       User.UserBuilder userBuilder=User.withDefaultPasswordEncoder();
       auth.userDetailsService(userService);
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return NoOpPasswordEncoder.getInstance();
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

template:

<form method="post"  th:action="@{/user/reset/{username}(username=${#authentication.principal.getUsername})}" th:object="${users}">
    <input type="hidden" th:field="*{id}">
    <div class="form-group">
        <label>Username</label>
        <input type="text" name="username" class="form-control" th:field="*{username}">
    </div>
    <div class="form-group">
        <label>New Password</label>
        <input type="password" id="password" th:field="*{password}" class="form-control">
    </div>
    <div class="form-group text-right">
        <input class="btn btn-primary btn-block" type="submit">
    </div>
</form>

In the database, it roughly looks like the following:

id email username password ...
34 something@gmail.com mehdi22 12345678 ...
@Entity
@Table(name = "tbl_users")
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Users implements Serializable, UserDetails {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private boolean enabled =true;

    @NotBlank
    @Size(min = 4,max = 10)
    private String username;
    @NotBlank
    @Size(min = 2,max = 20)
    private String name;
    @Email
    @Column(unique = true)
    private String email;
    @NotBlank
    @Size(min = 8,max = 30)
    private String password;

    @OneToMany(mappedBy = "user")
    private List<Posts> posts;

    @ElementCollection(targetClass = Roles.class,fetch = FetchType.EAGER)
    @CollectionTable(name = "authorities",joinColumns =
    @JoinColumn(name = "username",referencedColumnName = "username") )
    @Enumerated(EnumType.STRING)
    private List<Roles> roles;

    public Users() {
    }

    public Users(String username, String name, String email, String password) {
        this.username = username;
        this.name = name;
        this.email = email;
        this.password = password;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public List<Posts> getPosts() {
        return posts;
    }

    public void setPosts(List<Posts> posts) {
        this.posts = posts;
    }

    public void setEnabled(boolean enable) {
        this.enabled = enable;
    }

    public List<Roles> getRoles() {
        return roles;
    }

    public void setRoles(List<Roles> roles) {
        this.roles = roles;
    }


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return roles;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }
}
mahboy
  • 23
  • 2
  • 1
    Note: Don't store passwords in clear-text in your database. Instead, use hashing with a proper `PasswordEncoder` (not `NoOpPasswordEncoder`). – dan1st Dec 23 '22 at 11:30

1 Answers1

1

But when the user writes a new password the password doesn't change.

You are just not changing the password anywhere. When you send the form and /reset/{username} is called, you are not even looking at the password.

@PostMapping("/reset/{username}")
public String pass(@PathVariable("username") String username, @RequestParam("password") String password){ 
    userService.changePassword(username,password);
    return "redirect:/index"; 
}

and in your service:

@Autowired
private PasswordEncoder passwordEncoder;

@Transactional
@PreAuthorize("#username == authentication.name")
public void changePassword(String username, String password){ 
    Users user= this.usersRepo.findByUsername(username); 
    user.setPassword(this.passwordEncoder.encode(password));
    this.usersRepo.save(user);
}

Aside from that, you should hash your password. Imagine someone unauthorized gets access to your database. They can just access all your user's passwords.

For hashing passwords, change your PasswordEncoder to not use NoOpPasswordEncoder

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

Also note that you shouldn't show users their own passwords (or hashes).

For this, change

<input type="password" id="password" th:field="*{password}" class="form-control">

to

<input type="password" id="password" name="password" class="form-control">

I would also recommend you to not rely on @PreAuthorize for checking that the user actually changed their own password. Instead, I would do something like:

@PostMapping("/reset")
public String pass(Authentication authentication, @RequestParam("password") String password){ 
    userService.changePassword(authentication.getName(), password);
    return "redirect:/index"; 
}

this always changes the password of the current user. it also doesn't require the username to be sent from the frontend allowing you to simplify your form:

<form method="post" th:action="@{/user/reset" th:object="${users}">

Without th:object, the whole form could look like this:

<form method="post" th:action="@{/user/reset"">
    <input type="hidden" th:field="*{id}">
    <div class="form-group">
        <label>Username</label>
        <label th:text="${users.username}"></label>
    </div>
    <div class="form-group">
        <label>New Password</label>
        <input type="password" id="password" name="password" class="form-control">
    </div>
    <div class="form-group text-right">
        <input class="btn btn-primary btn-block" type="submit">
    </div>
</form>
dan1st
  • 12,568
  • 8
  • 34
  • 67
  • which library did you use for (Authentication authentication) ? – mahboy Dec 24 '22 at 19:38
  • [`Authentication`](https://javadoc.io/doc/org.springframework.security/spring-security-core/6.0.0/org/springframework/security/core/Authentication.html) is a class present with Spring Security (in `org.springframework.security.core`). Alternatively, you could use `Principal` (present in the JVM system libraries). - It should be present in your project. – dan1st Dec 25 '22 at 12:16