[Spring Boot] Spring Security - Form Login 구현

임원재·2024년 7월 11일
0

SpringBoot

목록 보기
4/19
post-thumbnail

서론

  • Spring Boot를 처음 시작할때부터 나를 괴롭혀왔던 로그인 부분을 확실하게 하고자 정리하게 되었다.
  • 로그인 기능도 크게 나누면 2가지이다.
    1. 직접 회원가입을 진행하는 일명 폼로그인(Form Login)과
    2. OAuth2 Provider (구글, 네이버, 카카오 등..)에서 인가코드와 사용자 정보를 받아 로그인 기능을 구현하는 소셜 로그인(Social Login)이 있다.
  • 로그인을 구현하는데 있어 사용되는 권한, 로직 등을 제공하는 Spring Security를 사용하여 로그인을 구현 가능하다. 하지만 초기설정이 어려워 Spring Security를 사용하지 않고 직접 로그인을 구현하는 방식도 존재한다. Spring Security의 사용유무에 따라 구현 코드도 크게 달라지는것처럼 느껴졌다.
  • 이뿐만이 아니라 Spring Boot의 버전(2.0 ~ or 3.0 ~)에 따라 작성해야하는 코드가 달라지기도 한다.
  • 수많은 방식이 존재하기 때문에 로그인을 구현하기가 어려웠다.
  • 이에 처음부터 간단한 기능을 구현하고 확실히 이해한 다음 추가적인 기능을 덧붙여나가는 식으로 공부해야겠다고 생각했다.
  • 일단은 Spring Security를 사용한 아주 간단한 폼로그인을 구현하고자 한다.

구현 기능

  1. 로그인 페이지 (/login) : GET방식, 로그인 View, 회원가입 페이지 링크

  2. 회원가입 페이지 (/singup) : GET방식, 회원가입 View, 로그인 페이지 링크

  3. 인증 페이지 (/home) : GET방식, 인증된 회원만 접근 가능한 페이지

  4. 로그인 API : 기본적으로 Spring Security에서 POST방식의 /login을 지원한다.

    Spring Security에서 제공하는 인증이 아닌 다른 인증(JWT)으로 진행하고자 한다면 해당 메서드를 구현하면 된다. (url은 설정해서 변경 가능하다)

    로그인 성공 시 /home으로 리다이렉트 예정이다.

  5. 회원가입 API (/api/v1/auth/signup) : 회원가입은 지원하지 않으므로 구현하였다.

  6. 로그아웃 API (/api/v1/auth/logout) : 로그아웃 기능은 Spring Security에서 제공하므로 기본 경로를 변경만 해주었다.

  • Spring Boot 버전은 3.3.1으로 구현하였다.
  • 사용한 라이브러리는 lombok, View를 구현하기 위한 thymeleaf, 그리고 Spring Security를 사용하였다.

Controller

  • View를 반환하는 controller와 api를 반환하는 controller 두개를 생성하였다.

AuthController

@Controller
@RequiredArgsConstructor
public class AuthController {

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

    @GetMapping("/signup")
    public String signup() {
        return "signup";
    }
    
    @GetMapping("/home")
    public String home() {
		    return "home";
    }
}
  • 각각 Login, Signup에 해당하는 View를 리턴한다.
  • home은 로그인 후 리다이렉트되는 페이지로 설정할 예정이다.

AuthApiController

@Controller
@RequiredArgsConstructor
@RequestMapping("/api/v1/auth")
public class AuthApiController {

    private final AuthService authService;

    @PostMapping("/signup")
    public String signup(SignUpRequestDto signUpRequestDto) {
        authService.signup(signUpRequestDto);
        return "redirect:/login";
    }
}
  • 일단은 Spring Security가 제공하는 로그인 기능을 사용할 것이므로 Signup만 구현하였다.
  • 회원가입 시 이메일, 비밀번호만 받으므로, 이를 담은 Dto를 파라미터로 하였다.
  • 해당 핸들러 호출 시 authService에서 signup()을 호출한다.
  • Signup 후 /login으로 리다이렉트하도록 하였다.

