70일차 (1) - Spring, javaScript (쿠키와 세션, 로그인, 인터셉터)

Yohan·2024년 6월 2일
0

코딩기록

목록 보기
107/156
post-custom-banner

쿠키와 세션

쿠키

  • 클라이언트가 기억

세션

  • 서버가 기억
    -> 로그인 검증시 session을 이용해서 로그인하면 새로고침을 해도 정보(여기서는 요한)가 사라지지 않음

세션을 통해 로그인 검증 -> 로그아웃까지!


세션을 통한 로그인 양식 열기 + 로그인 요청 처리

MemberController

로그인 양식 열기

  • 사용자가 로그인 페이지를 요청하면 -> 로그인 후에 가고 싶은 페이지를 기억 -> 로그인 페이지로 보내주는 역할
    // 로그인 양식 열기
    // @RequestParam(required = false) String redirect
    // -> 사용자가 로그인한 후에 가고 싶어하는 페이지 주소 (이 요청이 필수는 아니야!)
    @GetMapping("/sign-in")
    public String signIn(HttpSession session
                        , @RequestParam(required = false) String redirect)
    {

        // redirect를 session에 저장 (로그인 후에 어디 갈지 기억함)
        session.setAttribute("redirect", redirect);

        log.info("/members/sign-in GET : forwarding to sign-in.jsp");
        return "members/sign-in";
    }

// 로그인 요청 처리
    @PostMapping("/sign-in")
    public String signIn(LoginDto dto,
                         RedirectAttributes ra,
                         HttpServletRequest request) {
        log.info("/members/sign-in POST"); // 로그에 "로그인 요청이 들어왔어요"라고 기록
        // LoginDto에 @Setter 없으면 에러! (로그의 중요성) 꼭 찍어보자
        log.debug("parameter: {}", dto); // 사용자가 입력한 로그인 정보를 로그에 기록

        // 세션 얻기
        HttpSession session = request.getSession();

        LoginResult result = memberService.authenticate(dto, session);

        // 로그인 검증 결과를 수송객체를 통해 JSP에게 보내기
        // Redirect시에  Redirect된 페이지에 데이터를 보낼 떄는
        // Model객체를 사용할 수 없음
        // 왜냐면 Model 객체는 request 객체를 사용하는데 해당 객체는
        // 한 번의 요청이 끝나면 메모리에서 제거된다. 그러나 redirect는
        // 요청이 2번 발생하므로 다른 request객체를 jsp가 사용하게 됨
//        model.addAttribute("result", result); // (X)
        ra.addFlashAttribute("result", result);

        if(result == LoginResult.SUCCESS) {

            // 혹시 세션에 저장된 리다이렉트 URL이 있다면
            String redirect = (String) session.getAttribute("redirect");
            if (redirect != null) {
                session.removeAttribute("redirect");
                return "redirect:" + redirect;
            }

            return "redirect:/index"; // 로그인 성공시
        }

        return "redirect:/members/sign-in"; // 로그인 실패시 다시 제자리
    }

    @GetMapping("/sign-out")
    public String signOut(HttpSession session) {
        // 세션구하기
        // 스프링에서 HttpSession 파라미터로 하면 request에서 session 구해줌
//        HttpSession session = request.getSession();

        // 세션에서 로그인 기록 삭제
        session.removeAttribute("login");
        // 세션을 초기화
        session.invalidate();
        // 홈으로 보내기
        return "redirect:/";
    }

MemberService

  • 로그인에 성공했을 시 로그인 정보를 세션에 저장
    -> 로그인 유지!
    // 로그인 검증 처리
    public LoginResult authenticate(LoginDto dto, HttpSession session) {

        // 회원의 아이디를 통해 회원가입 여부 확인 (findOne)
        String account = dto.getAccount();
        Member foundMember = memberMapper.findOne(account);

        // 회원가입이 안되었을 때
        if (foundMember == null) {
            log.info("{} = 회원가입이 필요합니다.", account);
            return NO_ACC;
        }

        // 비밀번호 일치 검사
        String inputPassword = dto.getPassword(); // 클라이언트에 입력한 비밀번호
        String originPassword = foundMember.getPassword(); // DB에 저장된 비밀번호

        // PasswordEncoder에서는 암호화된 비번을 내부적으로 비교해주는 기능을 제공
        // 확인해주는 기능만 제공
        if (!encoder.matches(inputPassword, originPassword)) {
            log.info("비밀번호가 일치하지 않습니다.");
            return NO_PW;
        }

        log.info("{}님 로그인 성공", foundMember.getName());

        // 세션의 수명 : 설정된 시간 OR 브라우저를 닫기 전까지
        int maxInactiveInterval = session.getMaxInactiveInterval();
        session.setMaxInactiveInterval(60 * 60); // 세션 수명 1시간 설정
        log.debug("sessiontime: {}", maxInactiveInterval);

        // 로그인에 성공했을 때 클라이언트에 보낼 정보를 LoginUserInfoDto 객체에 담아서
        // 로그인한 사용자의 정보를 세션에 저장! (비밀번호는 제외)
        session.setAttribute(LOGIN, new LoginUserInfoDto(foundMember));

        return SUCCESS;
    }

LoginUserInfoDto

  • 로그인시 클라이언트에 보낼 정보를 담은 Dto
    -> 보안상 비밀번호는 제외하는 것이 좋다.
public class LoginUserInfoDto {

    // 클라이언트에 보낼 정보
    private String account;
    private String nickname;
    private String email;
    private String auth;

    public LoginUserInfoDto(Member member) {
        this.account = member.getAccount();
        this.email = member.getEmail();
        this.nickname = member.getName();
        this.auth = member.getAuth().name();
    }
}

