[프로젝트1] 6. HttpSession을 이용한 유저 관련 로직 구현하기

rin·2020년 5월 4일
1
post-thumbnail

목표
1. 회원 관리를 위해 User 도메인을 새롭게 생성한다.
2. UserEntity와 BoardEntity 사이에 관계를 정립한다.
3. 회원 가입, 로그인, 로그아웃을 구현한다.
4. 회원 정보를 이용하여 글을 작성, 수정, 삭제하도록 로직을 변경한다.

1. User 도메인 생성

BaseEntity 분리

어떤 Entity에도 상관없이 필수적으로 들어가야하는 컬럼들이 있다. 이를테면 고유 아이디, 데이터 생성 일시, 데이터 수정 일시 등이다.
따라서 이런 중복 컬럼들은 BaseEntity로 분리하도록 하겠다.

@Getter
@MappedSuperclass
public abstract class BaseEntity {

    @Id
    @Setter
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    protected long id;

    @CreationTimestamp
    @Column
    protected LocalDateTime createdAt;

    @UpdateTimestamp
    @Column
    protected LocalDateTime updatedAt;

}

@MappedSuperclass 어노테이션으로 지정된 클래스는 해당 수퍼 클래스 자체에 대한 테이블이 없으므로 모든 맵핑이 서브 클래스에만 적용된다.

UserEntity와 BoardEntity 모두 BaseEntity를 상속받도록 한다.

UserEntity 생성

@Entity
@Getter
@Table(name = "user")
@NoArgsConstructor
public class UserEntity extends BaseEntity {

    @Column
    private String accountId;

    @Column
    private String password;

    @Builder
    public UserEntity(String accountId, String password){
        this.accountId = accountId;
        this.password = password;
    }

}

BoardEntity와 UserEntity 사이의 관계
BoardEntity : UserEntity = N : 1 의 관계를 가지며 이는 BoardEntity에서 @ManyToOne을 이용해 정립 할 수 있다.

BoardEntity를 다음과 같이 고쳐준다.

@Entity
@Getter
@Table(name = "board")
@NoArgsConstructor
@DynamicUpdate
public class BoardEntity extends BaseEntity {

    @ManyToOne(optional = false)
    @JoinColumn(name = "writerId", nullable = false)
    private UserEntity writer;

    @Setter
    @Column
    private String contents;

    @Column
    private String title;

    @Builder
    public BoardEntity(UserEntity writer, String contents, String title){
        this.writer = writer;
        this.contents = contents;
        this.title = title;
    }

    public BoardEntity update(BoardEntity newBoard){
        this.writer = newBoard.getWriter();
        this.contents = newBoard.getContents();
        this.title = newBoard.getTitle();
        return this;
    }
}

JPA의 모든 EntityManager는 Primary Key를 기준으로 작동하며 위의 private UserEntity writer는 BoardEntity와 UserEntity가 UserEntity의 PK로 관리된다. 즉, BoardEntity를 생성할 때 UserEntity의 많은 컬럼 중 PK인 id 컬럼만 null이 아니라면 Entity를 생성하는 것에 문제가 되지 않는다는 것이다.

이렇게 두 개의 Entity를 수정한 뒤 프로젝트를 실행하려고 하면 전부 터질 것이다 ^^;;...

2. Session을 이용해 로그인 유저 관리하기

SpringSecurity를 사용하지는 않을 것이고, 대신 HttpSession을 이용하여 로그인한 유저의 정보를 관리할 것이다.
즉, 사용자가 로그인을 하면 서버에서 해당 정보를 모두 가지고 있고 특정 작업을 수행하고자하면 HttpSession을 이용하여 User 인스턴스를 받아오도록 할 것이다.


HttpSession

ref. interface HttpSession

한 페이지 이상에서의 요청이나 하나의 사이트에 접근하는 유저를 식별하거나 유저 정보를 보관하는 방법을 제공한다.