Dto

SignUpRequestDto

@Getter
@AllArgsConstructor
public class SignUpRequestDto {
    private String email;
    private String password;

    public Member toEntity(String password) {
        return Member.builder()
                .email(email)
                .password(password)
                .role(Role.USER)
                .type(Type.FORM)
                .build();
    }
}
  • Signup시 사용하는 Dto이다. 이메일과 패스워드를 담는다.
  • 이를 Member Entity로 변환해야하므로 메서드를 생성하였다.

Entity

Member

@Entity
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Member implements UserDetails {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "member_id")
    private Long memberId;

    @Column(name = "email", nullable = false, unique = true)
    private String email;

    @Column(name = "password", nullable = false)
    private String password;

    @Enumerated(EnumType.STRING)
    @Column(name = "role", nullable = false)
    private Role role;

    @Enumerated(EnumType.STRING)
    @Column(name = "type", nullable = false)
    private Type type;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return List.of(new SimpleGrantedAuthority(Role.USER.toString()));
    }

    @Override
    public String getUsername() {
        return email;
    }

    @Override
    public String getPassword() {
        return password;
    }
}
  • 회원을 담을 Entity를 생성하였다.
  • email로도 회원이 특정되도록 unique = true로 설정하였다.
  • UserDetails를 implement한 이유는 UserDetails가 로그인 시 사용되는 회원 정보이기 때문이다. MemberUserDetails를 구현하여 UserDetails로 사용되도록 하였다. UserDetails에 관한 건 뒤에서 후술할 예정이다.
  • Role은 enum 타입으로 아래와 같다
    public enum Role {
        USER, ADMIN
    }
  • Type은 enum타입으로 아래와 같다. 폼로그인이냐, 소셜로그인이냐를 구분할 때 사용할 예정이다.
    public enum Type {
        FORM
    }

Repository

MemberRepository

@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {

    Optional<Member> findByEmail(String email);
}
  • Member를 db에 저장하기 위한 인터페이스이다.
  • email로 Member를 찾아야하는 로직이 있으므로 findByEmail(String email)을 생성하였다.

Service

AuthService

@Service
@RequiredArgsConstructor
public class AuthService {

    private final PasswordEncoder passwordEncoder;
    private final MemberRepository memberRepository;

    public void signup(SignUpRequestDto signUpRequestDto) {
        String encodedPassword = passwordEncoder.encode(signUpRequestDto.getPassword());

        Member member = signUpRequestDto.toEntity(encodedPassword);
        memberRepository.save(member);
        System.out.println("save success in service");
    }
}
  • Signup 로직을 구현하였다.
  • SignUpRequestDto를 받아오고 PasswordEncoder라는 encoder를 불러와 패스워드를 암호화하여 Member Entity로 변환한다.
    이를 save하여 db에 저장한다.

Spring Security

  • Spring Framework를 기반으로 Authentication(인증) 과, Authorization(인가) 기능을 제공하여 보안 기능을 담당하는 프레임워크이다.
  • 로그인 기능을 기본으로 제공하여 많은 로직을 작성하지 않고도 사용 가능하다.
  • build.gradle에 아래와 같이 추가함으로써 불러올 수 있다.
    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter-security'
    }

Authentication 인증

  • Authentication은 사용자가 누구인지를 확인하는 과정이다.
  • 사용자의 정보를 가져오는 과정이라고 볼수도 있겠다.
  • 인증으로는 여러 방식이 있다.
    1. Form Login : 전통적으로 사용자 이름과 비밀번호를 입력하는 방식이다. 현재 사용하려고 하는 기능이다.
    2. HTTP Basic 인증 : HTTP 헤더를 사용하여 인증 정보를 전송한다.
    3. OAuth2 : OAuth2 Provider로부터 사용자의 정보를 받아와 소셜 로그인을 지원한다.

