StudyRepository
article thumbnail
Published 2023. 6. 27. 15:10
로그인 처리 (쿠키) Spring
728x90

 

 

선수지식

webdomain을 알고있지만 domainweb을 모르도록 설계해야 한다.

이것을 webdomain을 의존하지만, domainweb을 의존하지 않는다고 표현한다.

예를 들어 web 패키지를 모두 삭제해도 domain에는 전혀 영향이 없도록 의존관계를 설계하는 것이 중요하다.

반대로 이야기하면 domainweb을 참조하면 안된다.

 

 

 

 

우선 html파일로 홈 화면을 구현해보자.

public class HomeController {

    @GetMapping("/")
    public String home() {

        return "home";
    }
}

 

home.html

 

 

1.Member

package hello.login.domain.member;

import lombok.Data;

import javax.validation.constraints.NotEmpty;

@Data
public class Member {

    private Long id;

    @NotEmpty
    private String loginId; //로그인 ID
    @NotEmpty
    private String name; //사용자 이름
    @NotEmpty
    private String password;
}

 

 

2.MemberRepository

package hello.login.domain.member;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;

import java.util.*;

@Slf4j
@Repository
public class MemberRepository {
    private static Map<Long, Member> store = new HashMap<>();
    private static long sequence = 0L;

    public Member save(Member member) {
        member.setId(++sequence);
        log.info("save: member={}", member);
        store.put(member.getId(), member);
        return member;
    }

    public Member findById(Long id) {
        return store.get(id);
    }

    public Optional<Member> findByLoginId(String loginId) {
        return findAll().stream()
                .filter(m -> m.getLoginId().equals(loginId))
                .findFirst();
    }

    public List<Member> findAll(){
        return new ArrayList<>(store.values());
    }

    public void clearStore(){
        store.clear();
    }
}

 

람다식을 사용하여 findAll().stream()를 통해 모든 멤버를 스트림으로 가져온 후, filter(m -> m.getLoginId().equals(loginId))를 사용하여 입력된 loginId와 일치하는 loginId를 가진 멤버를 필터링한다.

 

findFirst()를 통해 필터링된 결과 중 첫 번째 요소를 찾는다. 만약 일치하는 멤버가 없다면, findFirst()는 Optional.empty를 반환한다.

이것은 Optional<Member> 타입을 반환하기 때문에 사용자는 반환값이 null인지 아닌지에 대해 안전하게 확인할 수 있다.



3.LoginService

public Member login(String loginId, String password) {
    return memberRepository.findByLoginId(loginId)
            .filter(m -> m.getPassword().equals(password))
            .orElse(null);
}

로그인의 핵심 비즈니스 로직은 회원을 조회한 다음에 파라미터로 넘어온 password와 비교해서 같으면 회원을 반환하고, 만약 password가 다르면 null 을 반환한다.

 

 

4.LoginControleller