서블릿 컨테이너는 HTTP clientHTTP server 사이의 세션을 만들 때 해당 인터페이스를 사용한다. 유저로부터의 하나 이상의 연결이나 페이지 요청 사이에서 특정 시간동안은 세션은 영속성을 가진다. 세션은 보통 한 명의 사용자에 해당하며, 한 사용자는 여러 번 사이트를 방문할 수 있다. 서버는 cookies를 사용하거나 URL을 재작성하는 등의 방법으로 세션을 유지, 관리한다.

이 인터페이스를 통해 서블릿은

  • 세션 식별자, 생성 시간 및 마지막으로 액세스한 시간과 같은 세션에 대한 정보를 보거나 조작한다.
  • 개체를 세션에 바인딩하여 사용자 정보가 여러 사용자 연결 간에 유지되도록 한다.

어플리케이션이 오브젝트를 세션에 저장하거나 세션에서 제거할 때, 세션은 오브젝트가 HttpSessionBindingListener를 구현하는지 확인한다. 이 경우, 서블릿은 세션에 바인딩되었거나 세션에서 바인딩되지 않았음을 객체에 통지한다. 알림은 바인딩 메소드가 수행된 뒤에 통지된다. 무효하거나 만료된 세션의 경우, 세션이 무효하거나 만료된 후에 통지가 보내진다.

컨테이너가 분산 컨테이너 설정에서 VM 간에 세션을 마이그레이션할 때 HttpSessionActivationListener 인터페이스를 구현하는 모든 세션 속성이 통지된다.

서블릿은 쿠키가 의도적으로 꺼진 경우와 같이 클라이언트가 세션에 참여하지 않는 경우를 처리할 수 있어야 한다. 클라이언트가 세션에 참여할 때까지 isNewtrue를 반환한다. 만약 클라이언트가 세션에 참여하지 않는다면, getSession은 각각의 요청에 대해 다른 세션을 반환하고 isNew는 항상 true를 반환한다.

세견 정보는 오로지 현재의 웹 어플리케이션(ServletContext) 영역에만 유효하다. 따라서 하나의 컨텍스트에 저장된 정보는 다른 곳에서 즉시 찾는 것이 불가하다.


✏️Note
Spring은 서블릿 컨테이너가 만든 HttpSession을 주입하지만, HttpSession을 생성하는 주체는 서블릿 컨테이너 자체이다.

HttpSession을 이용해 처음 로그인하면 정보를 저장해 두었다가 필요할 때 마다 꺼내서 사용하도록 할 것이다.

UserRepositoryIntegrationTest

test/java/com.freeboard01/domain 패키지 하위에 user 패키지를 생성하고 UserRepositoryIntegrationTest 테스트 클래스를 만들어 주었다.
우선 아래의 테스트를 수행해 사용자 데이터를 추가하도록 하겠다.

@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/applicationContext.xml"})
@Transactional
@Rollback(value = false)
public class UserRepositoryIntegrationTest {

    @Autowired
    private UserRepository userRepository;

    @Test
    @DisplayName("유저를 추가한다.")
    public void test1(){
        UserEntity user = UserEntity.builder().accountId("yerin").password("pass123").build();
        userRepository.save(user);
    }

}

board Table에도 writerId 컬럼이 추가되었을텐데 user, password 컬럼이 사라지지 않았을 것이다. 이는 수동으로 drop해주도록 한다.

또한 다음의 쿼리를 날려 강제로 board 데이터에 작성자 정보를 추가하도록 하자.

update board
set writerId = 1
where 1=1;

Board 관련 로직 변경하기

BoardEntity의 컬럼이 변경됨에 따라 RestController와 Service도 변경이 필요해졌다.

BoardDto, BoardForm

우선, BoardDto와 BoardForm을 변경해주자.

/*BoardDto*/
@NoArgsConstructor
@Getter
public class BoardDto{

    private long id;
    private UserDto writer;
    private String contents;
    private String title;

    public BoardDto(BoardEntity board) {
        this.writer = UserDto.of(board.getWriter());
        this.id = board.getId();
        this.contents = board.getContents();
        this.title = board.getTitle();
    }