Authorization 인가

  • Authorization은 인증된 사용자가 애플리케이션 내에서 어떠한 작업까지 허용할 것인지 결정하는 과정이다.
  • 인가도 다양한 방식으로 사용된다.
    1. URL 기반 : 특정 URL에 대한 접근 권한을 설정할 수 있다.
    2. 메서드 기반 : 메서드 호출에 대한 권한을 설정한다.

Filter

  • Filter는 Spring Security에서 중요한 개념 중 하나이다.
  • 보통의 Spring Boot는 Client가애플리케이션으로 HTTP 요청을 전송하면 이를 DispatcherServlet이 받아 Controller를 실행한다. Spring Security를 사용하게 되면 HTTP 요청이 DispatcherServlet에 도달하기 전에 FilterChain이라 불리는 Filter들의 집합부터 지나게 된다.
  • 일련의 Filter들을 거치게 되면서 인증, 인가, 로그아웃, 세션 등과 같은 기능을 수행하게 된다. 설정을 통해 어떤 필터를 거칠지, 어떤 필터를 직접 구현해서 쓸지에 따라 충분히 커스텀 가능하다. 해당 Filter의 기능에 대해서는 추후 정리해볼 예정이다.

  • SecurityFilterChain이라는 FilterChain으로 Form Login 과정이 구체화된다. 과정은 다음과 같다.
  1. 인증되지 않은 사용자가 리소스(/private)으로 요청을 보낸다.
  2. SecurityFilterChain에서 인증이 필요한 리소스에 인증되지 않은 요청이 들어오면 AccessDeniedException예외를 날린다.
  3. LoginUrlAuthenticationEntryPoint로 하여금 /login 리다이렉트를 전송한다.
  4. 사용자는 /login 페이지에서 요청을 전송한다.
  5. /login 페이지는 인증이 필요없으므로, Controller에서 View를 받아온다.

  • 위 다이어그램은 사용자가 보낸 username과 password이 어떤 흐름으로 처리되는지 도식화한 것이다.
  1. 사용자가 username과 password를 보내면 UsernamePasswordAuthenticationFilter이 Authentication의 한 종류인 UsernamePasswordAuthenticationToken를 생성한다.

  2. UsernamePasswordAuthenticationTokenAuthenticationManager로 전달되어 인증받는다.

    AuthenticationManager의 역할은 다음과 같다. UsernamePasswordAuthenticationToken에 저장되어있는 username, password로 UserDetailsService를 호출하여 Member중에 존재하는 회원인지 체크한다. 이때 AuthenticationManager가 Signup때 db Member테이블에서 찾을 수 있도록 UserDetailsService을 구현해야한다.

    회원이 존재할 때, 인증에 성공한다. 회원이 없으면 인증에 실패한다.

  3. 인증에 실패하면, SecurityContextHolder가 지워진다. SecurityContextholder는 현재 사용자의 Authentication을 저장하는 SecurityContext를 관리한다.

    그 후 AuthenticationFailureHandler가 실행된다. 이를 implement하여 사용자 정의로 실패 시 어떤 로직을 수행할지 결정할 수 있다.

  4. 인증에 성공하면, 인증에 실패했을 때의 반대처럼 행동한다. SecurityContextHolder에 사용자의 Authentication이 설정되고 AuthenticationSuccessHandler가 실행된다.


SecurityConfig

  • 이제 폼로그인을 구현하기 위한 Spring Security설정을 작성할 것이다. SecurityFilterChain를 빈으로 등록하여 폼로그인 시 필요한 설정을 맞춰야 한다.
@EnableWebSecurity
@Configuration
@RequiredArgsConstructor
public class SecurityConfig {

    private final CustomAuthSuccessHandler authSuccessHandler;
    private final CustomAuthFailureHandler authFailureHandler;

    private static final String[] AUTH_WHITELIST = {
        "/swagger-ui/**", "/", "/login", "/signup",
        "/api/v1/auth/**"
    };

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        //CSRF, CORS, BasicHttp 비활성화
        http
                .csrf(AbstractHttpConfigurer::disable)
                .cors(AbstractHttpConfigurer::disable);
                .httpBasic(AbstractHttpConfigurer::disable);