header.jsp

  • 로그인 했을 때 Welcome 옆에 이름이 뜨게 ${login.nickname} 추가
  • 헤더에 로그인 했을 때와 로그인 하지 않았을 때 화면에 뜨는 것이 달라지게 구분

인터셉터

  • 요청(request)과 응답(response) 사이에 특정한 처리를 수행하기 위해 사용되는 컴포넌트

인터셉터의 3가지 기능

  1. preHandle: 컨트롤러에 요청이 전달되기 전에 실행됩니다. 주로 인증 및 권한 검사와 같은 로직을 처리하는데 사용되며, 반환값이 true이면 요청이 계속 진행되고, false이면 요청이 중단됩니다.
  2. postHandle: 컨트롤러가 요청을 처리한 후, 뷰(View)를 렌더링하기 전에 실행됩니다. 반환값이 없으며, 주로 응답 데이터를 가공하거나 추가 로직을 수행하는데 사용됩니다.
  3. afterCompletion: 요청 처리가 완전히 끝난 후에 실행됩니다. 주로 리소스를 해제하거나, 로깅 등의 후처리 작업을 수행하는데 사용됩니다.

게시판에 적용사항

1. 로그인한 회원은 회원가입페이지와 로그인페이지에 접근을 차단하기 위해 인터셉터 이용

2. 로그인하지 않으면 글쓰기, 글수정, 글삭제 불가능


InterceptorConfig

  • 인터셉터들을 등록하는 설정 객체
// 만들어 놓은 인터셉터들을 스프링 컨텍스트에 등록하는 설정 파일
@Configuration // 설정파일을 의미
@RequiredArgsConstructor
public class InterceptorConfig implements WebMvcConfigurer {

    private final AfterLoginInterceptor afterLoginInterceptor;
    private final BoardInterceptor boardInterceptor;

    // 설정 메서드
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 로그인 인터셉터 등록
        registry
                .addInterceptor(afterLoginInterceptor)
                // 해당 인터셉터가 동작할 URL을 설정
                .addPathPatterns("/members/sign-up", "/members/sign-in")
                ;
        // 게시판 인터셉터 등록
        registry
                .addInterceptor(boardInterceptor)
                .addPathPatterns("/board/*")
                .excludePathPatterns("/board/list", "/board/detail")
                ;
    }

AfterLoginInterceptor

  • 로그인했을 때 회원가입, 로그인 페이지는 접근을 차단하게함
// 로그인한 회원은 회원가입페이지와 로그인페이지에 접근을 차단
@Configuration // 설정 파일
@Slf4j
public class AfterLoginInterceptor implements HandlerInterceptor {

    // 클라이언트의 요청이 컨트롤러에 들어가기 전에 해야할 일을 명시
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.debug("after login interceptor execute!");
        // 로그인이 되었다면 홈으로 redirect
        // InterceptorConfig에 설정된 부분은 진입 차단 (false 이므로)
        if (LoginUtil.isLoggedIn(request.getSession())) {
            response.sendRedirect("/");
        return false; // true일 경우 컨트롤러 진입 허용, false 진입 차단
        }
        return true;
    }
}

BoardInterceptor

  • 로그인하지 않았을 때 글쓰기, 글수정, 글삭제 요청 거부
@Configuration
@Slf4j
public class BoardInterceptor implements HandlerInterceptor {

    // preHandle을 구현하여
    // 로그인을 안한 회원은 글쓰기, 글수정, 글삭제 요청을 거부할 것!
    // 거부하고 로그인 페이지로 리다이렉션할 것!

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        // 로그인 안한 회원
        if(!LoginUtil.isLoggedIn(request.getSession())) {
            String redirectURI = request.getRequestURI();
            response.sendRedirect("/members/sign-in?message=login-required&redirect=" + redirectURI);
            return false;
        }
        return true;
    }
}

sign-in.jsp

  • BoardInterceptor에서 redirect보낸 url에서 message가 login-required면 alert를 보내게함

글 작성시 회원의 계정명이 뜨게함

1. Board에 계정명이 필요하므로 일단 DB에 account 추가하고 account를 admin으로 설정


  • board와 member를 join해서 로그인한 계정의 name이 글 작성자로 뜨게함

2. DB를 수정했으니 controller, service, board, xml도 수정

BoardMapper.xml

Board에 account 추가

BoardController

-> 로그인했을 때의 계정명을 불러와야 하므로 게시글 등록 요청할 때 session 추가

BoardService

  • 저장 중간 로직을 담당, 로그인된 계정명을 가져와야하므로 LoginUtil에서 로그인된 계정을 가져옴

왜 DTO -> Entity 과정을 거쳐야할까?

-> 여기서 DTO -> Entity의 과정을 거친다.
BoardWriteRequestDto에는 내가 게시글을 입력할 때 필요한 title, content, writer가 저장된다. 하지만 게시글을 저장할 때, 누가 이 게시글을 작성했는지를 알기 위해 사용자 계정 정보도 필요한데 이 정보는 세션에 저장되어있기 때문에 Entity로 바꿔서 DB와 연결되어서 로그인된 계정을 가지고와서 save한다.

LoginUtil

  • 로그인된 계정 가져오기

관리자이거나 본인이 쓴글에만 X버튼이 렌더링

계정명을 가져와서 계정이 관리자 or 본인일 경우만 X버튼 이 나오게 설정

  • DB에서 B.account를 추가하여 계정을 가져오게함
  • DB를 변경했으므로 request, reponseDTO에도 account를 추가

  • xml의 findAll 메서드에 B.account 추가
  • login유저의 auth가 admin or 본인일 때 X버튼 렌더링
profile
백엔드 개발자
post-custom-banner

0개의 댓글