    public static BoardDto of(BoardEntity board) {
        return new BoardDto(board);
    }
}

/*****/
/*BoardForm*/
@Getter
@NoArgsConstructor
public class BoardForm {

    private String password;
    private String contents;
    private String title;

    @Builder
    public BoardForm(String password, String contents, String title){
        this.password = password;
        this.contents = contents;
        this.title = title;
    }

    public BoardEntity convertBoardEntity(UserEntity user){
        return BoardEntity.builder()
                .writer(user)
                .contents(this.contents)
                .title(this.title)
                .build();
    }
}

BoardRepository

@Repository
public interface BoardRepository extends JpaRepository<BoardEntity, Long> {
    List<BoardEntity> findAllByWriterId(long writerId);
}

BoardApiController

BoardApiController를 수정하기전에 User 도메인 관련 클래스들을 우선적으로 추가해주었다.

/*UserDto*/
@Getter
@NoArgsConstructor
public class UserDto {

    private String accountId;

    public UserDto(UserEntity userEntity){
        this.accountId = userEntity.getAccountId();
    }

    public static UserDto of(UserEntity userEntity) {
        return new UserDto(userEntity);
    }
}

/***********/
/*UserForm*/
@Getter
@NoArgsConstructor
public class UserForm {

    private String accountId;
    private String password;

    @Builder
    public UserForm(String accountId, String password){
        this.accountId = accountId;
        this.password = password;
    }

    public UserEntity convertUserEntity(){
        return UserEntity.builder()
                .accountId(this.accountId)
                .password(this.password)
                .build();
    }

}

User 정보 중 노출될 필요가 있는 정보는 현재로썬 아이디 뿐이므로 해당 멤버 변수만 추가하였다.

아래가 BoardApiController 코드이다.

@RestController
@RequestMapping("/api/boards")
@RequiredArgsConstructor
public class BoardApiController {

    private final HttpSession httpSession;
    private final BoardService boardService;

    @GetMapping
    public ResponseEntity<PageDto<BoardDto>> get(@PageableDefault(page = 1, size = 10, sort = "createdAt", direction = Sort.Direction.DESC )Pageable pageable){
        Page<BoardEntity> pageBoardList = boardService.get(pageable);
        List<BoardDto> boardDtoList = pageBoardList.stream().map(boardEntity -> BoardDto.of(boardEntity)).collect(Collectors.toList());
        return ResponseEntity.ok(PageDto.of(pageBoardList, boardDtoList));
    }

    @PostMapping
    public ResponseEntity<BoardDto> post(@RequestBody BoardForm form){
        if(httpSession.getAttribute("USER") == null){
            new Exception();
        }
        BoardEntity savedEntity = boardService.post(form, (UserForm) httpSession.getAttribute("USER"));
        return ResponseEntity.ok(BoardDto.of(savedEntity));
    }

    @PutMapping("/{id}")
    public ResponseEntity<Boolean> update(@RequestBody BoardForm form, @PathVariable long id){
        if(httpSession.getAttribute("USER") == null){
            new Exception();
        }
        return ResponseEntity.ok(boardService.update(form, (UserForm) httpSession.getAttribute("USER"), id));
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Boolean> delete(@PathVariable long id){
        if(httpSession.getAttribute("USER") == null){
            new Exception();
        }
        return ResponseEntity.ok(boardService.delete(id, (UserForm) httpSession.getAttribute("USER")));
    }
}

자동주입으로 HttpSession을 받고 있다. 추후 Login을 수행하는 Service에서 로그인한 사용자의 데이터를 key=USER, value=UserForm 클래스로 (JSON 변환) 추가해 줄것이다.
Session 정보가 없는 경우에 Exception 처리를 하고 있다. Exception과 관련한 내용도 추가할 것이므로 여기서는 최대한 간단하게 처리하였다.

서비스의 각 메소드는 BoardForm과 UserForm(session에서 가져온 값)을 받는 것으로 전부 변경하였다.

BoardService

@Service
@Transactional
public class BoardService {