        //세션 관리 구성
        http
                .sessionManagement(sessionManagement -> sessionManagement
                        .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED));

        //ForLogin, Logout 활성화
        http
                .formLogin(form -> form
                        .loginPage("/login")
                        .successHandler(authSuccessHandler)
                        .failureHandler(authFailureHandler)
                )
                .logout(logout -> logout
                        .logoutUrl("/api/v1/auth/logout")
                        .logoutSuccessUrl("/login")
                )

        
        // permit, authenticated 경로 설정
        http
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers(AUTH_WHITELIST).permitAll() 
                        // 지정한 경로는 인증 없이 접근 허용
                        .anyRequest().authenticated());
                        // 나머지 모든 경로는 인증 필요

        return http.build();
    }
    
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
  • @EnableWebSecurity : Web Security를 활성화하겠다는 어노테이션
  • @Configuration : 해당 어노테이션으로 해당 클래스가 Spring 설정 클래스로 인식되도록 함
  • 일단, CSRF, CORS, BsicHttp를 비활성화 한다.
  • 폼로그인에서는 세션이 사용되므로 세션 활성화시켜야 한다. 추후 구현할 JWT에서는 세션이 필요없으므로 그때는 세션을 비활성화해야한다.
  • Form Login
    • loginpage를 설정해야한다. 그래야 Spring Security가 이를 인식하고 login 로직을 수행한다.
    • 로그인 성공시, 실패시 어떤 로직을 실행할지 handler를 설정하였다.
    • authSuccessHandler에서는 인증이 필요한 /home으로 리다이렉트되도록 설정하였고,
    • authFailureHandler에서는 /login?error=true로 리다이렉트되도록 설정하였다.
  • Logout
    • logout은 SecurityContextHolder에서 사용자의 인증정보를 삭제하는 로직을 수행한다.
    • logout호출할 url을 설정한다. 로그아웃은 View가 필요없으므로 api방식으로 작성하였다.
    • 로그아웃 성공시 리다이렉트할 url을 설정한다. 로그아웃시 로그인 페이지로 가도록 하였다.
  • permit, authenticated 경로 설정
    • requestMatchers에 들어가는 경로들을 permitAll()하여 해당 경로들은 인가 없이 사용 가능하도록 했다. 이에 들어가는 경로들은 "/swagger-ui/**", "/", "/login", "/signup", "/api/v1/auth/**"와 같은 swagger, auth관련한 인가가 필요없는 경로들을 넣어놓았다.
    • .anyRequest().authenticated()로 그 이외의 경로들은 인증이 필요하도록 설정하였다.
  • BCryptPasswordEncoder을 빈으로 설정하였다. Signup 때 password를 암호화하기 위함이다.

CustomAuthSuccessHandler

@Component
public class CustomAuthSuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.sendRedirect("/home");
    }
}
  • SecurityConfig에서 사용되는 AuthenticationSuccessHandler를 커스텀한 CustomAuthSuccessHandler이다.
  • /home으로 리다이렉트되도록 하였다.

CustomAuthFailureHandler

@Component
public class CustomAuthFailureHandler implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.sendRedirect("/login?error=true");
    }
}
  • SecurityConfig에서 사용되는 AuthenticationFailureHandler를 커스텀한 CustomAuthFailureHandler이다.
  • /login?error=true를 리다이텍트하도록 하였다.

CustomUserDetailsService

@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {

    private final MemberRepository memberRepository;

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        return memberRepository.findByEmail(email)
                .orElseThrow(() -> new UsernameNotFoundException("Member Not Found"));
    }
}
  • 해당 클래스는 AuthenticationManager에서 사용되는 Service이다.
  • Spring Security에서 로그인을 수행했을 때 받아온 username과 password로 사용자가 존재하는지 존재하지 않는지 찾는 로직을 구현해놓지 않았다. 사용자를 찾기 위해 해당 서비스 작성은 필수적이다.
  • loadUserByUsername(String email)메서드에서 memberRepository를 통해 email로 쉽게 멤버를 찾을 수 있다.
