Spring Instagram clone (Signup, Login, Auth)

Hyeonseop-Noh·2022년 4월 4일
0

Basic setup

IDE: Intellij
DB: Mariadb
Framework: Spring boot
Compile: Maven (will convert to Gradle)
Port: 3307
Views: jsp

Almost front-end settings are done.

Security setting

@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)

Domain(User)

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

Auth

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);
        }

    }

}

Controllers

AuthController

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";
        }

    }

}

UserController

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";
    }

}

UserApiController (RestController)

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);
    }

}

Services

AuthService

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

UserService

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

Exception management

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;
    }

}

ControllerExceptionHandler

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

Packages

profile
PlanBy Developer

0개의 댓글