관리 메뉴

bright jazz music

blog 31 : 세션 토큰 발급기능 추가 2 본문

Projects/blog

blog 31 : 세션 토큰 발급기능 추가 2

bright jazz music 2023. 2. 15. 22:48

클라이언트 입장에서는 전송하기 위해 세션(=accessToken)이 필요하다.

현재는 AuthService에서 user.addSession()만 할 뿐 그 세션 값을 가져오지는 않고 있다.

 

1. User 클래스의 addSession() 메서드가 세션을 반환하도록 설정

//User.java

package com.endofma.blog.domain;

import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name="users") //테이블 명 설정 user는 예약어라 오류발생
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private String email;

    private String password;

    private LocalDateTime createdAt;

    //연관관계 맺기
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "user") //한 명의 유저가 여러 세션을 가질 수 있기 때문에 OneToMany
    private List<Session> sessions = new ArrayList<>();

    @Builder
    public User(String name, String email, String password){
        this.name = name;
        this.email = email;
        this. password = password;
        this.createdAt = LocalDateTime.now(); //정책상 가입하여 생성된 시간을 넣어준다.
        //세션은 넣지 않는다. 가입을 한다고 해서 세션을 추가해 줄 필요는 없다.
    }

    public Session addSession() {
        Session session = Session.builder()
                .user(this)
                .build();
        sessions.add(session);

        return session;
    }
}

 

2. AuthService 수정

//AuthService.java

package com.endofma.blog.service;

import com.endofma.blog.domain.Session;
import com.endofma.blog.domain.User;
import com.endofma.blog.exception.InvalidSigninInformation;
import com.endofma.blog.repository.UserRepository;
import com.endofma.blog.request.Login;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;

@Service
@RequiredArgsConstructor
public class AuthService {

    private final UserRepository userRepository;

    @Transactional
    public String signin(Login login){
        User user = userRepository.findByEmailAndPassword(login.getEmail(), login.getPassword())
                .orElseThrow(InvalidSigninInformation::new);
        //세션 발급
        Session session = user.addSession();
        return session.getAccessToken();
    }
}

 

3. AuthController 수정

//AuthController.java

package com.endofma.blog.controller;

import com.endofma.blog.domain.User;
import com.endofma.blog.exception.InvalidRequest;
import com.endofma.blog.exception.InvalidSigninInformation;
import com.endofma.blog.repository.UserRepository;
import com.endofma.blog.request.Login;
import com.endofma.blog.service.AuthService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;


@Slf4j
@RestController
@RequiredArgsConstructor
public class AuthController {

    //생성자를 통한 주입
    //private final UserRepository userRepository;
    //로그인 및 인증 절차를 서비스에 위임
    private final AuthService authService;

    @PostMapping("/auth/login")
    public String login(@RequestBody Login login){

        String accessToken = authService.signin(login);

        return accessToken;
    }
}

 

서버 재기동

 

4. http 요청 

### auth.http

POST http://localhost:8080/auth/login
Content-Type: application/json

{
  "email" : "catnails@gmail.com",
  "password" : "1234"
}

accessToken이 text/plain의 문자열 형태로 오고 있음

따라서 이를 json타입으로 리턴하도록 만들어준다.

 

5. 응답 클래스 생성: SessionReponse.java

//SesseionResponse.java
package com.endofma.blog.response;

import lombok.Getter;

@Getter
public class SessionResponse {

    private final String accessToken;

    public SessionResponse(String accessToken){
        this.accessToken = accessToken;
    }
}

 

6. AuthController 수정

//AuthController.java

package com.endofma.blog.controller;


import com.endofma.blog.request.Login;
import com.endofma.blog.response.SessionResponse;
import com.endofma.blog.service.AuthService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;


@Slf4j
@RestController
@RequiredArgsConstructor
public class AuthController {

    //생성자를 통한 주입
    //private final UserRepository userRepository;
    //로그인 및 인증 절차를 서비스에 위임
    private final AuthService authService;

//    @ResponseBody
    @PostMapping("/auth/login")
    public SessionResponse login(@RequestBody Login login){

        String accessToken = authService.signin(login);
        return new SessionResponse(accessToken);
    }
}

