인증 (Authentication) : 사용자의 신원 확인, 로그인
인가 (Authorization) : 인증된 사용자가 어떤 권한을 가지는지 판단, 관리자만 접근 가능한 페이지 등
Spring Security가 바로 자바 웹 애플리케이션의 인증과 인가를 담당하는 보안 프레임워크이다.

Controller로 가려는 HTTP의 요청 / 응답을 가로채서 전처리 / 후처리를 해주는 장치이다. 로그인도 체크하고, 토큰도 검사하고 등등 문제가 있는지 없는지 검사한다.
이 경로에 접근할 권한이 있는지 판단한다. 예시로 다음 코드를 보면.
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/mppage/**").authenticated()
.anyRequest().permitAll()
);
/admin API 호출은 hasRole(”ADMIN”)에 따라 ADMIN 역할만 접근이 가능하고, /mypage API는 authenticated()에 따라 로그인한 사람만 접근이 가능하며, 그 외 anyRequest는 permitAll()에 따라 누구나 접근 가능하다.
중복되는 유저네임이 있으면 에러를 반환하기로 했다. 중복 여부 판단을 위해 boolean 타입의 변수를 선언하고 중복을 찾는 레포를 만들면 될 것 같다. 이 때 메서드명에 exists를 추가하면 boolean 타입 반환을 할 수 있다.
이제 서비스 계층에서 위에서 선언한 메서드를 사용해야 한다. 매개변수로 username을 불러와야 하지만 private로 선언된 username을 바로 사용할 순 없다. 따라서 메서드를 이용해야 하는데, getUsername(); 을 이용하면 된다. getUsername을 DTO에서 선언하지 않았지만, @Getter 어노테이션과 Lombok 플러그인 덕분에 어노테이션만 달아도 편리하게 private 변수를 불러올 수 있다. (@Setter를 쓰면 private 변수의 내용을 바꿀 수 있다. 하지만 주의해서 사용해야 한다.)
Id를 이용해 해당 멤버를 찾아와야 한다. JPA 내부 메서드에 findById()가 이미 선언되어 있지만, 타입이 Optional로 선언되어 있다. 우리는 Member라는 임의의 객체를 만들었기 때문에 타입이 달라 오류가 난다. Optional이란 값이 null일 때를 대비한 타입인데…
혹은 Optional을 사용하여 예외처리를 하지 않고, Member로 받는 대신 orElseThrow를 사용하여도 된다. orElseThrow에서 람다형을 이용해 객체가 비었다면 CustomException의 MEMBER_NOT_FOUND를 생성하여 예외 처리를 한 번에 할 수 있다. 이런 식으로.
Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new CustomException(MEMBER_NOT_FOUND));
JPA 레포에 delete가 이미 구현되어 있으므로 그냥 사용하면 된다.

POST로 dohun을 생성한 뒤, 다시 username이 dohun인 회원을 POST 시도 시 409 confllict 에러가 정상적으로 반환된다.

parameter를 이용한 단건 조회

member가 없다면 404가 뜨게 된다.

POST시 401 Unauthorized 에러가 뜨게 된다.
localhost:8080으로 로컬에서 직접 로그인하게 되면 자동으로 기본 로그인 페이지를 생성해 준다. 이 기본 로그인 페이지를 커스터마이징 하는 과정은 다음 시간부터 진행하게 된다.
프로젝트 트랙에서도 애용했던 record로 DTO를 변경하였다. record로 클래스를 선언할 때는 {} 가 아니라 먼저 ()를 이용해서 매개변수를 쭉 나열해준 뒤 그 다음 오는 {}안에 필요하다면 메서드를 정의해주면 된다. 헤더에 나열되는 필드를 컴포넌트라고 한다. 생성자, getter(), equals() 등 롬복의 어노테이션 필요 없이 메서드를 자동 제공하기 때문에 더 간결하고 편리하다. 각 필드는 private final로 자동 정의된다. 따라서 얻을 수 있는 이점은
또한, 서비스 계층에서의 활용에서도 간편한데, username을 불러오고 싶다면 getUsername() 대신 그냥 필드명인 username() 쓰면 된다. 필드명이 곧 메서드명이 되는 것이다.
of : 값 → 객체 생성, 파라미터로 DTO 객체를 만들 때 사용한다.
from : 객체 → 객체 변환, Entity를 DTO 객체로 만들 때 사용한다.
역시 코드의 가독성과 재사용성을 높일 수 있다. 예를 들면 다음과 같다.
원래의 MemberInfoResponse DTO는 다음과 같았다.
@Getter
public class MemberInfoResponse {
private final Long memberId;
private final String username;
public MemberInfoResponse(Member member) {
this.memberId = member.getId();
this.username = member.getUsername();
}
}
MemberInfoResponse 생성자가 Member 객체인 member를 받아 DTO를 생성하고 있다. 이를 Record로 변환하면.
public record MemberInfoResponse(
Long memberId,
String username
) {
public MemberInfoResponse(Member member) {
this(member.getId(), member.getUsername());
}
}
이렇게 1차로 간결하게 만들 수 있다. 여기서 메서드가 Member 객체를 DTO 객체로 만들고 있으므로 from을 이용하면.
public record MemberInfoResponse(
Long memberId,
String username
) {
public static MemberInfoResponse from(Member member) {
// 기본 생성자 호출
return new MemberInfoResponse(
member.getId(),
member.getUsername()
);
}
}
정적 메서드로 from 메서드를 정의할 수 있다. 이렇게 만들고 서비스 계층에서
MemberInfoResponse response = MemberInfoResponse.from(member);
new로 새 객체를 만드는 대신 from(member)로 변환의 느낌을 명확하게 보여줄 수 있으며 오버로딩 시 유지보수에도 편리하다.