로그인 처리1 - 쿠키, 세션

2025. 9. 17. 23:45·Spring

로그인 요구사항

1. 홈화면 (로그인 전)

- 회원가입

- 로그인

2. 홈화면 (로그인 후)

- 본인 이름 (xxx님 환영합니다)

- 상품 관리

- 로그아웃

3. 보안 요구사항

- 로그인 사용자만 상품이 접근, 관리 허용

- 로그인하지 않은 사용자가 상품관리에 접근하면 로그인 화면으로 이동

4. 회원가입, 상품관리

 

패키지 구조 설계

 

도메인이 가장 중요하다.

도메인 = 화면, UI, 기술, 인프라 등등의 영역은 제외한 시스템이 구현해야 하는 핵심 비즈니스 영역을 말함

 

향후 web을 다른 기술로 바꾸어도 도메인은 그대로 유지할 수 있어야한다.

web -> domain (O) / domain -> web (X)

웹은 도메인을 의존하되 도메인은 웹을 의존하면 안된다. 단방향 설계를 해야한다.

도메인이 웹을 의존하게되면 추후 코드 변경 발생 시 컴파일에러 등 문제가 생길 수 있음.

 

홈 화면

package hello.login.web;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Slf4j
@Controller
public class HomeController {

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

 

(HTML은 생략)

 

회원가입

package hello.login.domain.member;

import lombok.Data;

import javax.validation.constraints.NotEmpty;

@Data
public class Member {

    private Long id;

    @NotEmpty
    private String loginId;
    @NotEmpty
    private String name;
    @NotEmpty
    private String password;
}
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();
    }
}

 

findByLongId는 stream을 사용했다. stream은 자주 쓰는 기능이니 조금 자세히 공부해 둘 필요가 있다.

 

@Controller
@RequiredArgsConstructor
@RequestMapping("/members")
public class MemberController {

    private final MemberRepository memberRepository;

    @GetMapping("/add")
    public String addForm(@ModelAttribute("member") Member member) {
        return "members/addMemberForm";
    }

    @PostMapping("/add")
    public String save(@Validated @ModelAttribute Member member, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return "members/addMemberForm";
        }

        memberRepository.save(member);
        return "redirect:/";
    }
}

 

(HTML 생략)

 

로그인 기능 개발

package hello.login.domain.login;

import hello.login.domain.member.Member;
import hello.login.domain.member.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.security.PublicKey;
import java.util.Optional;

@Service
@RequiredArgsConstructor
public class LoginService {

    private final MemberRepository memberRepository;

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

 

로그인을 처리하는 비즈니스 로직인 LoginService 생성

findByLoginId와 파라미터로 입력받은 LoginId가 같다면 return m, 아니라면 null을 반환하는 코드

 

package hello.login.web.login;

import lombok.Data;

import javax.validation.constraints.NotEmpty;

@Data
public class LoginForm {

    @NotEmpty
    private String loginId;

    @NotEmpty
    private String password;
}

 

package hello.login.web.login;

import hello.login.domain.login.LoginService;
import hello.login.domain.member.Member;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
@Slf4j
@RequiredArgsConstructor
public class LoginController {

    private final LoginService loginService;

    @GetMapping("/login")
    public String loginForm(@ModelAttribute("loginForm") LoginForm form) {
        return "login/loginForm";
    }

    @PostMapping("/login")
    public String login(@Validated @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";
        }

        // 로그인 성공 처리 TODO

        return "redirect:/";
    }
}

 

loginMember의 값이 null이라면 글로벌 에러 발생 (bindingResult.reject 호출)

이 에러는 필드에러가 아니기 때문에, 글로벌로 처리해야함. DB까지 확인하고 오는 과정이 있기 때문

 

현재 로그인이 되면 홈 화면에 고객 이름이 보여야하지만 보이지 않는다. 로그인 상태를 유지하면서 로그인에 성공한 사용자는 홈 화면에 접근시 고객의 이름을 보여주려면 어떻게 해야할까?

 

로그인 처리 - 쿠키 사용

쿠키를 사용해서 로그인, 로그아웃 기능을 구현해보자

 

로그인 상태를 유지하려면 쿠키를 사용하면 된다.

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

 