? @ResponseBody 안 붙여도 되네

### auth.http

POST http://localhost:8080/auth/login
Content-Type: application/json

{
  "email" : "catnails@gmail.com",
  "password" : "1234"
}

json 형태로 응답 받은 것을 확인할 수 있다.

 

7. 테스트 코드 작성

test3

package com.endofma.blog.controller;

import com.endofma.blog.domain.User;
import com.endofma.blog.repository.SessionRepository;
import com.endofma.blog.repository.UserRepository;
import com.endofma.blog.request.Login;
import com.endofma.blog.request.PostCreate;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.juli.logging.Log;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

import javax.print.attribute.standard.Media;
import javax.transaction.Transactional;

import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.jsonPath;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@AutoConfigureMockMvc
public class AuthControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private SessionRepository sessionRepository;

    @BeforeEach
    void clean(){
    userRepository.deleteAll();
    }

    @Test
    @DisplayName("로그인 성공 ")
    void test1() throws Exception {
        //given
        //테스트에서는 data.sql이 실행되지 않으므로, 또 실행된다고 해도 테스트와 독립된 것이 권장되므로 값을 넣어준다.
        userRepository.save(User.builder()
                .name("catnails")
                .email("catnails@gmail.com")
                .password("1234") //추후 scrypt, bcrypt 적용
                .build());

        Login login = Login.builder()
                .email("catnails@gmail.com")
                .password("1234")
                .build();

        String json = objectMapper.writeValueAsString(login);

        //expected
        mockMvc.perform(post("/auth/login")
                .contentType(MediaType.APPLICATION_JSON)
                .content(json))
                .andExpect(status().isOk())
                .andDo(print());

    }
    @Test
    @Transactional
    @DisplayName("로그인 성공 후 세션 추가")
    void test2() throws Exception {
        //given
        User user = userRepository.save(User.builder()
                .name("catnails")
                .email("catnails@gmail.com")
                .password("1234") //추후 scrypt, bcrypt 적용
                .build());

        Login login = Login.builder()
                .email("catnails@gmail.com")
                .password("1234")
                .build();

        String json = objectMapper.writeValueAsString(login);

        //expected
        mockMvc.perform(post("/auth/login")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(json))
                .andExpect(status().isOk())
                .andDo(print());

        User loggedInUser = userRepository.findById(user.getId())
                        .orElseThrow(RuntimeException::new);

        Assertions.assertEquals(1L, user.getSessions().size());

    }

    @Test
    @DisplayName("로그인 성공 세션 응답")
    void test3() throws Exception {
        //given
        User user = userRepository.save(User.builder()
                .name("catnails")
                .email("catnails@gmail.com")
                .password("1234") //추후 scrypt, bcrypt 적용
                .build());

        Login login = Login.builder()
                .email("catnails@gmail.com")
                .password("1234")
                .build();

        String json = objectMapper.writeValueAsString(login);

        //expected
        mockMvc.perform(post("/auth/login")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(json))
                .andExpect(status().isOk())
//                .andExpect(MockMvcResultMatchers.jsonPath("$.accessToken", Matchers.nullValue()))
                .andExpect(MockMvcResultMatchers.jsonPath("$.accessToken", Matchers.notNullValue()))
                .andDo(print());

    }


}

 

성공

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.7.6)

