우리 서버는 Security가
감싸고 있음
따라서 클라이언트가 전송한 것이 서버에 도착 전에 Security가 먼저 검사를 함
무슨 검사 ? CSRF 토큰 검사
CSRF 토근 검사? 사용자가 회원가입 페이지
를 요청하면
서버에서 signup.jsp
를 응답해주겠지?
근데 여기서 서버가 signup.jsp 파일에 CSRF
토큰을 심어서 돌려준다.
자, 여기서 회원가입 페이지엔 input 태그가 두개(데이터1, 데이터2)가 있다고 하자
그럼 서버가 응답한 signup.jsp
의 input 태그엔 임시 난수값이 생겨서 돌아온다.
나의 signup.jsp 파일엔 안보이지만, 아래와 같이csr="csr"
가 추가되었다고 생각하면 된다.
<input type="text" name="username" placeholder="유저네임" required="required" csr="csr"/>
input태그에 데이터를 담아서 데이터1과 데이터2를 서버에게 전송하여 회원가입 요청을 했다고 하자
그럼 서버가 내가 만들어준 CSRF 토큰이 있는지 확인한다.
만약 맞다면 "아, 내가 보내준 signup.jsp
파일이 맞고 이 페이지에서 정상적으로 데이터를 보냈구나" 하는거다.
정상적이지 않은 요청이 뭐냐? 싶을 수 있는데,
간단한 예를 들자면 포스트맨에서 post를 보내는 경우라고 생각하면 됩니다.
http.csrf().disable();
package com.cos.photogramstart.config;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity // 해당 파일로 시큐리티 활성화시킴
@Configurable // IoC 등록
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// super.configure(http); -> super 삭제 : 기존 시큐리티가 가지고 있는 기능이 다 비활성화됨
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/","/user/**","/image/**","/subscribe/**","/comment/**").authenticated() // 인증이 필요한 페이지들
.anyRequest().permitAll() // 이외의 페이지는 접근 허용
.and()
.formLogin() //인증이 필요한 페이지들은 접근하면 로그인하도록
.loginPage("/auth/signin")// 로그인 페이지는 "~"
.loginProcessingUrl("/"); // 로그인이 성공적으로 됐다면 "/"으로 ㄱㄱ
}
}
목표 : jsp form 에 있는 username, password, email, name 을 회원가입 후 유저로 db에 저장
//jsp
<form class="login__input" action="/auth/signup" method="post">
<input type="text" name="username" placeholder="유저네임" required="required" />
<input type="password" name="password" placeholder="패스워드" required="required" />
<input type="email" name="email" placeholder="이메일" required="required" />
<input type="text" name="name" placeholder="이름" required="required" />
<button>가입</button>
</form>
//SingupDto
@Data //Getter,Setter
public class SingupDto {
private String username;
private String password;
private String email;
private String name;
}
DTO ?
Data Transfer Object : 통신할 때 필요한 데이터를 담아주는 object
@Data ?
Getter와 Setter를 만들어주는 어노테이션
Form으로 데이터가 날아오면 key=value 형식으로 데이터를 받아옴
// AuthController
@PostMapping("/auth/signup")
public String signup(SignupDto signupDto){
log.info(signupDto.toString());
return "auth/signin";
}
signup.jsp 에서 username, pw, email, name을 입력시 이 데이터들을 Dto에 담는 것까지 ok
그리고 이 Dto를 파라미터로 받는다.
그렇다면 누가 jsp 파일에서 입력된 데이터를 Dto에 담아주는건데?
jsp 폼 데이터의 확인 버튼을 클릭하면 Post 요청이 발생,
@PostMapping("/auth/signup")와 일치하는 엔드포인트로 보내짐
엔드포인트로 보내졌을 때, 해당 엔드포인트를 처리하는 메서드인 signup(SignupDto signupDto)가 호출됨
이 메서드가 POST 요청 처리하고, jsp 폼에 입력된 데이터 (즉 엔드포인트로 전송된 데이터)는 SignupDto 객체로 자동 매핑된다
HTML 폼의 각 입력 필드(username, password, email, name)에는 name 속성이 지정되어 있고,
이 name 속성은 서버로 데이터를 전달할 때 해당 데이터의 식별자 역할을 한다.
이 name 속성과 SignupDto 클래스의 필드 이름이 일치하면 Spring Framework가 요청 데이터를 자동으로 해당 필드에 매핑합니다.
@PostMapping?
post 요청이 "~" < 엔드포인트로 전달될 때 호출
(+)
사용자가 JSP 페이지에서 "확인" 버튼을 클릭하면 HTML 폼이 서버로 데이터를 전송하는 POST 요청을 생성합니다.
POST 요청의 action 속성에 /auth/signup와 같은 경로가 지정되어 있으므로, 이 요청은 Spring 컨트롤러의 @PostMapping("/auth/signup")와 일치하는 엔드포인트로 보내집니다.
Spring 컨트롤러에서는 해당 엔드포인트를 처리하는 메서드인 signup(SignupDto signupDto)가 호출됩니다. 이 메서드는 POST 요청을 처리하고, 폼에서 전송된 데이터는 SignupDto 객체로 자동 매핑됩니다.
따라서 "확인" 버튼을 누르면 POST 요청이 서버로 보내지고, 그 결과로 @PostMapping("/auth/signup") 엔드포인트에 매핑된 컨트롤러 메서드가 실행됩니다.
domain 패키지 생성 > User 패키지 생성 > User.java 클래스 생성
JPA ?
Java Persistence API
자바로 데이터를 영구적으로 저장(DB)할 수 있는 API를 제공
package com.cos.photogramstart.domain.User;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.time.LocalDateTime;
@AllArgsConstructor
@NoArgsConstructor
@Data
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // 번호 증가 전략이 데이터베이스를 따라감
private int id;
private String username;
private String password;
private String name;
private String website; // 웹사이트
private String bio; // 자기소개
private String email;
private String phone;
private String profileImageUrl; // 사진
private String role; // 권한
private LocalDateTime createDate;
@PrePersist // 디비에 INSERT 되기 직전에 실행
public void createDate(){
this.createDate = LocalDateTime.now();
}
}
LocalDateTime ?
import java.time.LocalDateTime;
데이터가 언제 들어왔는지
@PrePersist
사용하면 id, name .. 등의 데이터를 넣을 때 자동으로 LocalDateTime값이 들어감
@Entity
DB에 만들어지기 위해서 필요한 어노테이션
디비에 테이블을 생성
@Id
PK가 필요하기 때문에 id에 추가해주어야 하는 어노테이션
@GeneratedValue(strategy = GenerationType.IDENTITY)
번호 증가 전략이 데이터베이스를 따라감
@NoArgsConstructor
빈생성자
@AllArgsConstructor
전체 생성자
여기까지 해보고 mysql bench 들어가서 아래와 같이 입력하면 테이블이 생성되어 있는 것을 확인할 수 있다.
Dto에서 받은 데이터를 객체에 담는 가장 좋은 방법이 @Bulider
어노테이션을 사용하는 것
빌더 어노테이션 참고 : https://pamyferret.tistory.com/67
User.java에서 @Builder 추가
//SignupDto
public User toEntity(){
return User.builder()
.username(username)
.password(password)
.email(email)
.name(name)
.build();
}
잘 담겨졌는지 확인
//AuthController
@PostMapping("/auth/signup")
public String signup(SignupDto signupDto){
log.info(signupDto.toString());
User user = signupDto.toEntity();
log.info(user.toString());
return "auth/signin";
}
이제 잘 담긴 이 데이터 객체를 db에 넣어보자
domain> user > UserRepositroy 인터페이스 생성
public interface UserRepository extends JpaRepository<User, Integer> {
}
<User, Integer> = <오브젝트 이름, 프라이머리 키 타입>
JpaRepository을 상속했으니 어노테이션이 없어도 자동으로 Ioc 등록이 됨
Service 계층 생성
service 패키지 생성 > AuthService 클래스 생성
@RequiredArgsConstructor
@Service //1.Ioc 2.트랜잭션 관리: 이 서비스 어노테이션에 등록된 객체를 관리해줌
public class AuthService {
private final UserRepository userRepository;
public User 회원가입(User user){
//회원가입 진행
User userEntity = userRepository.save(user); //save가 된 뒤에 (즉 db에 들어간 후에) 응답받음
// user: 외부 통신에서 받은 것 userEntity : 데이터 베이스에 있는 user오브젝트를 담은것
return userEntity;
}
}
이후 AuthController에 적용시키기
//AuthController
@PostMapping("/auth/signup")
public String signup(SignupDto signupDto)
User user = signupDto.toEntity();
User userEntity = authService.회원가입(user);
return "auth/signin";
}
@DI 하는법
- @Autowired
- @RequiredArgsConstructor + private final UserRepository userRepository;
여기까지 정리하자면,
1. Auth controller 생성 > GetMapping/PostMapping 설정 > SignupDto > Model : User > UserRepository > Service
SecurityConfig 를 수정해보자
@EnableWebSecurity
해당 파일로 시큐리티 활성화시킨다
시큐리티 활성화 어노테이션을 추가해준다.
@Bean //SecurityConfig가 Ioc에 등록될때, Bean 어노테이션을 읽어서 리턴한 BCryptPasswordEncoder를 Ioc가 들고있음 > DI해서 쓰기만 하면 됨
public BCryptPasswordEncoder encode(){
return new BCryptPasswordEncoder();
}
@Bean
IoC 컨테이너가 관리하는 객체
https://ittrue.tistory.com/221
//AuthService
private final BCryptPasswordEncoder bCryptPasswordEncoder;
String rawPassword = user.getPassword();
String encPassword = bCryptPasswordEncoder.encode(rawPassword);
user.setPassword(encPassword);
user.setRole("ROLE_USER"); // 관리자는 ROLE_ADMIN
//User
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // 번호 증가 전략이 데이터베이스를 따라감
private int id;
@Column(unique = true)
private String username;
...
@Column(unique = true)
unique로 중복되는 값이 저장되지 않는다.
위의 User 객체에서 length = 20
를 추가하면 바로 적용이 되나?
@Column(length = 20, unique = true)
그렇지 않다. 쿼리가 변경된 것이니 application에서 update > create 변경 후 다시 적용하기
전처리 : validation 유효성검사
후처리 : exception handler
이 기능들을 공통 기능
이라고 한다.
없어도 잘 돌아가지만, 있어야 더 깔끔하게 돌아간다.
이를 AOP
라고 부른다.
전처리 후처리는 파일을 따로 두어 처리한다. 아니면 코드 너무 조잡해짐
[전처리]
//pom.xml 추가
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<!-- <version>2.4.4</version>-->
</dependency>
사용할 객체에 @Valid 어노테이션 추가
//AuthController
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());
log.info(error.getDefaultMessage().toString());
}
}
//SingupDto
public class SignupDto {
@Max(20)
private String username;
@NotBlank
private String password;
@NotBlank
private String email;
@NotBlank
private String name;
public String signup(
Valid
SignupDto signupDto,BindingResult bindingResult
)
SignupDto signupDto 를 Validation 체크해서
문제가 생기면 = ","
발생한 오류를 모두 BindingResult bindingResult에 담아둔다. 담아둔게 FieldError