쿠키의 종류

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

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

 

우리는 세션 쿠키를 사용할 것이다.

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

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

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

    // 로그인 성공 처리

    // 쿠키 시간 정보를 주지 않으면 세션 쿠키
    Cookie idCookie = new Cookie("memberId", String.valueOf(loginMember.getId()));
    response.addCookie(idCookie);
    return "redirect:/";
}

 

쿠키 생성 로직을 참고하자

로그인에 성공하면 쿠키를 생성하고, HttpServletResponse에 담는다. 쿠키 이름은 memberId, 값은 회원의 id를 담는다.

웹 브라우저 종료 전까지 계속해서 회원의 id를 서버에 보내주게 된다.

@Slf4j
@Controller
@RequiredArgsConstructor
public class HomeController {

    private final MemberRepository memberRepository;

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

    @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);
}

 

로그아웃도 응답 쿠키를 생성하는데, MaxAge를 0으로 설정한다. 해당 쿠키는 즉시 종료된다. (더이상 브라우저에서 응답하지 않음)

 

쿠키와 보안 문제

쿠키를 사용해서 memberId를 전달해 로그인을 유지할 수 있었다. 그러나 이 방식은 심각한 보안 문제가 있다.

- 쿠키 값을 임의로 변경할 수 있다.

실제로 웹 브라우저 개발자 모드에서 Application -> Cookie를 변경하면 다른 사용자가 되버린다.

다른 사용자로 로그인해서 개인정보를 탈취할 수 있기 때문에 큰 문제가 된다.

 

해결 방법

- 쿠키에 중요한 값을 노출하지 않고, 사용자 별로 예측 불가능한 임의의 토큰 (랜덤값)을 노출하고, 서버에서 토큰과 사용자 id를 매핑해서 인식한다. 그리고 서버에서 토큰을 관리한다.

- 토큰은 해커가 임의의 값을 넣어도 예측하지 못하도록 설정해야한다.

- 토큰이 털려도 시간이 지나면 사용할 수 없도록 토큰 만료시간을 짧게 설정해야 한다.

 

로그인 처리 - 세션 동작 방식

위의 쿠키에 중요한 정보를 저장하는 방식은 여러가지 보안 이슈가 있었다. 이 문제를 해결하려면 결국 중요한 정보를 모두 서버에 저장해야한다. 그리고 클라이언트와 서버는 추정 불가능한 임의의 식별자 값을 연결해야 한다.

 

세션 동작 방식

 

1. 사용자가 loginId, password를 전달하면 서버에서 해당 사용자가 맞는지 확인

2. 세션 ID를 생성 (추정 불가능한 값)

3. 생성된 세션 ID와 세션에 보관할 값 (memberA)을 서버의 세션 저장소에 보관

4. 서버는 클라이언트에 sessionId라는 이름으로 (현재 방식에서) 세션 ID만 쿠키에 담아서 전달

5. 클라이언트는 쿠키 저장소에 sessionId 쿠키를 보관

6. 클라이언트는 요청시 항상 sessionId 쿠키를 전달

7. 서버에서는 클라이언트가 전달한 sessionId 쿠키 정보로 세션 저장소를 조회해서 로그인시 보관한 세션 정보를 사용

 

요악하자면 쿠키에 UUID로 생성한 세션값만 전달, 세션값을 이용할 땐 서버에서 검증 후 처리하게 되는 것

 

세션 직접 만들기

세션 동작방식을 이해하기 위해 직접 만들어보자

 

세션관리는 크게 3가지 기능을 제공하면 된다.

 

1. 세션 생성

- sessionId 생성

- 세션 저장소에 sessionId와 보관할 값 저장

- sessionId로 응답 쿠키를 생성해서 클라이언트에 전달

 

2. 세션 조회

- 클라이언트가 요청한 sessionId 쿠키 값으로 세션 저장소에 보관한 값 조회

3. 세션 만료

- 클라이언트가 요청한 sessionId 쿠키 값으로, 세션 저장소에 보관한 sessionId와 값 제거

 

@Component
public class SessionManager {

    public static final String SESSION_COOKIE_NAME = "mySessionId";
    private Map<String, Object> sessionStore = new ConcurrentHashMap<>();