2023-02-15 23:18:22.533  INFO 11192 --- [    Test worker] c.e.blog.controller.AuthControllerTest   : Starting AuthControllerTest using Java 11.0.12 on DESKTOP-Q7HBM41 with PID 11192 (started by user in D:\personal\blog)
2023-02-15 23:18:22.535  INFO 11192 --- [    Test worker] c.e.blog.controller.AuthControllerTest   : No active profile set, falling back to 1 default profile: "default"
2023-02-15 23:18:23.562  INFO 11192 --- [    Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2023-02-15 23:18:23.670  INFO 11192 --- [    Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 96 ms. Found 3 JPA repository interfaces.
2023-02-15 23:18:24.426  INFO 11192 --- [    Test worker] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2023-02-15 23:18:24.769  INFO 11192 --- [    Test worker] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2023-02-15 23:18:24.881  INFO 11192 --- [    Test worker] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2023-02-15 23:18:24.972  INFO 11192 --- [    Test worker] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 5.6.14.Final
2023-02-15 23:18:25.267  INFO 11192 --- [    Test worker] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2023-02-15 23:18:25.503  INFO 11192 --- [    Test worker] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
2023-02-15 23:18:26.591  INFO 11192 --- [    Test worker] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2023-02-15 23:18:26.603  INFO 11192 --- [    Test worker] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2023-02-15 23:18:27.727  WARN 11192 --- [    Test worker] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2023-02-15 23:18:28.221  INFO 11192 --- [    Test worker] o.s.b.a.h2.H2ConsoleAutoConfiguration    : H2 console available at '/h2-console'. Database available at 'jdbc:h2:mem:blog'
2023-02-15 23:18:28.757  INFO 11192 --- [    Test worker] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring TestDispatcherServlet ''
2023-02-15 23:18:28.757  INFO 11192 --- [    Test worker] o.s.t.web.servlet.TestDispatcherServlet  : Initializing Servlet ''
2023-02-15 23:18:28.758  INFO 11192 --- [    Test worker] o.s.t.web.servlet.TestDispatcherServlet  : Completed initialization in 0 ms
2023-02-15 23:18:28.793  INFO 11192 --- [    Test worker] c.e.blog.controller.AuthControllerTest   : Started AuthControllerTest in 6.806 seconds (JVM running for 10.206)

MockHttpServletRequest:
      HTTP Method = POST
      Request URI = /auth/login
       Parameters = {}
          Headers = [Content-Type:"application/json;charset=UTF-8", Content-Length:"48"]
             Body = {"email":"catnails@gmail.com","password":"1234"}
    Session Attrs = {}

Handler:
             Type = com.endofma.blog.controller.AuthController
           Method = com.endofma.blog.controller.AuthController#login(Login)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Type:"application/json"]
     Content type = application/json
             Body = {"accessToken":"53f70d4c-0792-4cb0-8ecb-f362a3bf49d4"}
    Forwarded URL = null
   Redirected URL = null
          Cookies = []
2023-02-15 23:18:29.775  INFO 11192 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2023-02-15 23:18:29.776  INFO 11192 --- [ionShutdownHook] .SchemaDropperImpl$DelayedDropActionImpl : HHH000477: Starting delayed evictData of schema as part of SessionFactory shut-down'
2023-02-15 23:18:29.782  WARN 11192 --- [ionShutdownHook] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 90121, SQLState: 90121
2023-02-15 23:18:29.782 ERROR 11192 --- [ionShutdownHook] o.h.engine.jdbc.spi.SqlExceptionHelper   : Database is already closed (to disable automatic closing at VM shutdown, add ";DB_CLOSE_ON_EXIT=FALSE" to the db URL) [90121-214]
2023-02-15 23:18:29.784  WARN 11192 --- [ionShutdownHook] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 90121, SQLState: 90121
2023-02-15 23:18:29.784 ERROR 11192 --- [ionShutdownHook] o.h.engine.jdbc.spi.SqlExceptionHelper   : Database is already closed (to disable automatic closing at VM shutdown, add ";DB_CLOSE_ON_EXIT=FALSE" to the db URL) [90121-214]
2023-02-15 23:18:29.784  WARN 11192 --- [ionShutdownHook] o.s.b.f.support.DisposableBeanAdapter    : Invocation of destroy method failed on bean with name 'entityManagerFactory': org.hibernate.exception.JDBCConnectionException: Unable to release JDBC Connection used for DDL execution
2023-02-15 23:18:29.784  INFO 11192 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2023-02-15 23:18:29.790  INFO 11192 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
BUILD SUCCESSFUL in 14s
5 actionable tasks: 2 executed, 3 up-to-date
PM 11:18:30: Execution finished ':test --tests "com.endofma.blog.controller.AuthControllerTest.test3"'.

 

Comments