public class Member implements UserDetails {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "member_id")
    private Long memberId;
		....
		....
		
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return List.of(new SimpleGrantedAuthority(Role.USER.toString()));
    }

    @Override
    public String getUsername() {
        return email;
    }

    @Override
    public String getPassword() {
        return password;
    }

}
  • 아까 작성한 Member 클래스를 보자, UserDetails를 implements 함으로써 로그인시의 username, password가 Member클래스와 연관관계를 생성하였다.
  • 각각 인가, username, password를 get하는 메서드를 오버라이드 하였다. 이때 getUsername()에서 email을 반환함으로써 CustomUserDetailsServiceloadUserByUsername(String email)메서드에 email이 파라미터로 입력되도록 구현하였다.

실행

  • 추가로 Spring Security관련한 로그 레벨을 DEBUG로 설정하여 Auth과정을 확인할 수 있도록 하였다. (applitacion.properties)
    logging.level.org.springframework.security= DEBUG
  • 이제 애플리케이션을 실행할 수 있다.
  • 다음은 localhost:8080에 대한 View이다.
  • 계정이 없으므로 Sign up을 먼저 하자.
  • 가입을 진행하면 아래와 같은 로그가 출력된다.
2024-07-11T20:20:04.218+09:00 DEBUG 18384 --- [spring demo] [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Securing POST /api/v1/auth/signup
2024-07-11T20:20:04.223+09:00 DEBUG 18384 --- [spring demo] [nio-8080-exec-1] o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to anonymous SecurityContext
2024-07-11T20:20:04.223+09:00 DEBUG 18384 --- [spring demo] [nio-8080-exec-1] o.s.s.w.session.SessionManagementFilter  : Request requested invalid session id 691B9E9DAF197053E87C8E75D1E29053
2024-07-11T20:20:04.224+09:00 DEBUG 18384 --- [spring demo] [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : Secured POST /api/v1/auth/signup
Hibernate: 
    insert 
    into
        member
        (email, password, role, type) 
    values
        (?, ?, ?, ?)
save success in service
2024-07-11T20:20:04.370+09:00 DEBUG 18384 --- [spring demo] [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : Securing GET /login
2024-07-11T20:20:04.370+09:00 DEBUG 18384 --- [spring demo] [nio-8080-exec-2] o.s.s.w.a.AnonymousAuthenticationFilter  : Set SecurityContextHolder to anonymous SecurityContext
2024-07-11T20:20:04.370+09:00 DEBUG 18384 --- [spring demo] [nio-8080-exec-2] o.s.s.w.session.SessionManagementFilter  : Request requested invalid session id 691B9E9DAF197053E87C8E75D1E29053
2024-07-11T20:20:04.371+09:00 DEBUG 18384 --- [spring demo] [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : Secured GET /login
  1. Set SecurityContextHolder to anonymous SecurityContext로 보아, 로그인하지 않은 사용자를 익명 사용자로 설정했음을 알 수 있다.
  2. POST /api/v1/auth/signup 를 진행하여 Hibernate로 db에 insert를 진행한다.

  • 다음과 같이 db에 insert되었음을 볼 수 있다.

  • 이제 로그인을 진행하자.

    Authorized된 사용자만 접속 가능한 /home으로 리다이렉트 되며 아래와 같은 View가 보이게 된다.

2024-07-11T20:26:27.574+09:00 DEBUG 18384 --- [spring demo] [nio-8080-exec-5] o.s.security.web.FilterChainProxy        : Securing POST /login
Hibernate: 
    select
        m1_0.member_id,
        m1_0.email,
        m1_0.password,
        m1_0.role,
        m1_0.type 
    from
        member m1_0 
    where
        m1_0.email=?
2024-07-11T20:26:27.788+09:00 DEBUG 18384 --- [spring demo] [nio-8080-exec-5] o.s.s.a.dao.DaoAuthenticationProvider    : Authenticated user
2024-07-11T20:26:27.813+09:00 DEBUG 18384 --- [spring demo] [nio-8080-exec-5] w.c.HttpSessionSecurityContextRepository : Stored SecurityContextImpl [Authentication=UsernamePasswordAuthenticationToken [Principal=spring.auth.entity.Member@6ef44bfb, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[USER]]] to HttpSession [org.apache.catalina.session.StandardSessionFacade@618ccf9f]
2024-07-11T20:26:27.813+09:00 DEBUG 18384 --- [spring demo] [nio-8080-exec-5] w.a.UsernamePasswordAuthenticationFilter : Set SecurityContextHolder to UsernamePasswordAuthenticationToken [Principal=spring.auth.entity.Member@6ef44bfb, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[USER]]
2024-07-11T20:26:27.817+09:00 DEBUG 18384 --- [spring demo] [nio-8080-exec-6] o.s.security.web.FilterChainProxy        : Securing GET /home
  • Hibernate로 입력받은 email로 select문이 실행됨을 확인할 수 있다.

  • Authenticated user가 출력되어 인증이 완료되었다.

  • HttpSessionSecurityContextRepository가 인증된 사용자의 SecurityContext를 HTTPSession에 저장한다. 여기서 사용자 정보는 Authentication인 UsernamePasswordAuthenticationToken 형태로 저장된다.

  • 저장 후 /home으로 리다이렉트된다.

  • View로 생성하지 않았지만 로그아웃 하기 위해 /api/v1/auth/logout을 호출하면 로그아웃이 진행되어 /login으로 리다이렉트된다.

2024-07-11T20:30:38.452+09:00 DEBUG 18384 --- [spring demo] [nio-8080-exec-9] o.s.security.web.FilterChainProxy        : Securing GET /api/v1/auth/logout
2024-07-11T20:30:38.452+09:00 DEBUG 18384 --- [spring demo] [nio-8080-exec-9] w.c.HttpSessionSecurityContextRepository : Retrieved SecurityContextImpl [Authentication=UsernamePasswordAuthenticationToken [Principal=spring.auth.entity.Member@6ef44bfb, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[USER]]]
2024-07-11T20:30:38.452+09:00 DEBUG 18384 --- [spring demo] [nio-8080-exec-9] o.s.s.w.a.logout.LogoutFilter            : Logging out [UsernamePasswordAuthenticationToken [Principal=spring.auth.entity.Member@6ef44bfb, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[USER]]]
  • SecurityContext에서 인증된 사용자의 정보를 가져온다.
  • LogoutFilter가 인증된 사용자를 로그아웃 처리한다. 로그아웃이 수행되면서 사용자의 인증 정보를 삭제한다.

정리

  • Spring Security로 Form Login을 구현해보았다. 전반적인 흐름을 이해하는데 있어서 Spring 공식 문서의 도움이 컸다.

Spring Security

  • 위의 폼 로그인은 세션을 사용한 Username/Password Authentication이다. 위의 로그에서 확인했듯이 사용자의 세션 정보가 SecurityContext에 저장되어 관리된다. 클라이언트 측에서 조작할 수 없으므로 정보의 무결성이 유지된다. 또한 서버에서 세션 만료 시간을 설정하여 자동으로 로그아웃 시키는것도 가능하다. 하지만 서버에 저장된다는 것은 서버의 리소스를 사용한다는 뜻이다.
  • 이에 JWT라는 토큰 인증 방식은 클라이언트 측에 저장되어 서버의 리소스를 사용하지 않는다는 장점이 있다. 또한 서버 간의 세션 동기화가 필요 없어 확장이 용이하다.
  • 다음에는 위에서 구현한 폼 로그인을 JWT방식으로 구현해볼 예정이다.

0개의 댓글