    public void createSession(Object value, HttpServletResponse response) {

        // 세션 아이디 생성, 값 저장
        String sessionId = UUID.randomUUID().toString();
        sessionStore.put(sessionId, value);

        // 쿠키 생성
        Cookie mySessionCookie = new Cookie(SESSION_COOKIE_NAME, sessionId);
        response.addCookie(mySessionCookie);
    }

    // 세션 조회
    public Object getSession(HttpServletRequest request) {
        Cookie sessionCookie = findCookie(request, SESSION_COOKIE_NAME);
        if (sessionCookie == null) {
            return null;
        }
        return sessionStore.get(sessionCookie.getValue());
    }

    // 세션 만료
    public void expire(HttpServletRequest request) {
        Cookie sessionCookie = findCookie(request, SESSION_COOKIE_NAME);
        if (sessionCookie != null) {
            sessionStore.remove(sessionCookie.getValue());
        }
    }

    public Cookie findCookie(HttpServletRequest request, String cookieName) {
        if (request.getCookies() == null) {
            return null;
        }
        return Arrays.stream(request.getCookies())
                .filter(cookie -> cookie.getName().equals(cookieName))
                .findAny()
                .orElse(null);
    }
}

 

로직은 천천히 읽어보면 이해가 될 것

package hello.login.web.session;

import hello.login.domain.member.Member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;

import static org.assertj.core.api.Assertions.*;

public class SessionManagerTest {

    SessionManager sessionManager = new SessionManager();

    @Test
    void sessionTest() {

        MockHttpServletResponse response = new MockHttpServletResponse();

        // 세션 생성
        Member member = new Member();
        sessionManager.createSession(member, response);

        // 요청에 응답 쿠키 저장
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setCookies(response.getCookies());

        // 세션 조회
        Object result = sessionManager.getSession(request);
        assertThat(result).isEqualTo(member);

        // 세션 만료
        sessionManager.expire(request);
        Object expired = sessionManager.getSession(request);
        assertThat(expired).isNull();
    }
}

 

간단하게 테스트 진행. HttpServletRequest, Response 객체를 직접 사용할 수 없기때문에 Mock을 사용했다.

 

직접 만든 세션 적용

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

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

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

    // 로그인 성공 처리

    // 세션 관리자를 통해 세션 생성, 회원 데이터 보관
    sessionManager.createSession(loginMember, response);

    return "redirect:/";
}
@PostMapping("/logout")
public String logoutV2(HttpServletRequest request) {
    sessionManager.expire(request);
    return "redirect:/";
}

 

private final SessionManager sessionManager;를 주입했다.

 

sessionManager.createSession(loginMember, response);로 로그인 성공시 세션을 등록한다.

세션에 loginMember를 저장해두고 쿠키도 함께 발행한다.

 

HomeController

@GetMapping("/")
public String homeLoginV2(HttpServletRequest request, Model model) {

    // 세션 관리자에 저장된 회원정보 조회
    Member member = (Member) sessionManager.getSession(request);
    if (member == null) {
        return "home";
    }
    
    // 로그인
    model.addAttribute("member", member);
    return "loginHome";
}

 

세션 관리자에서 request로 받은 회원정보를 조회한다.

만약 없다면 쿠키나 세션이 없는것이므로 로그인 되지 않은 것으로 처리, 로그인 성공시 상품 관리, 로그아웃이 있는 페이지로 이동한다.

 

그냥 별거 없다. 세션 관리자에 세션id와 value를 넣어서 관리한다는 정도로 이해하면 된다.

 

로그인 처리 - 서블릿 HTTP 세션1

서블릿은 세션을 위해 HttpSession이라는 기능을 제공하는데, 지금까지 나온 문제들을 해결해준다.

우리가 구현한 기능 말고도 더 많은 기능이 잘 구현되어있다.

 

HttpSession

서블릿이 제공하는 HttpSession도 결국 우리가 만든 SessionManager와 같은 방식으로 동작한다.

서블릿을 통해 HttpSession을 생성하면 JSESSIONID가 생성되고, 값은 추정 불가능한 랜덤값이다.

 

package hello.login.web;