    private BoardRepository boardRepository;
    private UserRepository userRepository;

    @Autowired
    public BoardService(BoardRepository boardRepository, UserRepository userRepository){
        this.boardRepository = boardRepository;
        this.userRepository = userRepository;
    }

    public Page<BoardEntity> get(Pageable pageable) {
        return boardRepository.findAll(PageUtil.convertToZeroBasePageWithSort(pageable));
    }

    public BoardEntity post(BoardForm boardForm, UserForm userForm){
        UserEntity user = userRepository.findByAccountId(userForm.getAccountId());
        if(user == null){
            new Exception();
        }
        return boardRepository.save(boardForm.convertBoardEntity(user));
    }

    public Boolean update(BoardForm boardForm, UserForm userForm, long id) {
        UserEntity user = userRepository.findByAccountId(userForm.getAccountId());
        if(user == null){
            new Exception();
        }
        BoardEntity prevEntity = boardRepository.findById(id).get();
        if(prevEntity.getWriter().equals(user)){
            prevEntity.update(boardForm.convertBoardEntity(user));
            return true;
        }
        return false;
    }

    public boolean delete(long id, UserForm userForm) {
        UserEntity user = userRepository.findByAccountId(userForm.getAccountId());
        if(user == null){
            new Exception();
        }
        BoardEntity entity = boardRepository.findById(id).get();
        if(entity.getWriter().equals(user)){
            boardRepository.deleteById(id);
            return true;
        }
        return false;
    }
}

세션에 저장된 아이디로 등록된 유저 정보를 찾지 못하면 예외처리한다.
이전에 새로 받아온 password와 등록된 글이 가지고 있던 password가 동일한지 판단하는 부분을 User 객체가 동일한지 판단하는 로직으로 변경하였다.

Test Code

이전에 작성했던 테스트 코드 중 의미가 없어진 것은 제거하였다. 현재 테스트 코드는 다음과 같이 존재한다.

전체 테스트 코드는 여기에서 확인하도록 한다.

테스트의 일부만 가져와 보았다.

@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/applicationContext.xml", "file:src/main/webapp/WEB-INF/dispatcher-servlet.xml"})
@Transactional
@WebAppConfiguration
@Rollback(value = false)
public class BoardApiControllerTest {

    @Autowired
    private WebApplicationContext webApplicationContext;

    @Autowired
    private BoardRepository boardRepository;

    @Autowired
    private UserRepository userRepository;

    private MockMvc mvc;

    @Autowired
    private MockHttpSession mockHttpSession;

    private UserEntity testUser;
    private BoardEntity testBoard;


    @BeforeEach
    public void initMvc() {
        testUser = userRepository.findAll().get(0);
        UserForm userForm = UserForm.builder().accountId(testUser.getAccountId()).password(testUser.getPassword()).build();
        mockHttpSession.setAttribute("USER", userForm);

        testBoard = boardRepository.findAllByWriterId(testUser.getId()).get(0);

        mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }

    // ( ... 생략 )

    @Test
    @DisplayName("올바른 패스워드를 입력한 경우 데이터 수정이 가능하다.")
    public void updateTest() throws Exception {
        BoardForm updateForm = BoardForm.builder().title("제목을 입력하세요").contents("수정된 데이터입니다 ^^*").build();
        ObjectMapper objectMapper = new ObjectMapper();

        mvc.perform(put("/api/boards/"+testBoard.getId())
                .session(mockHttpSession)
                .contentType(MediaType.APPLICATION_JSON_VALUE)
                .content(objectMapper.writeValueAsString(updateForm)))
                .andExpect(status().isOk())
                .andExpect(content().string("true"));
    }
    
