정말.. 정말 많이 바빴다.
새로운 프레임워크/라이브러리를 학습하는것도 모자라서 토이 프로젝트도 진행해야 했고, 코딩테스트도 쳐야되는데 발표에 기획준비에 정신이 하나도 없었다.
그래도 안적어놓으면 나중에 잊어먹을수 있으니 최대한 적어야 한다.
스프링은 자바를 위한 오픈소스 프레임워크 이다. 따라서 객체지향형으로 작동이 되며, 다양한 기능들을 가지고 있다.
자바 어노테이션은 메타데이터의 일종이다. 즉, 다른 데이터들을 정의하기 위해서 사용하는 데이터이다.
이렇게 말하면 뭔말을 하는지 잘 이해가 되지 않는데 다음 예시를 보자.
@Controller
public class MainController {
@GetMapping("/main")
@ResponseBody
public String index() {
return "안녕하세요 홈페이지에 오신것을 환영합니다.";
}
@GetMapping("/")
public String root() {
return "redirect:/question/list";
}
}
class 위에 @로 이루어진 어노테이션이 붙어있는걸 확인 할 수 있다.
저 @Controller의 기능은 다음과 같다.
즉 @Controller는 해당 class를 Controller로써 작동할 수 있게 해주며, Controller의 기능을 도와주는 부가적인 어노테이션 또한 사용 가능하도록 허락해 준다.
우리가 해야할 일은 자주 사용하는 어노테이션을 정리, 기록하여 빨리 외워서 사용해야 하는 것이다.
어노테이션은 기능이 다양하게 사용될 수 있기 때문에 그 종류또한 엄청나게 많다.
@PostMapping("/login")
public boolean login(
@RequestBody Map<String, String> loginRequest, HttpServletResponse response
) {
String username = loginRequest.get("username");
String password = loginRequest.get("password");
System.out.println(username);
System.out.println(password);
SiteUser user = userRepository.findByusername(username).orElse(null);
System.out.println(user);
if(passwordEncoder.matches(password, user.getPassword())){
Cookie authCookie = new Cookie("loggedIn", username);
authCookie.setMaxAge(24 * 60 * 60); // 쿠키 유효기간 설정 (예: 1일)
authCookie.setPath("/"); // 쿠키 경로 설정
response.addCookie(authCookie);
return true;
}else{
return false;
}
}
private String generateToken() {
return "sample_token"; // 임시로 샘플 토큰을 반환합니다.
}
}
위의 코드는 게시판에서 사용한 로그인 코드이다.
위 코드의 로직은 다음과 같다.
이때 쿠키에 username을 집어넣는데, 게시글을 쓰는 권한을 주거나 게시글을 쓸때 누가 썼는지 판별하기 위해서 사용된다.
스프링의 보안을 담당하는 부분이다. 그리고 높은확률로 403이 뜬다면 security가 문제인 것이다.
가장 대표적인 예시를 들자면 csrf 에러가 발생하는 것인데
.csrf((csrf) -> csrf.ignoringRequestMatchers(new AntPathRequestMatcher("/h2-console/**")))
.csrf(AbstractHttpConfigurer::disable)
결국은 참지 못하고 disable로 무력화를 시켜버렸다.
원인을 찾아보니
"csrf 공격을 방어하기 위해서 csrf 토큰이라는 걸 가져와야 되는데, IDE 환경에서 따로 토큰을 지정해주지 않았기 때문에 알지 못하는 외부에서 공격을 하는 것으로 인식하였다." 라고 한다.
물론 이러한 보안을 알고 활용하게 된다면 정말 좋겠지만 아직은 이런걸 사용할 때는 아닌거 같다.
간단하게 적었는데 이 403이 뭔지 알아내고 해결하는것에 거의 8시간은 썼다.
파일 구조를 다음과 같이 짰다. 이러한 구조가 가능한 이유는 위에서 나온 어노테이션과 인터페이스의 도움이 컸다.
우선 Question이 어떤것인가를 정의하는 Question 파일의 내용은 다음과 같다.
@Getter
@Setter
@Entity
public class Question {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(length = 200)
private String subject;
....
}
클래스의 위에는 @Getter, @Setter 등등의 어노테이션이 붙어있는걸 확인 할 수 있고,
내용으로는 id, subject. ..... 이 들어가있다. 즉 Question의 구조는 어떤것이 들어있다 라는 것을 확인할 수 잇다.
또한 Dto는 다음과 같다.
@Getter
public class QuestionDto {
private Integer id;
private String subject;
private String content;
private LocalDateTime createDate;
private SiteUser author;
public QuestionDto(Question question) {
this.id = question.getId();
this.subject = question.getSubject();
this.content = question.getContent();
this.createDate = question.getCreateDate();
this.author = question.getAuthor();
}
}
데이터를 줄건데, 데이터의 내용이 어떻게 될 것인가를 나타낸 것이다.
QuestionDto는 id, jubject, content, createDate, author의 데이터를 준다는 뜻이다.
위의 사진은 Dto를 잘 설명해주는 그림이다.
데이터베이스에서 컨트롤러로 데이터가 잘 넘어오더라도, 이것을 client에게 전부 전해주는건 큰 리스크를 발생할 수 있다.(비밀번호를 그대로 return한다면 해커가 스니핑 공격을 했을때 client의 데이터가 유출될 가능성이 높다.)
또한 필요하지 않는 데이터를 모두 보내게 된다면 불필요한 통신으로 인해 오버해드가 발생하게 된다.
리액트는 자바스크립트 기반의 라이브러리 이다. 물론 자바스크립트를 사용하는것도 중요하지만, 기본적으로 프론트앤드 작업이기 때문에 기본적인 html 사전지식이 있어야 작업이 편하다.
처음 봤을때 감도 못잡았던 개념들이다. 하지만 간단하게 설명이 가능하다.
Props
props는 간단하게 말하면 부모로부터 받아온 데이터를 의미한다.
const List_component = (props) => {
const navigate = useNavigate();
const onClickPost = (event) => {
event.preventDefault();
navigate(`/detail/${props.list.id}`, {
state: {
list: props
}
});
}
....
}
리스트의 구성요소들을 받아오는 코드이다.
리스트의 구성요소들을 받아온다고는 적었지만 사실 props는 리스트의 id를 받아서 특정 데이터를 불러오는 코드라고 이해하면 되겠다.
이렇게 부모가 자식을 불러올때 특정한 props를 통해서 자식에게 데이터를 전달하고, 그 데이터를 파싱하거나 사용해 자식이 돌아가는게 일반적인 구조라 생각하면 된다.
정말 간단하게 설명을 하면
def fibo(x):
return fibo(x-1) + fibo(x-2)
라는 간단한 파이썬 코드에서
fibo 함수는 자식이라고 보면 되고, x가 props라는 느낌으로 이해하면 된다.
useState
import {useState} from "react";
const QuestionForm = () => {
const [content, setContent] = useState("");
...
const handleContentChange = (event) => {
setContent(event.target.value);
}
...
}
위와 같은 형태로 쓰일 수 있다.
코드를 풀어서 쓰자면
이러한 과정을 통해서 우리는 content 객체의 값을 변화시킬 수 있다.
그냥 변수에 대입하면 되는것이 아닌가? 라는 고민을 할 수 있는데 인프런 AI 인턴의 답은 다음과 같다.
즉 새로고침을 하지 않아도 즉각적으로 데이터에 반영이 되기 때문에 동적으로 페이지를 만들 수 있다는 것이다.
useEffect
useEffect(() => {
console.log("getList start");
axios.get("http://localhost:8080/api/getList")
.then((res) => {
setList(res.data);
console.dir(res.data);
})
.catch((err) => console.log(err));
}, []);
위의 코드를한번 살펴보자.
이러한 useEffect는 사용 방법에 따라서 마운트 될때, 언마운트 될때 등등 다양한 방법으로 쓰일 수 있다.
그런데 useEffect를 남용하지 마라는 글이 자주 나온다.
우선 side effect의 개수가 늘어나게 된다면 고려해야 할 경우의 수가 기하급수적으로(선형적이지 않게) 증가하게 될 것이고, 흐름의 추적이 어렵게 된다고 말하고 있다.
useEffect를 사용할때는 현명하게 사용하기 위해서 노력해야 할 것 같다.
useNavigate
import Navbar from "./navbar";
const LoginPage = () => {
const navigate = useNavigate();
...
if(res.data){
navigate("/");
}else{
setShowError(true);
alert("로그인 정보가 다릅니다!")
}
...
}
간단하다. useNavigate를 정의한 뒤 내가 가고싶은 url을 적으면 끝난다.
사실 정말 간단하고 기능 자체'만'써서 그런것이지 찾아보니 useNavigate는 많은 일을 한다.
nav("/location", {
state: {
first : first_data,
second : second_data,
third : third_data,
....
},
위와 같이 특정 페이지로 넘어갈때 상테 데이터도 같이 넘겨줄 수 있다는 것이다.
이 뿐만 아니라 함수도 넘겨줄 수 있다고 한다.
즉 한줄로 정리하자면
페이지 전환 후 특정한 작업을 할 때는 useNavigate로, 아니라면 Link를 쓰자