세션 기반 인증을 구현해봅시다.
springboot:2.7.12
org.springframework.boot:spring-boot-starter-web
org.springframework.boot:spring-boot-starter-data-jpa
org.springframework.boot:spring-boot-starter-security
(암호화를 위해 사용)org.mapstruct:mapstruct:1.5.3.Final
com.h2database:h2
lombok
패키지 구성은 Layered Architecture로 구성하였습니다.
package com.example.loginexample.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class PasswordEncoderConfig {
@Bean
public PasswordEncoder BcyPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
package com.example.loginexample;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
public class LoginExampleApplication {
public static void main(String[] args) {
SpringApplication.run(LoginExampleApplication.class, args);
}
}
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
MemberMapper.class
package com.example.loginexample.mapper;
import com.example.loginexample.domain.member.Member;
import com.example.loginexample.dto.MemberDto;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper(componentModel = "spring")
public interface MemberMapper {
Member toEntity(MemberDto memberDto);
MemberDto toDto(Member member);
}
Mapper를 통해 각 계층간 DTO를 통해 데이터를 교환하게해 계층간 결합도를 낮췄습니다.
AuthenticationDto.class
package com.example.loginexample.dto;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class AuthenticationDto {
private final String accessToken;
private final String refreshToken;
private final String memberId;
public AuthenticationDto(String accessToken, String refreshToken, String memberId) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
this.memberId = memberId;
}
}
LoginDto.class
package com.example.loginexample.dto;
import lombok.Data;
@Data
public class LoginDto {
private String email;
private String password;
}
MemberDto.class
package com.example.loginexample.dto;
import lombok.Builder;
import lombok.Data;
import java.io.Serializable;
@Data
@Builder
public class MemberDto implements Serializable {
private final Long id;
private final String memberId;
private final String email;
private final String password;
}
ResponseDto.class
package com.example.loginexample.dto;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class ResponseDto<T> {
private T body;
}
LoginFailureException.class
package com.example.loginexample.exception;
public class LoginFailureException extends IllegalStateException {
private static final String MESSAGE = "이메일 혹은 비밀번호를 확인해주세요.";
public LoginFailureException() {
super(MESSAGE);
}
}
Member.class
package com.example.loginexample.domain.member;
import lombok.*;
import javax.persistence.*;
@Entity
@Table(name = "member")
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Getter
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "member_id", unique = true, nullable = false)
private String memberId;
@Column(name = "email", unique = true, nullable = false)
private String email;
@Column(name = "password", nullable = false)
private String password;
}
Id
는 DB에서 생성하는 PK를 위한 필드입니다.memberID
는 UUID를 통해 생성하여 Member
의 고유번호입니다.MemberReadRepository.class
package com.example.loginexample.domain.member;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface MemberRepository extends JpaRepository<Member, Long> {
Optional<Member> findByEmail(String email);
Optional<Member> findByMemberId(String memberId);
}
findByEmail
: email을 통해 Member를 찾습니다.findByMemberId
: MemberID를 통해 Member를 찾습니다.MemberReadService.class
package com.example.loginexample.domain.member;
import com.example.loginexample.dto.MemberDto;
import com.example.loginexample.mapper.MemberMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class MemberReadService {
private final MemberRepository memberRepository;
private final MemberMapper memberMapper;
public MemberDto findMemberByEmail(String email) {
Membermember = memberRepository.findByEmail(email)
.orElseThrow(() -> new IllegalArgumentException());
return memberMapper.toDto(member);
}
}
MemberWriteService.class
package com.example.loginexample.domain.member;
import com.example.loginexample.dto.MemberDto;
import com.example.loginexample.mapper.MemberMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.UUID;
@Service
@RequiredArgsConstructor
public class MemberWrtieService {
private final MemberRepository memberRepository;
private final PasswordEncoder passwordEncoder;
private final MemberMapper memberMapper;
public MemberDto join(MemberDto memberDto) {
MemberjoinMember = makeMember(memberDto);
return memberMapper.toDto(memberRepository.save(joinMember));
}
private Member makeMember(MemberDto memberDto) {
Stringpassword = memberDto.getPassword();
StringencryptPassword = passwordEncoder.encode(password);
MemberDtojoinMemberDto= MemberDto.builder()
.id(memberDto.getId())
.memberId(UUID.randomUUID().toString())
.email(memberDto.getEmail())
.password(encryptPassword)
.build();
return memberMapper.toEntity(joinMemberDto);
}
}
join
: password를 암호화해서 DB의 저장하는 로직입니다.AuthMemberFacade.class
package com.example.loginexample.facade;
import com.example.loginexample.dto.AuthenticationDto;
import com.example.loginexample.dto.MemberDto;
public interface AuthMemberFacade {
MemberDto loginSession(String email, String password);
}
AuthMemberFacadeImpl.class
package com.example.loginexample.facade;
import com.example.loginexample.domain.member.MemberReadService;
import com.example.loginexample.dto.MemberDto;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class AuthMemberFacadeImpl implements AuthMemberFacade {
private final MemberReadService memberReadService;
private final PasswordEncoder passwordEncoder;
@Override
public MemberDto loginSession(String email, String password) {
MemberDto findMember = memberReadService.findMemberByEmail(email);
if (passwordEncoder.matches(password, findMember.getPassword()))
return findMember;
return null;
}
}
loginSession
MemberController.class
package com.example.loginexample.presentation;
import com.example.loginexample.domain.member.MemberWrtieService;
import com.example.loginexample.dto.MemberDto;
import com.example.loginexample.dto.ResponseDto;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/members")
@RequiredArgsConstructor
public class MemberController {
private final MemberWrtieService memberWrtieService;
@PostMapping
public ResponseEntity<MemberDto> join(@RequestBody MemberDto memberDto) {
MemberDto joinMember = memberWrtieService.join(memberDto);
return ResponseEntity
.ok()
.body(joinMember);
}
}
AuthMessage.class
package com.example.loginexample.presentation.auth;
public enum AuthMessage {
LOGIN_SUCCESS("로그인이 되었습니다."),
IS_LOGIN_TRUE("로그인이 되어있습니다."),
IS_LOGIN_FALSE("로그인이 되어 있지 않습니다."),
INVALID_TOKEN("토큰이 유효하지 않습니다."),
REISSUE_TOKEN("토큰이 재발급되었습니다."),
LOGOUT("로그아웃 되었습니다.");
public final String message;
AuthMessage(String message) {
this.message = message;
}
@Override
public String toString() {
return message;
}
}
SessionConst.class
package com.example.loginexample.presentation.auth;
public interface SessionConst {
String LOGIN_MEMBER = "loginMember";
}
LoginCotroller.class
import static com.example.loginexample.presentation.auth.AuthMessage.*;
@RestController
@RequiredArgsConstructor
public class LoginController {
private final AuthMemberFacade authMemberFacade;
@PostMapping("/loginV1")
public ResponseEntity<ResponseDto> loginV1(@RequestBody LoginDto loginDto, HttpServletRequest request) {
MemberDto loginMember = authMemberFacade.loginSession(loginDto.getEmail(), loginDto.getPassword());
// 로그인 실패
if (loginMember == null)
throw new LoginFailureException();
// 로그인 성공
HttpSession session = request.getSession();
session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);
ResponseDto responseDto = ResponseDto.builder()
.body(LOGIN_SUCCESS.message)
.build();
return ResponseEntity
.status(HttpStatus.CREATED)
.body(responseDto);
}
@PostMapping("/logoutV1")
public ResponseEntity<ResponseDto> logoutV1(HttpServletRequest request) {
request.getSession().invalidate();
ResponseDto responseDto = ResponseDto.builder()
.body(LOGOUT.message)
.build();
return ResponseEntity
.status(HttpStatus.OK)
.body(responseDto);
}
@GetMapping("/is-loginV1")
public ResponseEntity<ResponseDto> isLoginV1(HttpServletRequest request) {
HttpSession session = request.getSession(false);
ResponseDto responseDto = ResponseDto
.builder()
.body((session != null) ? IS_LOGIN_TRUE.message : IS_LOGIN_FALSE.message)
.build();
return ResponseEntity
.ok()
.body(responseDto);
}
}
loginV1
authMemberFacade.loginSession
을 통해 MemberDto를 반환받습니다.LoginFailureException
을 발생시킵니다.logoutV1
isLoginV1
좋은 글 잘 봤습니다^^