Changed Authentication
This commit is contained in:
parent
15f747807b
commit
ab84002276
@ -7,12 +7,17 @@ import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.bernard.misael.model.Privilege;
|
||||
import com.bernard.misael.model.Role;
|
||||
import com.bernard.misael.model.User;
|
||||
import com.bernard.misael.repository.UserRepository;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Service
|
||||
public class CustomUserDetailsService implements UserDetailsService {
|
||||
@ -28,18 +33,19 @@ public class CustomUserDetailsService implements UserDetailsService {
|
||||
User user = userRepository.findByName(pseudo);
|
||||
|
||||
if (user != null) {
|
||||
Stream<Privilege> inducedPrivileges = user.getRoles().stream()
|
||||
.map(Role::getPrivileges)
|
||||
.map(Set<Privilege>::stream)
|
||||
.flatMap(Function.identity())
|
||||
.sorted()
|
||||
.distinct();
|
||||
Stream<Role> roles = user.getRoles().stream();
|
||||
return new org.springframework.security.core.userdetails.User(user.getName(),
|
||||
user.getPassword(),
|
||||
mapRolesToAuthorities(user.getRoles()));
|
||||
Stream.concat(inducedPrivileges, roles).toList()
|
||||
);
|
||||
}else{
|
||||
throw new UsernameNotFoundException("Invalid username or password.");
|
||||
}
|
||||
}
|
||||
|
||||
private Collection < ? extends GrantedAuthority> mapRolesToAuthorities(Collection<Role> roles) {
|
||||
Collection < ? extends GrantedAuthority> mapRoles = roles.stream()
|
||||
.map(role -> new SimpleGrantedAuthority(role.getName()))
|
||||
.collect(Collectors.toList());
|
||||
return mapRoles;
|
||||
}
|
||||
}
|
||||
@ -1,34 +1,24 @@
|
||||
package com.bernard.misael;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
|
||||
import com.bernard.misael.model.User;
|
||||
import com.bernard.misael.service.UserService;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableMethodSecurity(securedEnabled = true)
|
||||
public class SpringSecurity {
|
||||
|
||||
public static final Logger LOG = Logger.getLogger(SpringSecurity.class.getName());
|
||||
@ -43,11 +33,10 @@ public class SpringSecurity {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http.csrf().disable()
|
||||
http.csrf(csrf -> csrf.disable())
|
||||
.authorizeHttpRequests((authorize) ->
|
||||
authorize.requestMatchers("/**").permitAll()
|
||||
// .requestMatchers("/index").permitAll()
|
||||
// .requestMatchers("/users").hasRole("ADMIN")
|
||||
authorize
|
||||
.requestMatchers("/**").permitAll()
|
||||
).formLogin(
|
||||
form -> form
|
||||
.loginPage("/login")
|
||||
|
||||
14
src/main/java/com/bernard/misael/model/Privilege.java
Normal file
14
src/main/java/com/bernard/misael/model/Privilege.java
Normal file
@ -0,0 +1,14 @@
|
||||
package com.bernard.misael.model;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
||||
public enum Privilege implements GrantedAuthority {
|
||||
|
||||
LIST_USERS,ADD_USERS,LIST_QUIZZ;
|
||||
|
||||
@Override
|
||||
public String getAuthority() {
|
||||
return this.name();
|
||||
}
|
||||
|
||||
}
|
||||
@ -6,7 +6,12 @@ import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ -14,7 +19,7 @@ import java.util.List;
|
||||
@AllArgsConstructor
|
||||
@Entity
|
||||
@Table(name="roles")
|
||||
public class Role
|
||||
public class Role implements GrantedAuthority
|
||||
{
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@ -25,4 +30,22 @@ public class Role
|
||||
|
||||
@ManyToMany(mappedBy="roles")
|
||||
private List<User> users;
|
||||
|
||||
@ElementCollection(fetch = FetchType.EAGER)
|
||||
@Enumerated(EnumType.STRING)
|
||||
@CollectionTable(name = "role_privileges"
|
||||
, joinColumns = @JoinColumn(name = "id"))
|
||||
@Column(name = "privileges", nullable = false)
|
||||
private Set<Privilege> privileges;
|
||||
|
||||
public Role(String name){
|
||||
super();
|
||||
this.setName(name);
|
||||
this.setPrivileges(EnumSet.noneOf(Privilege.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthority() {
|
||||
return "ROLE_"+name;
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@ public class User
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(nullable=false)
|
||||
@Column(nullable=false,unique = true)
|
||||
private String name;
|
||||
|
||||
@Column(nullable=false)
|
||||
|
||||
74
src/main/java/com/bernard/misael/service/AdminMaker.java
Normal file
74
src/main/java/com/bernard/misael/service/AdminMaker.java
Normal file
@ -0,0 +1,74 @@
|
||||
package com.bernard.misael.service;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.bernard.misael.model.Privilege;
|
||||
import com.bernard.misael.model.Role;
|
||||
import com.bernard.misael.model.User;
|
||||
import com.bernard.misael.repository.RoleRepository;
|
||||
import com.bernard.misael.repository.UserRepository;
|
||||
|
||||
import jakarta.transaction.Transactional;
|
||||
|
||||
@Component
|
||||
public class AdminMaker implements
|
||||
ApplicationListener<ContextRefreshedEvent> {
|
||||
|
||||
boolean alreadySetup = false;
|
||||
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
|
||||
@Autowired
|
||||
private RoleRepository roleRepository;
|
||||
|
||||
@Autowired
|
||||
private PasswordEncoder passwordEncoder;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void onApplicationEvent(@NonNull ContextRefreshedEvent event) {
|
||||
|
||||
if (alreadySetup)
|
||||
return;
|
||||
Logger log = Logger.getLogger("AdminMaker");
|
||||
log.info("Checking that privileges and mysaa user exist");
|
||||
|
||||
Role adminRole = createRoleIfNotFound("ADMIN", EnumSet.allOf(Privilege.class));
|
||||
createRoleIfNotFound("USER", EnumSet.noneOf(Privilege.class));
|
||||
|
||||
User mysaa = userRepository.findByName("mysaa");
|
||||
if (mysaa == null) {
|
||||
User user = new User();
|
||||
user.setName("mysaa");
|
||||
user.setPassword(passwordEncoder.encode("super"));
|
||||
user.setRoles(Arrays.asList(adminRole));
|
||||
userRepository.save(user);
|
||||
}
|
||||
|
||||
alreadySetup = true;
|
||||
log.info("Everything needed has been created");
|
||||
}
|
||||
|
||||
@Transactional
|
||||
private Role createRoleIfNotFound(
|
||||
String name, EnumSet<Privilege> privileges) {
|
||||
|
||||
Role role = roleRepository.findByName(name);
|
||||
if (role == null) {
|
||||
role = new Role(name);
|
||||
role.setPrivileges(privileges);
|
||||
roleRepository.save(role);
|
||||
}
|
||||
return role;
|
||||
}
|
||||
}
|
||||
@ -6,7 +6,9 @@ import com.bernard.misael.model.User;
|
||||
import com.bernard.misael.service.dto.UserDto;
|
||||
|
||||
public interface UserService {
|
||||
|
||||
void saveUser(UserDto userDto);
|
||||
void changePassword(User user, String password);
|
||||
|
||||
User findUserByName(String name);
|
||||
|
||||
|
||||
@ -34,13 +34,13 @@ public class UserServiceImpl implements UserService {
|
||||
// encrypt the password using spring security
|
||||
user.setPassword(passwordEncoder.encode(userDto.getPassword()));
|
||||
|
||||
Role role = roleRepository.findByName("ROLE_ADMIN");
|
||||
if(role == null){
|
||||
role = checkRoleExist();
|
||||
}
|
||||
user.setRoles(Arrays.asList(role));
|
||||
user.setRoles(List.of());
|
||||
userRepository.save(user);
|
||||
}
|
||||
@Override
|
||||
public void changePassword(User user, String password) {
|
||||
user.setPassword(passwordEncoder.encode(password));
|
||||
}
|
||||
|
||||
@Override
|
||||
public User findUserByName(String name) {
|
||||
@ -60,10 +60,4 @@ public class UserServiceImpl implements UserService {
|
||||
userDto.setName(user.getName());
|
||||
return userDto;
|
||||
}
|
||||
|
||||
private Role checkRoleExist(){
|
||||
Role role = new Role();
|
||||
role.setName("ROLE_ADMIN");
|
||||
return roleRepository.save(role);
|
||||
}
|
||||
}
|
||||
@ -1,14 +1,11 @@
|
||||
package com.bernard.misael.web;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.annotation.Secured;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
@ -16,18 +13,24 @@ import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import com.bernard.misael.model.Role;
|
||||
import com.bernard.misael.model.User;
|
||||
import com.bernard.misael.repository.UserRepository;
|
||||
import com.bernard.misael.service.UserService;
|
||||
import com.bernard.misael.service.dto.UserDto;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.Getter;
|
||||
|
||||
@Controller
|
||||
public class AuthController {
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
@Autowired
|
||||
private UserRepository urepo;
|
||||
|
||||
public AuthController(UserService userService) {
|
||||
this.userService = userService;
|
||||
@ -56,44 +59,68 @@ public class AuthController {
|
||||
return "index";
|
||||
}
|
||||
|
||||
@GetMapping("/adduser")
|
||||
public String showRegistrationForm(Model model){
|
||||
// create model object to store form data
|
||||
UserDto user = new UserDto();
|
||||
model.addAttribute("user", user);
|
||||
return "adduser";
|
||||
@GetMapping("/users")
|
||||
@Secured("LIST_USERS")
|
||||
public String listUsers(Model model, Principal p){
|
||||
// User should have MANAGE_USERS now
|
||||
UserDto newUser = new UserDto();
|
||||
model.addAttribute("newuser",newUser);
|
||||
List<UserInfo> userz = urepo.findAll().stream().map(UserInfo::new).sorted().toList();
|
||||
model.addAttribute("users", userz);
|
||||
return "users";
|
||||
}
|
||||
|
||||
@PostMapping("/adduser/save")
|
||||
public String registration(@Valid @ModelAttribute("user") UserDto userDto,
|
||||
@PostMapping("/adduser")
|
||||
@Secured("ADD_USERS")
|
||||
public String registration(@Valid @ModelAttribute("newuser") UserDto userDto,
|
||||
BindingResult result,
|
||||
Model model){
|
||||
User existingUser = userService.findUserByName("mysaa");
|
||||
Model model,
|
||||
Principal p){
|
||||
User existingUser = userService.findUserByName(userDto.getName());
|
||||
|
||||
if(existingUser != null){
|
||||
result.reject("User is already registered");
|
||||
}
|
||||
|
||||
if(result.hasErrors()){
|
||||
model.addAttribute("user", userDto);
|
||||
return "/adduser";
|
||||
List<UserInfo> userz = urepo.findAll().stream().map(UserInfo::new).sorted().toList();
|
||||
model.addAttribute("users", userz);
|
||||
model.addAttribute("newuser",userDto);
|
||||
return "redirect:/users?duplicate";
|
||||
}
|
||||
userService.saveUser(userDto);
|
||||
return "redirect:/adduser?success";
|
||||
return "redirect:/users?success";
|
||||
}
|
||||
|
||||
@GetMapping("/create-mysaa-user")
|
||||
public ResponseEntity<String> checkMysaaExists() {
|
||||
if(userService.findUserByName("mysaa") == null) {
|
||||
UserDto u = new UserDto();
|
||||
Random r = new Random();
|
||||
UUID pass = new UUID(r.nextLong(),r.nextLong());
|
||||
u.setName("mysaa");
|
||||
u.setPassword(pass.toString());
|
||||
userService.saveUser(u);
|
||||
Logger.getLogger(AuthController.class.getName()).warning("Created user mysaa with password "+pass.toString());
|
||||
@GetMapping("/change-password")
|
||||
public String showChangePassword(){
|
||||
return "change-password";
|
||||
}
|
||||
|
||||
@PostMapping("/change-password/change")
|
||||
public String changePassword(@RequestParam("new-password") String newPassword, Principal p) {
|
||||
User u = null;
|
||||
if (p==null)
|
||||
return "redirect:/login?restricted";
|
||||
u = userService.findUserByName(p.getName());
|
||||
userService.changePassword(u, newPassword);
|
||||
|
||||
return "redirect:/change-password?success";
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static class UserInfo implements Comparable<UserInfo> {
|
||||
private long id;
|
||||
private String pseudo;
|
||||
private String roles;
|
||||
public UserInfo(User u){
|
||||
this.id = u.getId();
|
||||
this.pseudo = u.getName();
|
||||
this.roles = u.getRoles().stream().map(Role::getName).collect(Collectors.joining(";"));
|
||||
}
|
||||
@Override
|
||||
public int compareTo(UserInfo other) {
|
||||
return this.pseudo.compareTo(other.pseudo);
|
||||
}
|
||||
return new ResponseEntity<>(HttpStatus.OK);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -66,6 +66,8 @@ public class QuestionsController {
|
||||
User u = null;
|
||||
if (p!=null)
|
||||
u = ur.findByName(p.getName());
|
||||
else
|
||||
return "redirect:/login?restricted";
|
||||
//XXX test that user can answer quizz
|
||||
m.addAttribute("formid", quizzId);
|
||||
Quizz q = qrepo.getById(quizzId);
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
create table role_privileges (id bigint not null, privileges varchar(255) not null check (privileges in ('LIST_USERS','ADD_USERS','LIST_QUIZZ')), primary key (id, privileges));
|
||||
alter table if exists role_privileges add constraint FK7xfa4foqs58j7rhk6ex78hpf3 foreign key (id) references roles;
|
||||
@ -74,3 +74,8 @@ div.welcome{
|
||||
font-size: 20px;
|
||||
font-style: italic;
|
||||
}
|
||||
table, th, td {
|
||||
border: 1px solid black;
|
||||
border-collapse: collapse;
|
||||
padding: 4px;
|
||||
}
|
||||
25
src/main/resources/templates/change-password.html
Normal file
25
src/main/resources/templates/change-password.html
Normal file
@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr" dir="ltr">
|
||||
<head>
|
||||
<div th:replace="~{html-head}"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div th:replace="~{header}"/>
|
||||
<main>
|
||||
|
||||
<div th:if="${param.success}">
|
||||
<div class="alert alert-success"> Votre mot de passe a été changé</div><br/>
|
||||
</div>
|
||||
<form
|
||||
method="post"
|
||||
role="form"
|
||||
th:action="@{/change-password/change}">
|
||||
<label for="new-password">Nouveau mot de passe :</label>
|
||||
<input type="password" id="new-password" name="new-password"/>
|
||||
<br/>
|
||||
<input id="connect" type="submit" value="Changer de mot de passe"/>
|
||||
</form>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@ -14,6 +14,9 @@
|
||||
<div th:if="${param.logout}">
|
||||
<div class="alert alert-success"> You have been logged out.</div><br/>
|
||||
</div>
|
||||
<div th:if="${param.restricted}">
|
||||
<div class="alert alert-success"> Vous devez vous connecter pour vous accéder à cette page</div><br/>
|
||||
</div>
|
||||
|
||||
<form
|
||||
method="post"
|
||||
|
||||
@ -9,19 +9,22 @@
|
||||
<main>
|
||||
|
||||
<div th:if="${param.success}">
|
||||
You have successfully registered our app!
|
||||
Utilisteurice ajouté·e avec succès !
|
||||
</div>
|
||||
<div th:if="${param.duplicate}">
|
||||
Le nom d'utilisateur·ice est déjà utilisé
|
||||
</div>
|
||||
<form
|
||||
method="post"
|
||||
role="form"
|
||||
th:action="@{/adduser/save}"
|
||||
th:object="${user}"
|
||||
th:action="@{/adduser}"
|
||||
th:object="${newuser}"
|
||||
>
|
||||
<label for="name">Name</label>
|
||||
<label for="name">Nom</label>
|
||||
<input
|
||||
id="name"
|
||||
name="name"
|
||||
placeholder="Enter pseudo"
|
||||
placeholder="Le pseudo"
|
||||
th:field="*{name}"
|
||||
type="text"
|
||||
/>
|
||||
@ -40,6 +43,19 @@
|
||||
|
||||
<button class="btn btn-primary" type="submit">Register</button>
|
||||
</form>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Pseudo</th>
|
||||
<th>Roles</th>
|
||||
</tr>
|
||||
<tr th:each="u : ${users}">
|
||||
<td th:text="${u.id}"></td>
|
||||
<td th:text="${u.pseudo}"></td>
|
||||
<td th:text="${u.roles}"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user