public class SessionConst {
    public static final String LOGIN_MEMBER = "loginMember";
}

HttpSession에 데이터를 보관하고 조회할 때 같은 이름이 중복되어 사용되므로 상수를 하나 정의했다.

(interface나 abstract class로 만드는 방법도 있다.)

 

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

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

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

    // 로그인 성공 처리
    // 세션이 있으면 있는 세션 반환, 없으면 신규 세션 생성
    HttpSession session = request.getSession();
    // 세션에 로그인 회원 정보 보관
    session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);

    return "redirect:/";
}

 

세션을 생성하려면 request.getSession()을 사용하면 된다. (true가 default value)

 

true를 사용하면

- 세션이 있으면 기존 세션 반환

- 세션이 없으면 새로운 세션 생성 후 반환

 

false를 사용하면

- 세션이 있으면 기존 세션 반환

- 세션이 없으면 null 반환 (세션 생성안함)

 

세션에 로그인 회원 정보 보관

session.setAttribute()

세션 데이터를 보관하는 방법은 reequest.setAttribute와 비슷하다. 하나의 세션에 여러 값을 저장할 수 있다.

 

@PostMapping("/logout")
public String logoutV3(HttpServletRequest request) {
    HttpSession session = request.getSession(false);
    if (session != null) {
        session.invalidate();
    }

    return "redirect:/";
}

 

로그아웃시엔 false를 사용했다.

 

@GetMapping("/")
public String homeLoginV3(HttpServletRequest request, Model model) {

    HttpSession session = request.getSession(false);
    if (session == null) {
        return "home";
    }

    Member loginMember = (Member)session.getAttribute(SessionConst.LOGIN_MEMBER);

    // 세션에 회원 데이터 값이 없으면 home
    if (loginMember == null) {
        return "home";
    }

    // 세션이 유지되면 로그인으로 이동
    model.addAttribute("member", loginMember);
    return "loginHome";
}

 

request.getSession(false)

로그인을 하지 않은 사용자도 의미 없이 세션이 만들어지는것을 방지하기 위해 false로 설정했다.

(false는 세션이 없을 시 생성하지 않고 null 반환)

 

session.getAttribute()

로그인 시점에 세션에 보관한 회원 객체를 찾는다.

 

이제 실행 시 JSESSIONID가 정상적으로 생성되는것을 확인할 수 있다.

 

로그인 처리하기 - 서블릿 HTTP 세션2

@SessionAttribute

스프링은 세션을 더 편리하게 사용할 수 있도록 @SessionAttribute를 지원한다.

 

이미 로그인 된 사용자를 찾을 때는 다음과 같이 사용하면 된다.

@GetMapping("/")
public String homeLoginV3Spring(
        @SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false) Member loginMember,
        Model model) {

    // 세션에 회원 데이터 값이 없으면 home
    if (loginMember == null) {
        return "home";
    }

    // 세션이 유지되면 로그인으로 이동
    model.addAttribute("member", loginMember);
    return "loginHome";
}

        // 아래 코드가 생략된 것이다.
        HttpSession session = request.getSession(false);
        if (session == null) {
            return "home";
        }

        Member loginMember = (Member)session.getAttribute(SessionConst.LOGIN_MEMBER);

 

참고로 이 기능은 세션을 생성하지 않는다.

세션을 찾고, 세션에 들어있는 데이터를 찾는 번거로은 과정을 스프링이 한번에 편리하게 처리해준다.

 

TrackingModes

로그인을 처음 시도하면 URL뒤에 ;jessionId=xxxxxx 를 포함한다.

이유는 브라우저가 쿠키를 지원하지 않을 때 URL을 통해 세션을 유지하는 방법이다. (이 방법은 잘 사용하지 않는다.)

서버 입장에선 브라우저가 쿠키를 지원하는지 여부를 최초에 판단할 수 없기 때문에, 쿠키 값도 전달하고 URL에 jsessionId도 같이 전달한다.

 

application.properties에서 설정을 통해 노출되지 않게 할 수 있다.

server.servlet.session.tracking-modes=cookie

 

세션 정보와 타임아웃 설정

@Slf4j
@RestController
public class SessionInfoController {