    // ( ... 생략 )
}

MockHttpSession 객체를 추가하였는데, MockMvc 테스트를 수행할 때 가짜 세션을 사용할 수 있도록 해준다.
이를 initMvc() 메소드에서 초기화해주고 있으며, 실제 구현된 RestApi와 같이 (key, value) = ("User", UserForm.Class)를 추가해주었다.

아래 updateTest 메소드에서 볼 수 있듯이 put( /*url*/ ).session(MockHttpSession.Class)로 세션을 추가할 수 있다.

session 메소드 대신 sessionAttr, sessionAttrs를 사용해 key-value 쌍으로 세션을 만들수도 있다.

3. 유저 관련 로직 추가하기

위에선 유저가 로그인 했다는 가정하에 일어날 서비스에 대해서 수정해주었다. 이어서 UserApiController와 UserService 등 유저관련 비즈니스 로직을 추가해보겠다.

UserService

@Service
@Transactional
public class UserService {

    private UserRepository userRepository;

    @Autowired
    public  UserService(UserRepository userRepository){
        this.userRepository = userRepository;
    }

    public Boolean join(UserForm user) {
        UserEntity userEntity = userRepository.findByAccountId(user.getAccountId());
        if(userEntity == null){
            userRepository.save(user.convertUserEntity());
            return true;
        }
        return false;
    }

    public Boolean login(UserForm user) {
        UserEntity userEntity = userRepository.findByAccountId(user.getAccountId());
        return userEntity != null && userEntity.getPassword().equals(user.getPassword());
    }

}

Boolean 타입을 리턴하여 가입과 로그인 시도 시 성공/실패를 확인하도록 하였다.

UserApiController

@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserApiController {

    private final HttpSession httpSession;
    private final UserService userService;

    @PostMapping
    private ResponseEntity<Boolean> join(@RequestBody UserForm user){
        return ResponseEntity.ok(userService.join(user));
    }

    @PostMapping(params = {"type=LOGIN"})
    private ResponseEntity<Boolean> login(@RequestBody UserForm user){
        if(userService.login(user) == true){
            httpSession.setAttribute("USER", user);
            return ResponseEntity.ok(true);
        }
        return ResponseEntity.ok(false);
    }
}

로그인하면

❗️ NOTE
여기서 삽질을 엄청했는데, httpSession에 attribute를 넣는 작업을 service에서 수행한 뒤 다른 컨트롤러에서 세션을 호출하면 해당하는 attribute는 존재하지 않았다.
Controller에서 setAttribute를 수행하면 문제없이 세션값을 불러낼 수 있었는데 원인을 모르겠음..🤔

HomeController

@Controller
public class HomeController {

    @GetMapping("/")
    public String home() {
        return "index";
    }

    @GetMapping("/board")
    public String board(HttpSession httpSession, Model model) {
        UserForm loginUser = (UserForm) httpSession.getAttribute("USER");
        if(loginUser != null ) {
            model.addAttribute("accountId", loginUser.getAccountId());
        }
        return "board";
    }

    @GetMapping("/join")
    public String join() { return "join"; }

    @GetMapping("/logout")
    public String logout(HttpSession httpSession) {
        httpSession.removeAttribute("USER");
        return "index";
    }

}

로그인 후 게시판으로 진입할 때 accountId라는 key값으로 사용자 아이디를 셋해준다.
로그아웃 시 세션에서 제거하고 메인 페이지로 이동한다.

UserApiControllerTest

package com.freeboard01.api.user;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.freeboard01.domain.user.UserEntity;
import com.freeboard01.domain.user.UserRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.WebApplicationContext;

import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/applicationContext.xml", "file:src/main/webapp/WEB-INF/dispatcher-servlet.xml"})
@Transactional
@WebAppConfiguration
@Rollback(value = false)
public class UserApiControllerTest {

    @Autowired
    private WebApplicationContext webApplicationContext;

    @Autowired
    private UserRepository userRepository;

    private MockMvc mvc;

    private ObjectMapper objectMapper;

    @BeforeEach
    public void initMvc() {
        mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
        objectMapper = new ObjectMapper();
    }

    private String randomId(){
        String id = "";
        for(int i = 0; i < 10; i++) {
            double dValue = Math.random();
            if(i%2 == 0) {
                id += (char) ((dValue * 26) + 65);   // 대문자
                continue;
            }
            id += (char)((dValue * 26) + 97); // 소문자
        }
        return id;
    }

