개발/Spring Security(Hodol)

Spring - Session 토큰 발급 과정

잇(IT) 2023. 10. 5. 20:14
728x90

- Session 토큰 발급

- AuthController.java

@Slf4j
@RestController
@RequiredArgsConstructor
public class AuthController {

    private final AuthService authService;

    @PostMapping("/auth/login")
    public SessionResponse login(@RequestBody Login login){
    
        log.info(">>>login={}", login);
        
        String accessToken = authService.signin(login);

        return new SessionResponse(accessToken);

    }
}

1. 로그인 Controller에서 /auth/login 경로에 Post를 통해 로그인 관련 데이터를 전달하게 되면, Login 클래스에 매핑되어 데이터가 전달된다.

2. authService 클래스의 signin메서드가 실행되고, 요청의 응답 데이터로 SessionResponse 객체를 반환한다.

 

- AuthService.java

@Slf4j
@Service
@RequiredArgsConstructor
public class AuthService {

    private final MemberRepository memberRepository;

    @Transactional
    public String signin(Login login) {
        Member member = memberRepository.findByEmailAndPassword(login.getEmail(), login.getPassword())
                .orElseThrow(InvalidSigninInformation::new);
        Session session = member.addSession();

        return session.getAccessToken();
    }
}

1. Controller에서 signin 메서드를 실행하게 되면, login 객체의 email, password 값을 가져와 DB로부터 해당 값에 대응하는 member 객체를 가져온다.

2. 유한 객체라면 예외가 발생하지 않고, Member 클래스에 있는 addSession 메서드를 실행한다.

 2.1 addSession 메서드는 로그인이 되었을 때 세션을 생성하여 클라이언트에게 Session 값을 전달하기 위한 메서드이다.

3. Member의 addSession() 메서드의 반환 타입이 Session이고, Session으로부터 accessToken 값을 signin 메서드의 반환값으로 전달한다.

 

* Member와 Session은 OneToMany 관계에 있다. 사용자 한명이 여러개의 세션을 가질 수 있기 때문이다.

 

- Member.java

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@ToString
public class Member {

    @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 = "member")
    private List<Session> sessions = new ArrayList<>();

    @Builder
    public Member(Long id, String name, String email, String password, LocalDateTime createdAt, List<Session> sessions) {
        this.name = name;
        this.email = email;
        this.password = password;
        this.createdAt = LocalDateTime.now();
    }

    public Session addSession() {
        Session session = Session.builder()
                .member(this)
                .build();

        sessions.add(session);

        return session;
    }
}

1. Session 엔티티와 Member 엔티티는 OneToMany 관계에 있으므로 List<Session>을 통해 Member 엔티티에 리스트를 생성하여 보관할 수 있다.

2. Member 엔티티의 addSession()은 Member 엔티티와 Session 엔티티가 연관관계에 있기 때문에 addSession() 메서드가 호출되는 순간 Session 객체의 member 필드에 현재 Member 객체가 설정된다.

 

- Session.java

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Session {

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

    private String accessToken;

    @ManyToOne
    private Member member;

    @Builder
    public Session(Member member) {
        this.accessToken = UUID.randomUUID().toString();
        this.member = member;
    }
}

1. Session 엔티티는 Member 클래스를 파라미터로 받는 생성자를 가진다.

2. Session 객체가 생성되면서 해당 생성자가 실행되는데 생성자가 실행될 때 임의의 UUID를 생성하여 accessToken 변수에 값을 대입한다.

 

- SessionResponse.java

@Getter
public class SessionResponse {

    private final String accessToken;

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

1. SessionResponse 클래스는 controller에서 반환 타입으로 객체를 사용하여 JSON 형태로 데이터를 전달하기 위해 사용한다.

2. accessToken을 파라미터로 받는 생성자를 통해 객체 생성 시 전달 받은 accessToken에 값을 대입한다.

 


- Test 코드를 통한 요청 확인
@Test
    @Transactional
    @DisplayName("로그인 성공 후 세션 응답")
    void test3() throws Exception {

        //given
        Member member = memberRepository.save(Member.builder()
                .name("백인수")
                .email("saymay10@naver.com")
                .password("1234")
                .build());

        Login login = Login.builder()
                .email("saymay10@naver.com")
                .password("1234")
                .build();

        String json = objectMapper.writeValueAsString(login);

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

        Assertions.assertEquals(1L, member.getSessions().size());
    }

1. Member(사용자)를 DB에 저장하고, 로그인을 시도하는 email, password 값을 가진 Login 객체를 생성한다.

2. objectMapper.writeValueAsString를 통해 객체를 JSON 형태의 문자열로 변경한다.

3. /auth/login 경로로 body에 Login 객체를 JSON 형태로 만든 문자열을 전달한다.(참고로 데이터가 전달될 때는 객체 형태로 전달되는 것이 아닌 문자열로 전달된다.)

4. /auth/login으로 post 요청이 가게 되면 authService 클래스의 signin메서드가 실행되고, DB에서 전달 받은 JSON 데이터가 DB에 있는지 확인 한 후 Member 클래스의 addSession() 메서드를 통해 새로운 세션 객체를 생성한다.

5. addSession() 메서드에 의해 세션 객체가 생성될 때, 생성자 구현체에 의해 UUID를 통해 임의의 accessToken 코드가 생성된다.

6. Member과 Session은 OneToMany 관계이기 때문에 session 객체를 생성하게 되면 Member 클래스의 Session 객체를 담는 리스트에 Session 객체를 추가한다.

7. 최종적으로 session의 accessToken 값을 응답 데이터로 전달한다.

728x90