    @GetMapping("/session-info")
    public String sessionInfo(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session == null) {
            return "세션이 없습니다.";
        }

        //세션 데이터 출력
        session.getAttributeNames().asIterator()
                .forEachRemaining(name -> log.info("session name={}, value ={}", name, session.getAttribute(name)));

        log.info("sessionId={}", session.getId());
        log.info("getMaxInactiveInterval={}", session.getMaxInactiveInterval());
        log.info("creationTime={}", new Date(session.getCreationTime()));
        log.info("lastAccessedTime={}", new Date(session.getLastAccessedTime()));
        log.info("isNew={}", session.isNew());

        return "세션 출력";
    }
}

 

- sessionId : 세션 id, (jssesionId의 값)

- maxInactiveInterval : 세션 유효 시간 (초단위)

- creationTime : 세션 생성 일시

- lascAccessedTime : 세션과 연결된 사용자가 최근에 서버에 접근한 시간. (클라이언트에서 서버로 요청 시 갱신)

- isNew : 새로 생성된 세션인지 아닌지 여부

 

세션 타임아웃 설정

세션은 사용자가 로그아웃을 직접 호출해서 session.invalidate()가 호출되는 경우에 삭제된다. 그런데 대부분의 사용자는 로그아웃을 하지 않고 브라우저를 종료하는 방식을 사용한다. 문제는 HTTP가 비연결성이므로 서버 입장에서는 사용자가 브라우저를 종료한 것인지 알 수 없다. 따라서 서버에서 언제 세션 데이터를 삭제해야하는지 판단하기 어렵다.

 

세션을 무한정 보관하면 발생하는 문제

- 세션과 관련된 쿠키가 탈취되었을 경우에 오랜시간동안 악의적인 요청을 할 수 있음

- 세션은 기본적으로 메모리에 생성되는데, 메모리는 무한하지 않기 때문에 메모리 낭비가 될 수 있음

 

보통 종료시점은 30분정도로 잡으면 될것이다. 그런데 30분마다 세션이 삭제되면 30분에 한번씩 로그인을 다시해야하는 번거로움이 생긴다. 이를 해결하기 위해선 세션 생성 시점이 아닌 사용자가 서버에 요청하는 시간 기준으로 30분으로 계속해서 초기화해주는 것이다.

HttpSession은 이 방식을 사용한다.

 

세션엔 최소한의 데이터만 보관해야한다. 보관한 데이터가 많을수록 메모리에 사용량이 급격하게 늘어난다. 추가로 너무 길게 가져가도 메모리에 부담이 된다. 따라서 적절하게 조절하는것이 필요하다.

'Spring' 카테고리의 다른 글

예외처리와 오류 페이지  (0) 2025.09.19
로그인 처리2 - 필터, 인터셉터  (0) 2025.09.18
Bean Validation  (0) 2025.09.12
오류 코드와 메세지처리, Validator 분리  (0) 2025.09.11
메세지, 국제화  (1) 2025.09.07
'Spring' 카테고리의 다른 글
  • 예외처리와 오류 페이지
  • 로그인 처리2 - 필터, 인터셉터
  • Bean Validation
  • 오류 코드와 메세지처리, Validator 분리
공부처음하는사람
공부처음하는사람
  • 공부처음하는사람
    lazzzykim
    공부처음하는사람
  • 전체
    오늘
    어제
    • 분류 전체보기 (159)
      • Kotlin (31)
      • Java (56)
      • Spring (44)
      • JPA (6)
      • Algorithm (3)
      • TroubleShooting (1)
      • 내일배움캠프 프로젝트 (14)
      • Setting (2)
      • ... (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 글쓰기
  • 링크

  • 인기 글

  • 태그

    spring
    예외처리
    제네릭
    김영한의 실전 자바
    jpa
    언체크예외
    kotlin
    배열
    중첩클래스
    김영한의 실전자바
    내일배움캠프
    싱글톤
    OCP
    다형성
    빈 생명주기
    Di
    트랜잭션
    캡슐화
    java
    김영한
  • hELLO· Designed By정상우.v4.10.3
공부처음하는사람
로그인 처리1 - 쿠키, 세션
상단으로

티스토리툴바