    @Test
    @DisplayName("동일한 아이디를 가진 유저가 없으면 가입에 성공한다.")
    public void joinTest1() throws Exception {
        UserForm userForm = UserForm.builder().accountId(randomId()).password("password").build();
        mvc.perform(post("/api/users")
                .content(objectMapper.writeValueAsString(userForm))
                .contentType(MediaType.APPLICATION_JSON_VALUE))
                .andExpect(status().isOk())
                .andExpect(content().string("true"));
    }

    @Test
    @DisplayName("동일한 아이디를 가진 유저가 있으면 가입에 실패한다.")
    public void joinTest2() throws Exception {
        UserEntity userEntity = userRepository.findAll().get(0);
        UserForm userForm = UserForm.builder().accountId(userEntity.getAccountId()).password("password").build();
        mvc.perform(post("/api/users")
                .content(objectMapper.writeValueAsString(userForm))
                .contentType(MediaType.APPLICATION_JSON_VALUE))
                .andExpect(status().isOk())
                .andExpect(content().string("false"));
    }

    @Test
    @DisplayName("비밀번호를 올바르게 입력하면 로그인되고, 세션에 저장된다.")
    public void loginTest1() throws Exception {
        UserEntity userEntity = userRepository.findAll().get(0);
        UserForm userForm = UserForm.builder().accountId(userEntity.getAccountId()).password(userEntity.getPassword()).build();

        mvc.perform(post("/api/users?type=LOGIN")
                        .contentType(MediaType.APPLICATION_JSON_VALUE)
                        .content(objectMapper.writeValueAsString(userForm)))
                .andExpect(request().sessionAttribute("USER", notNullValue()))
                .andExpect(status().isOk())
                .andExpect(content().string("true"));
    }

    @Test
    @DisplayName("비밀번호를 올바르게 입력하지 않으면 로그인 거부된다.")
    public void loginTest2() throws Exception {
        UserEntity userEntity = userRepository.findAll().get(0);
        UserForm userForm = UserForm.builder().accountId(userEntity.getAccountId()).password(userEntity.getPassword()+"wrongPass").build();

        mvc.perform(post("/api/users?type=LOGIN")
                .contentType(MediaType.APPLICATION_JSON_VALUE)
                .content(objectMapper.writeValueAsString(userForm)))
                .andExpect(request().sessionAttribute("USER", nullValue()))
                .andExpect(status().isOk())
                .andExpect(content().string("false"));
    }
  
}

테스트를 위해 아이디를 랜덤으로 생성하는 randomId() 메소드를 추가해 사용하도록 하였다.
andExpect(request().sessionAttribute(${attributeName}, ${Matcher<Objec>})) 메소드를 이용해 세션을 검증하였다.
sessionAttribute의 두번째파라미터로 들어가는 매처의 제네릭 타입이 오브젝트로 명시된 것을 사용해야지만 런타임 익셉션이 발생하지 않는다.

HomeControllerTest

@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/applicationContext.xml", "file:src/main/webapp/WEB-INF/dispatcher-servlet.xml"})
@Transactional
@WebAppConfiguration
@Rollback(value = false)
public class HomeControllerTest {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private WebApplicationContext webApplicationContext;

    private MockMvc mvc;

    private ObjectMapper objectMapper;

    @BeforeEach
    public void initMvc() {
        mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
        objectMapper = new ObjectMapper();
    }