@PostMapping("/login")
public String login(@Valid @ModelAttribute LoginForm form, BindingResult bindingResult) {
    if (bindingResult.hasErrors()) {
        return "login/loginForm";
    }

    Member loginMember = loginService.login(form.getLoginId(), form.getPassword());

    if (loginMember == null) {
        bindingResult.reject("loginFail", "아이디 또는 비밀번호가 맞지 않습니다");
        return "login/loginForm";
    }

로그인 컨트롤러는 로그인 서비스를 호출해서 로그인에 성공하면 홈 화면으로 이동하고, 로그인에 실패하면 bindingResult.reject() 를 사용해서 글로벌 오류( ObjectError )를 생성한다. 그리고 정보를 다시 입력하도록 로그인 폼을 뷰 템플릿으로 사용한다.

 

 

 

실행해보면 로그인이 성공하면 홈으로 이동하고, 로그인에 실패하면 "아이디 또는 비밀번호가 맞지 않습니다."라는 경고와 함께 로그인 폼이 나타난다.

그런데 아직 로그인이 되면 홈 화면에 고객 이름이 보여야 한다는 요구사항을 만족하지 못한다. 로그인의 상태를 유지하면서, 로그인에 성공한 사용자는 홈 화면에 접근시 고객의 이름을 보여주려면 어떻게 해야할까?

 

 

쿠키

로그인의 상태를 어떻게 유지할 수 있을까?
HTTP
강의에서 일부 설명했지만, 쿼리 파라미터를 계속 유지하면서 보내는 것은 매우 어렵고 번거로운 작업이다. 쿠키를 사용해보자.

서버에서 로그인에 성공하면 HTTP 응답에 쿠키를 담아서 브라우저에 전달하자. 그러면 브라우저는 앞으로 해당 쿠키를 지속해서 보내준다.

 

1.

 

2.클라이언트 쿠키 전달 

 

 

쿠키에는 영속 쿠키와 세션 쿠키가 있다.


1.영속 쿠키: 만료 날짜를 입력하면 해당 날짜까지 유지

2.세션 쿠키: 만료 날짜를 생략하면 브라우저 종료시 까지만 유지

 

브라우저 종료시 로그아웃이 되길 기대하므로, 우리에게 필요한 것은 세션 쿠키이다.

 

 

 

쿠키 생성 로직 

 Cookie idCookie = new Cookie("memberId", String.valueOf(loginMember.getId()));
  response.addCookie(idCookie);

 

로그인에 성공하면 쿠키를 생성하고 HttpServletResponse 에 담는다.

쿠키 이름은 memberId 이고, 값은 회원의 id를담아둔다.웹브라우저는종료전까지회원의 id를서버에계속보내줄것이다.

 

 

크롬 브라우저를 통해 HTTP 응답 헤더에 쿠키가 추가된 것을 확인할 수 있다.

 


이제 로그인에 성공하면 로그인 한 사용자 전용 홈 화면을 보여주자.

 

@GetMapping("/")
public String homeLogin(@CookieValue(name = "memberId", required = false) Long memberId, Model model){
    if (memberId == null) {
        return "home";
    }

    //로그인
    Member loginMember = memberRepository.findById(memberId);
    if (loginMember == null) {
        return "home";
    }

    model.addAttribute("member", loginMember);
    return "loginHome";
}


@CookieValue
를 사용하면 편리하게 쿠키를 조회할 수 있다.

로그인 하지 않은 사용자도 홈에 접근할 수 있기 때문에 required = false 를 사용한다.

 

 

로그인 쿠키( memberId )가 없는 사용자는 기존 home 으로 보낸다. 추가로 로그인 쿠키가 있어도 회원이 없으면 home 으로 보낸다.

로그인 쿠키( memberId )가 있는 사용자는 로그인 사용자 전용 홈 화면인 loginHome 으로 보낸다.

추가로 홈 화면에 화원 관련 정보도 출력해야 해서 member 데이터도 모델에 담아서 전달한다.

 

 

마지막으로 로그아웃(쿠키 제거) 코드를 구현해보자

@PostMapping("/logout")
public String logout(HttpServletResponse response) {
    expireCookie(response, "memberId");
    return "redirect:/";
}

private static void expireCookie(HttpServletResponse response, String cookieName) {
    Cookie cookie = new Cookie(cookieName, null);
    cookie.setMaxAge(0);
    response.addCookie(cookie);
}

 

세션 쿠키이므로 웹 브라우저 종료시 서버에서 해당 쿠키의 종료 날짜를 0으로 지정한다.

로그아웃도 응답 쿠키를 생성하는데 Max-Age=0 를 확인할 수 있다. 해당 쿠키는 즉시 종료된다.

 

 

728x90

'Spring' 카테고리의 다른 글

로그인 처리 (세션)  (0) 2023.06.27
쿠키와 보안문제  (0) 2023.06.27
@ModelAttribute, @RequestBody  (0) 2023.06.26
메시지, 국제화  (1) 2023.06.16
주요 annotation  (0) 2023.06.07
profile

StudyRepository

@Minseo26262

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!