- 인증: 누구인지 확인하는 절차
- 인가: 특정 행위를 할 수 있는지 확인하는 절차
인증을 먼저 하고, 그 다음에 인가 (누구인지 알아야 권한이 있는지를 확인 가능)
401 Unauthorized403 Forbidden사용자의 로그인 정보를 서버가 보관하는 공간 혹은 방식
서버가 사용자를 기억해야 함 -> Stateful
자바에서는 JSESSIONID라는 키에 세션값(쿠키)을 담아 보낸다.
아래에 헷갈렸던 포인트를 정리했다.
Set-Cookie(서버 응답) 혹은 Cookie(클라 요청)이다.JSESSIONID=random_value이다.이름=값;옵션...의 문자열 형태로 전송된다.JSESSIONID인 쿠키를 찾고 그 값을 추출한다.Map<SessionID, HttpSession> 형태)에서 키를 통해 해당 브라우저 전용 세션 객체를 찾아온다.1. 엔티티
@Getter
@Entity
@Table(name = "members")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
private String password;
public Member(String email, String password) {
this.email = email;
this.password = password;
}
}
2. DTO
@Getter
public class SignupRequest {
private String email;
private String password;
}
@Getter
public class LoginRequest {
private String email;
private String password;
}
@Getter
@RequiredArgsConstructor
public class MemberResponse {
private final Long id;
private final String email;
}
3. 레포지토리
public interface MemberRepository extends JpaRepository<Member, Long> {
Optional<Member> findByEmail(String email);
}
4. 서비스
@Service
@RequiredArgsConstructor
public class AuthService {
private final MemberRepository memberRepository;
// 가입
@Transactional
public MemberResponse signup(SignupRequest request) {
Member member = memberRepository.save(new Member(request.getEmail(), request.getPassword()));
return new MemberResponse(member.getId(), member.getEmail());
}
// 로그인
@Transactional(readOnly = true)
public MemberResponse authenticate(LoginRequest request) {
Member member = memberRepository.findByEmail(request.getEmail())
.orElseThrow(() -> new ResponseStatusException(HttpStatus.UNAUTHORIZED));
// 비밀번호 일치 확인
if (!member.getPassword().equals(request.getPassword())) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
}
return new MemberResponse(member.getId(), member.getEmail());
}
// 회원 정보 조회
@Transactional(readOnly = true)
public MemberResponse requireMember(Long memberId) {
if (memberId == null) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
}
Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.UNAUTHORIZED));
return new MemberResponse(member.getId(), member.getEmail());
}
}
세션 관리용 상수
public abstract class SessionConst {
public static final String LOGIN_MEMBER_ID = "LOGIN_MEMBER_ID";
}
컨트롤러
@RestController
@RequiredArgsConstructor
public class AuthController {
private final AuthService authService;
// 가입
// 새 멤버를 등록하는 일
// 기존에 다루던 POST와 동일
@PostMapping("/signup")
public ResponseEntity<MemberResponse> signup(
@RequestBody SignupRequest body
) {
return ResponseEntity.status(HttpStatus.CREATED).body(authService.signup(body));
}
// 로그인
// DB를 바꾸지 않는데 POST?
// DB는 그대로지만, 서버 내부 세션 저장소가 변경됨 (세션 생성 혹은 변경)
// 서버의 상태가 변경되는 요청이기 때문에 POST
// 또한 GET 방식은 아이디와 비밀번호가 URL 쿼리 파라미터로 노출되어야 함(평문)
// POST는 body에 담아 비문으로 전송될 수 있음(HTTPS에서)
@PostMapping("/login")
public ResponseEntity<MemberResponse> login(
@RequestBody LoginRequest body,
HttpServletRequest request
) {
// 서비스 레이어에서 인증 (실패시 401 Unauthorized)
MemberResponse member = authService.authenticate(body);
// 세션 획득 혹은 새로 생성
// 요청에 JSESSIONID 값(쿠키)이 실려왔다면 해당 키와 일치하는 세션 탐색
// 요청에 쿠키가 없다면 새로 생성
HttpSession session = request.getSession(); // 기본 인자 true: 세션이 없으면 생성
// 세션 아이디 재발급 (하이재킹 방지)
// 세션은 유지되지만, 세션을 조회할 쿠키 값이 바뀌는 것
request.changeSessionId();
// 해당 세션 공간에 LOGIN_MEMBER_ID를 키로 멤버 Id 저장
session.setAttribute(SessionConst.LOGIN_MEMBER_ID, member.getId());
return ResponseEntity.ok().cacheControl(CacheControl.noStore()).body(member);
}
// 내 정보 가져오기
@GetMapping("/me")
public ResponseEntity<MemberResponse> me(
@SessionAttribute(name = SessionConst.LOGIN_MEMBER_ID, required = false) Long memberId
) {
return ResponseEntity.ok().cacheControl(CacheControl.noStore())
.body(authService.requireMember(memberId));
}
// 로그아웃
@PostMapping("/logout")
public ResponseEntity<Void> logout(
HttpServletRequest request
) {
// 세션이 있다면 가져옴
HttpSession session = request.getSession(false);
// 세션이 있다면
if (session != null) {
// 세션을 무효화 (없앰)
session.invalidate();
}
return ResponseEntity.noContent().build();
}
}
가입

로그인

응답 헤더에 Set-Cookie 키와 JSESSIONID=...값을 확인할 수 있다.
정보 조회

logout API 날린 뒤
환경 변수에 아래와 같은 값을 추가한다.
server.servlet.session.timeout=30m // 분 단위 세션 만료
server.servlet.session.timeout=30s // 초 단위 세션 만료