SNS 제작 (로그인)

개발연습생log·2022년 12월 21일
0

SNS 제작

목록 보기
3/15
post-thumbnail

✨개요

🏃 목표

📢 로그인 기능을 구현하자.

📢 요구사항

  • Spring Security와 JWT를 활용하여 구현한다.
  • 로그인 성공 시 토큰을 리턴하고, userNamepassword 가 틀릴 시 예외처리를 한다.
  • POST /login
  • 입력폼 (JSON 형식)
    {
    	"userName" : "user1",
    	"password" : "user1234"
    }
  • 리턴 (JSON 형식)
    {
        "resultCode": "SUCCESS",
        "result": {
            "jwt": "eyJhbGciOiJIU",
        }
    }

✅ TO-DO

  • 로그인 테스트 작성
  • 로그인 컨트롤러 구현
  • 로그인 서비스 구현
  • JWT 토큰 발행

🔧 구현

로그인 테스트 작성

<@WebMvcTest
class UserControllerTest {

    @Autowired
    MockMvc mockMvc;

    @MockBean
    UserService userService;

    @Autowired
    ObjectMapper objectMapper;
		
		// 3가지 테스트 추가
    @Test
    @DisplayName("로그인 성공")
    @WithMockUser
    void login_SUCCESS()throws Exception{

        String userName = "홍길동1";
        String password = "0000";

        when(userService.login(any(),any()))
                .thenReturn(new UserLoginResponse("token"));

        mockMvc.perform(post("/api/v1/users/login")
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsBytes(new UserLoginRequest(userName, password))))
                .andDo(print())
                .andExpect(status().isOk());
    }

    @Test
    @DisplayName("로그인 실패_userName이 존재하지 않는 경우")
    @WithMockUser
    void login_FAILED_userName()throws Exception{

        String userName = "홍길동1";
        String password = "0000";

        when(userService.login(any(),any()))
                .thenThrow(new AppException(ErrorCode.USERNAME_NOT_FOUND,ErrorCode.USERNAME_NOT_FOUND.getMessage()));

        mockMvc.perform(post("/api/v1/users/login")
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsBytes(new UserLoginRequest(userName, password))))
                .andDo(print())
                .andExpect(status().isNotFound());
    }

    @Test
    @DisplayName("로그인 실패_password가 다른 경우")
    @WithMockUser
    void login_FAILED_password()throws Exception{

        String userName = "홍길동1";
        String password = "0000";

        when(userService.login(any(),any()))
                .thenThrow(new AppException(ErrorCode.INVALID_PASSWORD,ErrorCode.INVALID_PASSWORD.getMessage()));

        mockMvc.perform(post("/api/v1/users/login")
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsBytes(new UserLoginRequest(userName, password))))
                .andDo(print())
                .andExpect(status().isUnauthorized());
    }

}

로그인 컨트롤러 구현

UserController 메서드 추가

@PostMapping("/login")
    public ResponseEntity<Response> login(@RequestBody UserLoginRequest userLoginRequest){
        UserLoginResponse userLoginResponse = userService.login(userLoginRequest.getUserName(),userLoginRequest.getPassword());
        return ResponseEntity.ok().body(Response.toResponse("SUCCESS",userLoginResponse));
    }

UserLoginRequest 구현

@AllArgsConstructor
@NoArgsConstructor
@Builder
@Setter
@Getter
public class UserLoginRequest {
    private String userName;
    private String password;
}

UserLoginResponse 구현

@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
@Setter
public class UserLoginResponse {
    private String token;
    
    public static UserLoginResponse of(String token){
        return UserLoginResponse.builder()
                .token(token)
                .build();
    }
}

로그인 서비스 구현

UserService 메서드 추가

public UserLoginResponse login(String userName, String password) {
        //userName 체크
        User selectedUser = userRepository.findByUserName(userName).orElseThrow(() -> {
            throw new AppException(ErrorCode.USERNAME_NOT_FOUND,ErrorCode.USERNAME_NOT_FOUND.getMessage());
        });
        //패스워드 체크
        if(!encoder.matches(password,selectedUser.getPassword())){
            throw new AppException(ErrorCode.INVALID_PASSWORD,ErrorCode.INVALID_PASSWORD.getMessage());
        }
        //토큰 발행
        return UserLoginResponse.of("token");
    }

JWT 토큰 발행

jjwt 의존성 추가

  • implementation 'io.jsonwebtoken:jjwt:0.9.1'

JwtTokenUtil

public class JwtTokenUtil {

    public static String createToken(String userName, String key, long expireTimeMs) {
        Claims claims = Jwts.claims();
        claims.put("userName", userName);

        return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + expireTimeMs))
                .signWith(SignatureAlgorithm.HS256, key)
                .compact();
    }
}

UserService 수정

@Service
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;
    private final BCryptPasswordEncoder encoder;

    @Value("${jwt.token.key}")
    private String key;

    private long expireTimeMs = 1000 * 60 * 60l;

    public UserJoinResponse join(String userName, String password) {
        //userName 중복확인
        userRepository.findByUserName(userName).ifPresent(user -> {
            throw new AppException(ErrorCode.DUPLICATED_USER_NAME, ErrorCode.DUPLICATED_USER_NAME.getMessage());
        });
        //저장
        User savedUser = User.of(userName, encoder.encode(password));
        savedUser = userRepository.save(savedUser);
        //ResponseDTO
        UserJoinResponse userJoinResponse = UserJoinResponse.of(savedUser.getUserId(), savedUser.getUserName());
        return userJoinResponse;
    }

    public UserLoginResponse login(String userName, String password) {
        //userName 체크
        User selectedUser = userRepository.findByUserName(userName).orElseThrow(() -> {
            throw new AppException(ErrorCode.USERNAME_NOT_FOUND, ErrorCode.USERNAME_NOT_FOUND.getMessage());
        });
        //패스워드 체크
        if (!encoder.matches(password, selectedUser.getPassword())) {
            throw new AppException(ErrorCode.INVALID_PASSWORD, ErrorCode.INVALID_PASSWORD.getMessage());
        }
        //토큰 발행
        String token = JwtUtil.createToken(selectedUser.getUserName(), key, expireTimeMs);
        return UserLoginResponse.of(token);
    }
}

환경변수 추가

jwt:
  token:
    key: ${KEY}

🌉 회고

  • 예전에는 로그인까지 부랴부랴 구현을 할 수 있었지만 토큰 발행은 구현하기 힘들었다.
  • 오늘 프로젝트를 진행하면서 토큰 발행의 대략적인 과정을 조금 이해하게 된 것 같다.
profile
주니어 개발자를 향해서..

0개의 댓글