    @Test
    @DisplayName("로그아웃과 동시에 세션이 지워진다.")
    public void logoutTest1() throws Exception {
        UserEntity userEntity = userRepository.findAll().get(0);
        UserForm userForm = UserForm.builder().accountId(userEntity.getAccountId()).password(userEntity.getPassword()).build();
        MockHttpSession mockHttpSession = new MockHttpSession();
        mockHttpSession.setAttribute("USER", userForm);

        mvc.perform(get("/logout")
                .session(mockHttpSession))
                .andExpect(request().sessionAttribute("USER", nullValue()))
                .andExpect(status().isOk());
    }

}

4. front view 변경

join.hbs

WEB-INF/view 하위에 join.hbs를 추가한다.

{{#partial "header"}}
    <title>Main Page</title>
{{/partial}}

<!--body-->
{{#partial "contents"}}
    <h1>회원가입 페이지입니다.</h1>
    <form id="joinForm" style="width: 30% !important;">
        <div class="form-group">
            <label for="recipient-name" class="col-form-label">Id:</label>
            <input type="text" class="form-control" id="accountId" required>
        </div>
        <div class="form-group">
            <label for="recipient-name" class="col-form-label">Password:</label>
            <input type="password" class="form-control" id="accountPassword" required>
        </div>
    </form>
    <button onclick='location.href="/freeboard01/"' class='btn btn-primary'>메인으로 이동</button>
    <button onclick='join()' class='btn btn-primary'>가입하기</button>
{{/partial}}
<!--body-->

<!--js-->
{{#partial "js"}}
    <script>
        var join = () => {
            let account = new Object();
            var $joinForm = $('#joinForm');
            account.accountId = $joinForm.find('#accountId').val();
            account.password = $joinForm.find('#accountPassword').val();

            $.ajax({
                method : 'POST',
                url : 'api/users',
                contentType: 'application/json',
                data : JSON.stringify(account)
            }).done(function (response) {
                if(response == true) {
                    location.href="/freeboard01/";
                } else {
                    alert("동일한 아이디가 존재합니다.");
                }
            });
        }
    </script>
{{/partial}}
<!--js-->

{{> layout/layout}}

board.hbs

    <h1>이곳은 게시판입니다.</h1>
    {{#if accountId}}
    <div>로그인한 계정 : {{accountId}} <button onclick='location.href="logout"' class='btn btn-primary'>로그아웃</button></div>
    {{/if}}
    <div id="tableSpace"></div>
    <div id="pageMarkerSpace"></div>
    <div id="writeModalSpace"></div>
    {{#if accountId}}
        <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#boardModal">글쓰기</button>
    {{/if}}

partial contents 부분을 위와같이 변경한다.

index.hbs

{{#partial "header"}}
    <title>Main Page</title>
{{/partial}}

<!--body-->
{{#partial "contents"}}
    <h1>어서오세요. 로그인을 해주세요.</h1>
    <form id="loginForm" style="width: 30% !important;">
        <div class="form-group">
            <label for="recipient-name" class="col-form-label">Id:</label>
            <input type="text" class="form-control" id="accountId" required>
        </div>
        <div class="form-group">
            <label for="recipient-name" class="col-form-label">Password:</label>
            <input type="password" class="form-control" id="accountPassword" required>
        </div>
    </form>
    <button onclick='location.href="/freeboard01/board"' class='btn btn-primary'>게시판으로 이동하기</button>
    <button onclick='login()' class='btn btn-primary'>로그인하기</button>
    <button onclick='location.href="/join"'>가입하기</button>
{{/partial}}
<!--body-->

<!--js-->
{{#partial "js"}}
{{/partial}}
<script>
    var login = () => {
        let account = new Object();
        var $loginForm = $('#loginForm');
        account.accountId = $loginForm.find('#accountId').val();
        account.password = $loginForm.find('#accountPassword').val();

        $.ajax({
            method : 'POST',
            url : 'api/users?type=LOGIN',
            contentType: 'application/json',
            data : JSON.stringify(account)
        }).done(function (response) {
            if(response == true) {
                location.href="/freeboard01/board";
            } else {
                alert("로그인 정보가 바르지 않습니다.");
            }
        });
    }
</script>
<!--js-->

{{> layout/layout}}

톰캣을 띄워 회원가입, 로그인, 로그아웃을 수행해보자.
(✏️ PageDto를 변경하면서 목록에 글 작성자가 뜨지않는 것은 다음 게시글에서 변경하도록 하겠다.)

profile
🌱 😈💻 🌱

1개의 댓글

아주 좋아용 ㅎㅎ

답글 달기