LG CNS 부트캠프 학습일지 29일차
학습 내용
- JPA (Java Persistence API)
- JSON Web Token
Java Persistence API
이전까지 사용한 MyBatis를 대체하는 라이브러리이다. 가장 큰 특징은 관계형 데이터베이스를 자바 객체로 접근할 수 있다는 점이다. MyBatis를 사용할 때는 Mapper 인터페이스의 메소드와 SQL을 수동으로 연결해주어야 했다. 하지만 JPA를 사용할 때는 메소드 이름을 특정 형식에 맞춰서 만들어주면 자동으로 SQL이 만들어져서 원하는 데이터를 가져올 수 있게 된다.
도서관에서 빌린 책으로 혼자서 스프링부트를 공부할 때 JPA 처음 접했었는데, 부트캠프에서 MyBatis를 배우면서 이게 굳이 왜 필요한지 의문이 들었었다. 하지만 두 가지를 직접 사용해보고 비교해보니, JPA가 편하긴 하지만 코드를 유지보수하는 관점에서는 약점이 있다고 느꼈다. 메소드의 이름을 SQL로 자동으로 번역해주는 기능은 상당히 강력하지만, 데이터베이스를 수정해야하는 상황이 발생하면 백엔드에서 메소드 이름을 변경해주어야 한다는 것이 소프트웨어 설계의 관점에서는 과연 타당한지 모르겠다.
아무튼 JPA를 사용하려면 스프링부트 프로젝트의 application.yml 파일에는 다음 내용을 추가해주어야 한다.
1
2
3
4
5
6
7
8
9
spring:
jpa:
hibernate:
ddl-auto: none
show-sql: true
properties:
hibernate:
dialect: org.hibernate.dialect.MariaDBDialect
format_sql: true
Entity (엔티티)
엔티티는 데이터베이스의 테이블에 대응하는 개념이다. 엔티티 클래스는 테이블 스키마를 정의하고, 객체로서 다루는 엔티티는 테이블의 레코드(행)을 의미한다.
1
2
3
4
5
6
7
8
9
10
11
@Entity
public class UserEntity {
@Id
private String email;
@Column
private String password;
@Column
private String name;
}
@Entity 스테레오타입 어노테이션을 사용한다.
Repository
MyBatis를 사용할 때는 Mapper 인터페이스를 정의해서 사용했다. 하지만 JPA를 사용한다면 Repository 인터페이스를 만들어서 사용하게 된다. Repository 접두사도 사실은 관행인데, 실무에서는 따를 수 밖에는 없을 것이다. JpaRepository 인터페이스를 상속하며 레포지토리가 반환하는 엔티티의 자료형과 기본키의 자료형을 전달하면 된다.
1
2
3
4
@Repository
public interface UserRepository extends JpaRepository<UserEntity, String> {
...
}
@Repository 스테레오타입 어노테이션을 사용한다.
JSON Web Token
저번주에 필터(filter)에 대해서 배웠다. 프론트엔드에서 백엔드로 접근할 때 그리고 자바 Servlet에 접근하기 전에, 클라이언트가 권한을 가지고 있는 여부 등을 점검할 때 사용한다. 그 권한은 토큰을 가지고 있는지 여부로 판단하고, 토큰은 JSON Web Token의 형태로 구현한다.
JWT는 통신에 사용되는 약속이다보니 왜 이렇게 만들어져 있는지 고민하는 것은 크게 의미가 없어보인다. 자주 접하면서 익숙해지는 것이 제일 좋을 것이다. JWT는 HTTP 요청의 헤더에 넣어서 전달된다. Access Token의 경우 Bearer 문자열을 앞에 붙여서 전달한다. Refresh Token은 이상한 문자열을 그냥 전달하면서 왜 Access Token 앞에는 Bearer 문자열을 붙이는지 궁금했다. ChatGPT에 물어보니 전달받은 토큰값을 어떻게 다룰지 같이 알려주는 것이라고 한다.
1
2
3
Authorization: Basic dXNlcjpwYXNzd29yZA==
Authorization: Digest ...
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Component
public class JwtFilter implements Filter {
private static final List<String> WHITELIST = List.of( ... );
@Override
public void doFilter(
ServletRequest servletRequest,
ServletResponse servletResponse,
FilterChain filterChain
) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
if (isPreflight(request)) {
System.out.println("Preflight request");
...
}
if (isWhitelisted(request.getRequestURI())) {
System.out.println("Whitelisted request");
...
}
String authorization = response.getHeader("Authorization");
if (authorization == null || !authorization.startsWith("Bearer ")) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
String accessToken = authorization.substring(7);
// 검증을 통과했다고 가정
filterChain.doFilter(request, response);
}
}
JWT 토큰값을 암호화하기
1
2
3
4
5
6
7
8
9
10
11
@Component
public class JwtProvider {
@Value("${jwt.secret}")
private String secret;
...
public Key getSecretKey() {
return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
}
}
Keys#hmacShaKeyFor() 메소드를 사용한다. 문자열 secret 을 byte 자료형으로 변환하고 UTF-8 같은 인코딩을 전달해서 토큰값을 암호화하기 위한 재료를 만들어준다. 그냥 문자열을 전달받아서 Key 객체를 만들게 하지않고 자료형 변환과 인코딩을 명시하게 한 이유가 무엇일까. 물론 그렇게 하면 개발자가 필요에 맞게 수정할 수 있는 여지가 있지만, 초보자 입장에서는 지나치게 많은 것이 드러나는 것은 아닌가하는 생각이 들었다.
Comments powered by Disqus.