package coffee.pastry.joshuablog.model.user;
import java.time.LocalDateTime;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Table;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Builder
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "user_tb")
@AllArgsConstructor
@Getter
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, length = 20)
private String username;
@Column(length = 60)
private String password;
@Column(length = 50)
private String email;
private String role;
private String profile;
private Boolean status;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public void changeProfile(String profile) {
this.profile = profile;
}
@PrePersist
protected void onCreate() {
this.createdAt = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
this.updatedAt = LocalDateTime.now();
}
}
@Builder + @AllArgsConstructor (쌍으로 움직임)
@NoArgsConstructor : 하이버네이트가 User new 하기 위해서
access = AccessLevel.PROTECTED : 개발자 직접 new 못하게 하기 위해서
package coffee.pastry.joshuablog.model.user;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.username = :username")
Optional<User> findByUsername(@Param("username") String username);
}
namedQuery -> JPQL 쿼리
package coffee.pastry.joshuablog.dto.user;
import coffee.pastry.joshuablog.model.user.User;
import lombok.Getter;
import lombok.Setter;
public class UserRequestDto {
@Getter
@Setter
public static class JoinInDto {
private String username;
private String password;
private String email;
public User toEntity() {
return User.builder()
.username(username)
.password(password)
.email(email)
.role("USER") // ENUM(check)
.status(true)
.profile("person.png")
.build();
}
}
}
package coffee.pastry.joshuablog.service;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import coffee.pastry.joshuablog.dto.user.UserRequestDto;
import coffee.pastry.joshuablog.model.user.UserRepository;
import lombok.RequiredArgsConstructor;
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Service
public class UserService {
private final UserRepository userRepository;
private final BCryptPasswordEncoder passwordEncoder;
@Transactional
public void 회원가입(UserRequestDto.JoinInDto joinInDto) {
joinInDto.setPassword(passwordEncoder.encode(joinInDto.getPassword()));
userRepository.save(joinInDto.toEntity());
} // 더티채킹, 세션종료
}
OSIV가 false -> 트랜젝션 종료시, 더치채킹 + 세션종료 -> 컨트롤러 LAZY 로딩 X
@RequiredArgsConstructor
@Controller
public class UserController {
private final UserService userService;
@PostMapping("/join")
public String join(UserRequestDto.JoinInDto joinInDto) {
userService.회원가입(joinInDto);
return "redirect:/loginForm";
}
@GetMapping("/joinForm")
public String joinForm() {
return "user/joinForm";
}
@GetMapping("/loginForm")
public String loginForm() {
return "user/loginForm";
}
}
로그인 화면이 나옴 = 리다이렉션이 됨
1번더 누르면?
username 유니크 제약조건 에러 발생
어디서 터진걸까? -> save에서, save가 throws Exception을 가지고 있지 않기 때문에
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Service
public class UserService {
private final UserRepository userRepository;
private final BCryptPasswordEncoder passwordEncoder;
@Transactional
public void 회원가입(UserRequestDto.JoinInDto joinInDto) {
try {
joinInDto.setPassword(passwordEncoder.encode(joinInDto.getPassword()));
userRepository.save(joinInDto.toEntity());
} catch (Exception e) {
throw new RuntimeException("회원가입 오류 : " + e.getMessage());
}
}
}
일단 Exception 코드를 안만들어서 일단 가장 조상인 RuntimeException으로 처리
다시 포스트맨으로 회원가입 2번 연속하면? 잘 처리함.
package coffee.pastry.joshuablog;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import coffee.pastry.joshuablog.model.user.User;
import coffee.pastry.joshuablog.model.user.UserRepository;
@SpringBootApplication
public class BlogApplication {
@Bean
CommandLineRunner init(UserRepository userRepository, BCryptPasswordEncoder passwordEncoder) {
return args -> {
User song = User.builder()
.username("song")
.password(passwordEncoder.encode("1234"))
.email("song@nate.com")
.role("USER")
.profile("person.png")
.status(true)
.build();
userRepository.save(song);
};
}
public static void main(String[] args) {
SpringApplication.run(BlogApplication.class, args);
}
}
insert 쿼리 + TRACE로 detail 확인
h2 확인 -> 막힘 -> 시큐리티 떄문
@Bean // 권한 주소 설정
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable();
http.headers().frameOptions().disable(); // 추가
http.formLogin()
.loginPage("/loginForm")
.loginProcessingUrl("/login")
.successHandler(((request, response, authentication) -> {
log.debug("디버그 : 로그인 성공");
}))
.failureHandler(((request, response, exception) -> {
log.debug("디버그 : 로그인 실패 : " + exception.getMessage());
}));
http.authorizeRequests(
authorize -> authorize.antMatchers("/s/**").authenticated()
.anyRequest().permitAll());
return http.build();
}
h2-console 때문에 iframe 해제