3/25 TIL - 점프투스프링부트 따라하기 4

큰모래·2023년 3월 25일
0
post-custom-banner

점프투스프링부트


3-03 게시물에 일련번호 추가하기

게시물 번호 공식

게시물 번호 = 게시물 전체 개수 - (게시물 페이지 * 페이지당 게시물 개수) - 나열 인덱스

question_list - 추가

<td th:text="${paging.getTotalElements - (paging.number * paging.size) - loop.index}"></td>

3-04 답변 개수 표시

question_list - 추가

  • text-danger : 빨간색
  • small : 작게
  • ms-2 : 왼쪽 여백
  • th:if로 답변이 있는지 확인하고 있다면, 답변 개수 표시
<span class="text-danger small ms-2"
	th:if="${#lists.size(question.answerList) > 0}" 
	th:text="${#lists.size(question.answerList)}">
</span>

3-05 스프링 시큐리티

스프링 시큐리티는 스프링 기반 프레임워크의 인증과 권한을 담당하는 스프링 하위 프레임워크이다.
스프링 시큐리티는 기본적으로 인증되지 않은 사용자는 접속 할 수 없게 설정되어 있으므로, 시큐리티 설정을 통해 인증에 대한 설정을 해야 한다.

  • 인증 : 로그인
  • 권한 : 인증된 사용자가 어떤 것을 할 수 있는지

SecurityConfig

  • @Configuration : 스프링 환경설정 파일임을 의미한다.
  • @EnableWebSecurity : 모든 요청 url이 스프링 시큐리티의 제어를 받는다.
    • 내부적으로 SpringSecurityFilterChain이 동작하여 url 필터가 동작한다.
  • SecurityFilterChainBean으로 등록하여 시큐리티 세부 설정이 가능하다.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests().requestMatchers(
                new AntPathRequestMatcher("/**")).permitAll()
                ;
        return http.build();
    }
}

하지만, 위와 같이 설정해도 페이지에 접근할 수 없다.
왜냐하면, 스프링 시큐리티를 적용하면 CSRF가 동작하기 때문이다.

CSRF
웹페이지 취약점 공격을 방지하기 위한 기술.
스프링 시큐리티에 의해 세션에서 토큰이 발급되고, 이후에 폼 전송 시 해당 토큰 값을 통해 유효한 요청인지 스프링 시큐리티를 통해 확인한다.


SecurityConfig - 수정

  • csrf 검증을 진행하지 않게 잠시 수정하였다. (아예 페이지 접근을 할 수 없으므로)
  • 로그인 기능을 개발한 후 다시 없애도록 하자.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests().requestMatchers("/**").permitAll().and()
                .csrf()
                .ignoringRequestMatchers(new AntPathRequestMatcher("/**"));
        return http.build();
    }
}

3-06 회원가입

SiteUser 엔티티

  • usernameemailUnique로 지정해 유저 간 중복 저장이 불가능하게 만들었다.
@Entity
@Getter
@Setter
public class SiteUser {

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

    @Column(unique = true)
    private String username;

    private String password;

    @Column(unique = true)
    private String email;
}

UserRepository

public interface UserRepository extends JpaRepository<SiteUser, Long> {
}

SecurityConfig - 추가

  • BCryptPasswordEncoder 객체를 반환하는 PasswordEncoder 객체를 빈으로 추가
	@Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

UserService

  • 유저의 비밀번호는 빈으로 등록한 passwordEncoder를 주입받아 비밀번호를 암호화했다.
@RequiredArgsConstructor
@Service
public class UserService {

    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;

    public SiteUser create(String username, String password, String email) {
        SiteUser user = new SiteUser();
        user.setUsername(username);
        user.setPassword(passwordEncoder.encode(password));
        user.setEmail(email);
        userRepository.save(user);

        return user;
    }
}

회원가입 폼

  • 회원가입 컨트롤러와 회원가입 폼 사이에서 데이터를 전송할 UserCreateForm 생성
  • 비밀번호는 password1password2를 만들어 두번 입력
  • @Email은 해당 형식이 이메일 형식인지 검증한다.
@Getter
@Setter
public class UserCreateForm {

    @Size(min = 3, max = 25)
    @NotEmpty(message = "사용자ID는 필수항목입니다.")
    private String username;

    @NotEmpty(message = "비밀번호는 필수항목입니다.")
    private String password1;


    @NotEmpty(message = "비밀번호 확인은 필수항목입니다.")
    private String password2;


    @NotEmpty(message = "이메일은 필수항목입니다.")
    @Email
    private String email;

}

UserController

  • /signup url을 통해 회원가입 폼으로 이동
  • 회원가입 폼 작성 완료 시 /signup post 요청
  • 폼에 입력한 데이터가 유효한지 검증한다.
  • bindingResult 객체에 error가 담겼으면 회원가입 폼으로 다시 이동
  • password1과 password2가 다르다면 bindingResult.rejectValue를 사용하여 오류가 발생하게 했다.
  • userCreateForm을 통해 받은 데이터로 user 생성 및 저장
  • 사용자 아이디나 이메일이 중복일 경우 DataIntegrityViolationException이 발생한다.
  • bindingResult.reject는 특정 필드의 오류가 아닌 일반적인 오류를 등록할때 사용한다.
  • 이러한 예외 발생 시 회원 가입 폼으로 다시 이동
@RequiredArgsConstructor
@Controller
@RequestMapping("/user")
public class UserController {

    private final UserService userService;

    @GetMapping("/signup")
    public String signup(@ModelAttribute UserCreateForm userCreateForm) {
        return "/user/signup_form";
    }

    @PostMapping("/signup")
    public String signup(@Valid UserCreateForm userCreateForm, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return "/user/signup_form";
        }

        if (!userCreateForm.getPassword1().equals(userCreateForm.getPassword2())) {
            bindingResult.rejectValue("password2", "passwordInCorrect",
                    "2개의 패스워드가 일치하지 않습니다.");
            return "/user/signup_form";
        }

        try {
            userService.create(userCreateForm.getUsername(),
                    userCreateForm.getEmail(), userCreateForm.getPassword1());
        } catch (DataIntegrityViolationException e) {
            e.printStackTrace();
            bindingResult.reject("signupFailed", "이미 등록된 사용자입니다.");
            return "signup_form";
        } catch (Exception e) {
            e.printStackTrace();
            bindingResult.reject("signupFailed", e.getMessage());
            return "signup_form";
        }

        return "redirect:/";
    }
}
profile
큰모래
post-custom-banner

0개의 댓글