IDE: Intellij
DB: Mariadb
Framework: Spring boot
Compile: Maven (will convert to Gradle)
Port: 3307
Views: jsp
Almost front-end settings are done.
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/", "/user/**", "/image/**", "/subscribe/**", "/comment/**", "/api/**").authenticated()
.anyRequest().permitAll()
.and()
.formLogin()
.loginPage("/auth/signin") // GET
.loginProcessingUrl("/auth/signin") // POST -> spring security proceed login
.defaultSuccessUrl("/");
}
Routes to "/", "/user/~", "/image/~", "/subscribe/~", "/comment/~", "/api/~" should be authenticated.
Default is login page. (GET request -> view page, POST request -> validation method)
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // Strategy of increasing number follow the DB
private int id;
@Column(unique = true)
private String username;
@Column(nullable = false)
private String password;
@Column(nullable = false)
private String name;
private String bio;
private String gender;
@Column(nullable = false)
private String email;
private String phone;
private String website;
private String profileImageUrl;
private String role;
private LocalDateTime createDate;
@PrePersist
public void createDate() {
this.createDate = LocalDateTime.now();
}
}
User domain session
Get UserDetail interface to PrincipalDetails -> Override
Didn't consider about account expiration, lock, enable
public class PrincipalDetails implements UserDetails {
private static final long serialVersionUID = 1L;
private User user;
public PrincipalDetails(User user) {
this.user = user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collector = new ArrayDeque<>();
collector.add(() -> {return user.getRole();});
return collector;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true; // return user.getExpired();
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
public class PrincipalDetailsService implements UserDetailsService {
private final UserRepository userRepository;
// 1. Password: auto check
// 2. Return(o) -> auto make UserDetails type to session
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User userEntity = userRepository.findByUsername(username);
if (userEntity == null) {
return null;
} else {
return new PrincipalDetails(userEntity);
}
}
}
public class AuthController {
private final AuthService authService;
@GetMapping("/auth/signin")
public String signinForm() {
return "auth/signin";
}
@GetMapping("/auth/signup")
public String signupForm() {
return "auth/signup";
}
@PostMapping("/auth/signup")
public String signup(@Valid SignupDto signupDto, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
Map<String, String> errorMap = new HashMap<>();
for (FieldError error : bindingResult.getFieldErrors()) {
errorMap.put(error.getField(), error.getDefaultMessage());
}
throw new CustomValidationException("Validation check failed", errorMap);
} else {
User user = signupDto.toEntity();
User userEntity = authService.signupService(user);
System.out.println(userEntity);
return "auth/signin";
}
}
}
public class UserController {
@GetMapping("/user/{id}")
public String profile(@PathVariable int id) {
return "user/profile";
}
@GetMapping("/user/{id}/update")
public String update(@PathVariable int id, @AuthenticationPrincipal PrincipalDetails principalDetails) {
return "user/update";
}
}
public class UserApiController {
private final UserService userService;
@PutMapping("/api/user/{id}")
public CMRespDto<?> update(@PathVariable int id, UserUpdateDto userUpdateDto, @AuthenticationPrincipal PrincipalDetails principalDetails) {
User userEntity = userService.editUser(id, userUpdateDto.toEntity());
principalDetails.setUser(userEntity);
return new CMRespDto<>(1, "User info edit successfully", userEntity);
}
}
public class AuthService {
private final UserRepository userRepository;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
@Transactional // Write(Insert, Update, Delete)
public User signupService(User user) {
String rawPassword = user.getPassword();
String encPassword = bCryptPasswordEncoder.encode(rawPassword);
user.setPassword(encPassword);
user.setRole("ROLE_USER");
User userEntity = userRepository.save(user);
return userEntity;
}
}
Logic of user authentication (Signup)
By default, role is ROLE_USER
public class UserService {
private final UserRepository userRepository;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
@Transactional
public User editUser(int id, User user) {
// 1. Persistence
User userEntity = userRepository.findById(id).get();
// 2. Edit object - Dirty checking(update finished)
userEntity.setName(user.getName());
String rawPassword = user.getPassword();
String encPassword = bCryptPasswordEncoder.encode(rawPassword);
userEntity.setPassword(encPassword);
userEntity.setBio(user.getBio());
userEntity.setWebsite(user.getWebsite());
userEntity.setPhone(user.getPhone());
userEntity.setGender(user.getGender());
return userEntity;
}
}
Edit user info
Catch RuntimeException (extended by CustomValidationException)
public class CustomValidationException extends RuntimeException {
private static final long serialVersionUID = 1L;
private Map<String, String> errorMap;
public CustomValidationException(String message, Map<String, String> errorMap) {
super(message);
this.errorMap = errorMap;
}
public Map<String, String> getErrorMap() {
return errorMap;
}
}
public class ControllerExceptionHandler {
@ExceptionHandler(CustomValidationException.class)
public String validationException(CustomValidationException e) {
// Comparison of CMRespDto, Script
// 1. Script is better for response with client
// 2. Ajax communication - CMRespDto
// 3. Android communication - CMRespDto
return Script.back(e.getErrorMap().toString());
}
}
Refer the comments