Notice
Recent Posts
Recent Comments
Link
«   2026/05   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
Tags
more
Archives
Today
Total
관리 메뉴

김쥬르에 개발일지

Spring Boot 스프링 시큐리티 본문

Spring boot

Spring Boot 스프링 시큐리티

김쥬르 2024. 1. 1. 21:28

이번엔 스프링 시큐리티를 이용하여

로그인/로그아웃을 구현해보겠습니다.

 

스프링 시큐리티?

스프링 시큐리티는 스프링 기반의 애플리케이션 보안(인증,인가,권한)을 담당하는

스프링 하위 프레임워크입니다. 스프링 시큐리티를 이해하려면 인증과 인가에 대한

개념을 알아야합니다.

 

인증과 인가?

인증은 사용자의 신원을 입증하는 관정입니다.

예를 들어 사용자가 사이트에 로그인을 할 때 누구인지 확인하는 과정을 인증이라고 합니다.

인가는 사이트의 특정 부분에 접근할 수 있는지에 권한을 확인하는 작업입니다.

예를 들어 관리자는 관리자 페이지에 들어갈 수 있지만

일반 사용자는 관리자 페이지에 들어갈 수 없습니다.

이런 권한을 확인하는 과정을 인가라고 합니다.

 

스프링 시큐리티에 대해 더 알아보자면

보안 관련 옵션을 많이 제공합니다. 어너테이션으로 설정이 쉽고

CSRF 공격, 세션 고정 공격을 방어해주고 요청 헤더도 보안 처리를 해주어

개발자가 보안 관련 개발을 해야 하는 부담을 크게 줄여줍니다.

 

CSRF 공격?

사용자의 권한을 가지고 특정 동작을 수행하도록 유도하는 공격입니다.

예를 들어 커뮤니티 사이트에서 자신의 게시글에 좋아요를 늘리고 싶어서

나의 게시글을 보면 자동으로 좋아요가 눌리게 조작을 하는겁니다. 좋아요를 눌렀을때

url 값이 "http://community.com/like?post=[게시글 id]" 이였고 본인 게시글 id가 1111라고 했을때

게시글 안에 이미지 태그를 삽입하여 src 값에 "http://community.com/like?post=1111"를 넣어

게시글을 조회하면 자동으로 좋아요가 눌리게 되는것이죠.

 

세션 고정 공격?

사용자의 인증 정보를 탈취하거나 변조하는 공격입니다.

https://guleum-zone.tistory.com/163

 

Session Fixation(세션고정) 취약점

개요 Session Fixation(세션 고정) 이란 로그인 시 발급받은 세션 ID가 로그인 전/후 모두 동일하게 사용되어 악의적인 사용자가 피해자의 세션을 하이제킹 하여 정상적인 사용자로 위장하여 접근하

guleum-zone.tistory.com

 

스프링 시큐리티는 필터 기반으로 동작합니다.

스프링 시큐리티의 필터 구조를 살펴보겠습니다.

스프링 시큐리티는 이렇게 다양한 필터들로 나누어져 있으며

각 필터에서 인증 , 인가와 관련된 작업을 처리합니다.

 

위에서 아래로 순차적으로 필터를 거칩니다.

필터를 실행할 때는 화살표로 연결된 오른쪽 박스의 클래스를 거치며 실행합니다.

원하는 때에는 특정 필터를 제거하거나 필터 뒤에 커스텀 필터를 넣는 등의 설정도 가능합니다.

여기서 중요한 필터는 1번 표시가 된 UsernamePasswordAuthenticationFilter와

2번 표시가 된 FilterSecurityIntercepter입니다.

1번은 아이디와 패스워드가 넘어오면 인증 요청을 위임하는 인증관리자 역할을 합니다.

2번은 권한 부여 처리를 위임해 접근 제어 결정을 쉽게 하는 접근 결정 관리자 역할을 합니다.

 

각 필터별 역할 설명은 한번씩 보시기 바라겠습니다.

 

가장 많이 사용하는 아이디와 패스워드 기반 폼 로그인을 시동하면

스프링 시큐리티에서는 어떤 절차로 인증 처리를 하는지 알아보겠습니다.

 

1. 사용자가 폼에 아이디와 패스워드를 입력하면

HTTPServletRequest에 아이디와 비밀번호 정보가 전달됩니다.

 

2.유효성 검사가 끝나면 실제 구현체인

UsernamePasswordAuthenticationToken을 만들어 넘겨줍니다.

 

3.전달받은 인증용 객체인

UsernamePasswordAuthenticationToken 을 AuthenticationManager에게 보냅니다.

 

4.UsernamePasswordAuthenticationToken을 AuthenticationProvider에 보냅니다.

 

5. 사용자 아이디를 UserDetailService에 보냅니다.

UserDetailService는 사용자 아이디로 찾은 사용자의 정보를 UserDetails 객체로 만들어

AuthenticationProvider에게 전달합니다.

 

6.DB에 있는 사용자 정보를 가져옵니다.

 

7.입력 정보와 UserDetails의 정보를 비교해 실제 인증 처리를 합니다.

 

8~10까지 인증이 완료되면 SecurityContextHolder에 Authentication를 저장합니다.

인증 성공 여부에 따라 성공하면 AuthenticationSuccessHandler 실패하면

AuthenticationFailureHandler 핸들러를 실행합니다.

 

스프링 시큐리티 폼 로그인의 인증 흐름을 알아보았습니다.

스프링 시큐리티의 폼 로그인을 설정하는것은 간단하지만

실제로는 이러한 복잡한 내부 동작을 실행합니다.

물론 이 동작은 모두 외워야 하는것은 아니지만 어떠한 흐름으로 로그인이

동작하는지 이해하면 스프링 시큐리티를 더 잘 이해하고 활용할수 있을것입니다.

 

 

기본 지식을 알았으니 직접 인증 , 인가 기능을 구현해보겠습니다.

회원 정보를 저장할 테이블을 만들고 테이블과 연결할 도메인을 만든 다음,

이 테이블과 연결할 회원 엔티티를 만들고 , 회원 엔티티와 연결되어 데이터를 조회하게 해줄

리포지터리를 만든 후 마지막으로 시큐리티에서 사용자 정보를 가져오는 서비스를 만들겠습니다.

 

우선 스프링 시큐리티를 사용하기 위해 의존성을 추가하겠습니다.

implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
testImplementation 'org.springframework.security:spring-security-test'

해당 의존성을 추가해주시고 그레이들을 새로고침해주세요.

 

회원 엔티티와 매핑할 테이블의 구조를 참고해 회원 엔티티를 만들겠습니다.

컬럼명 자료형 null 허용 설명
id INT N  기본키(pk) 일련번호, 기본키
email VARCHAR(255) N   이메일
password VARCHAR(255) N   패스워드(암호화저장)
created_at DATETIME N   생성일자
updated_at DATETIME N   수정일자

 

domain 패키지에 User.java 파일을 생성하고 코드를 작성합니다.

 

@Table(name = "users")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Entity
public class User implements UserDetails { // UserDetails를 상속받아 인증 객체로 사용

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", updatable = false)
    private Long id;

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

    @Column(name = "password")
    private String password;

    @Builder
    public User(String email, String password, String nickname) {
        this.email = email;
        this.password = password;
    }

    @Override //권한 반환
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return List.of(new SimpleGrantedAuthority("user"));
    }

    @Override //사용자의 id를 반환(고유한 값)
    public String getUsername() {
        return email;
    }

    @Override // 사용자의 패스워드를 반환
    public String getPassword() {
        return password;
    }
	
    //계정 만료 여부 반환
    @Override
    public boolean isAccountNonExpired() {
    // 만료되었는지 확인하는 로직
        return true; 만료되지 않았음
    }
	
    // 계정 잠금 여부 반환
    @Override 
    public boolean isAccountNonLocked() {
        // 계정 잠금되었는지 확인하는 로직
        return true; // true -> 잠금되지 않았음
    }
	
    // 패스워드의 만료 여부 반환
    @Override 
    public boolean isCredentialsNonExpired() {
        // 패스워드가 만료되었는지 확인하는 로직
        return true; // true -> 만료되지 않았음
    }

    @Override // 계정 사용 가능 여부 반환
    public boolean isEnabled() {
        // 계정이 사용 가능한지 확인하는 로직
        return true; // true -> 사용 가능
    }
}

 

User 클래스가 상속한 UserDetails 클래스는

스프링 시큐리티에서 사용자의 인증정보를 담아두는 인터페이스입니다.

스프링 시큐리티에서 해당 객체를 통해 인증 정보를 가져오므로

필수 오버라이드 메서드가 있습니다.

메서드 반환 타입 설명
getAuthorities() Collection<? extends GramtedAuthority> 사용자가 가지고있는 권한의 목록을 반환합니다.
현재 예제 코드에서는 사용자 이외의 권한이 없기 때문에
user 권한만 담아 반환합니다.
getUsername() String 사용자를 식별할 수 있는 사용자 이름을 반환합니다.
이때 사용되는 사용자 이름은 반드시 고유해야 합니다. 현재 예제 코드는 유니크 속성이 적용된 이메일을 반환합니다.
getPassword() String 사용자의 비밀번호를 반환합니다.
이때 저장되어 있는 비밀번호는 암호화해서 저장합니다.
isAccountNonExpired() boolean 계정이 만료되었는지 확인하는 메서드입니다.
만약 만료되지 않은 때는 true를 반환합니다
isAccountNonLocked boolean 계정이 잠금되었는지 확인하는 메서드입니다.
만약 잠금되지 않은 때는 true를 반환합니다
isCredentialsNonExpired() boolean 비밀번호가 만료되었는지 확인하는 메서드입니다.
만약 만료되지 않은 때는 true를 반환합니다.
isEnabled() boolean 계정이 사용 가능한지 확인하는 메서드입니다.
만약 사용 가능하다면 true를 반환합니다.

 

repository 디렉터리에 UserRepository.java 파일을 생성하고 인터페이스를 만들어줍니다.

 

public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);
}

 

사용자를 식별하기 위해 이메일을 사용합니다.

사용자 정보를 가져오기 위해서는 스프링 시큐리티가 이메일을 전달받아야합니다.

스프링 데이터 JPA는 메서드 규칙에 맞춰 메서드를 선언하면 이름을 분석해 자동으로 쿼리를 생성해줍니다.

findByEmail() 메서드는 실제 데이터베이스에 회원 정보를 요청할 때 다음 쿼리를 실행합니다.

 

From users
        Where email = #{eamil}

 

로그인을 진행할 때 사용자 정보를 가져오는 코드를 작성하겠습니다.

service 디렉터리에 UserDetailService.java 파일을 생성하고 아래 코드를 입력합니다.

 

@RequiredArgsConstructor
@Service
// 스프링 시큐리티에서 사용자 정보를 가져오는 인터페인스
public class UserDetailService implements UserDetailsService {

    private final UserRepository userRepository;

    // 사용자 이름(email)으로 사용자의 정보를 가져오는 메서드
    @Override
    public User loadUserByUsername(String email) {
        return userRepository.findByEmail(email)
                .orElseThrow(() -> new IllegalArgumentException((email)));
    }

}

 

스프링 시큐리티에서 사용자의 정보를 가져오는 UserDetailService 인터페이스를 구현합니다.

필수로 구현해야 하는 loadUserByUsername() 메서드를 오버라이딩해서 사용자 정보를 가져오는 로직을 작성합니다.

 

인증 처리를 하는 시큐리티 설정 파일을 작성하겠습니다.

config 패키지를 새로 만들어 WebSecurityConfig.java 를 생성하고 아래 코드를 작성합니다.

 

@RequiredArgsConstructor
@Configuration

public class WebSecurityConfig {
    private final UserDetailService userService;

    // 1.스프링 시큐리티 기능 비활성화
    @Bean
    public WebSecurityCustomizer configure() {
        return (web) -> web.ignoring()
                .requestMatchers("/img/**", "/css/**", "/js/**");
    }
    
    // 2.특정 HTTP 요청에 대한 웹 기반 보안 구성
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
        http    
                // 3.인증 , 인가 설정
                .authorizeHttpRequests((auth) -> auth
                        .requestMatchers("/login", "/signup", "/user").permitAll()
                        .anyRequest().authenticated()
                )
                // 4.폼 기반 로그인 설정
                .formLogin((formLogin) -> formLogin
                        .loginPage("/login")
                        .defaultSuccessUrl("/articles"));

        http
                // 5.로그아웃 설정
                .logout((logout) ->
                        logout.logoutSuccessUrl("/login")
                                .invalidateHttpSession(true)
                );

        http.csrf((csrf) -> csrf.disable()); // 6.csrf 비활성화

        return http.build();
    }
    
    // 7.인증 관리자 관련 설정
    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider() throws Exception {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();

        daoAuthenticationProvider.setUserDetailsService(userService); // 8.사용자 정보 서비스 설정
        daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());

        return daoAuthenticationProvider;
    }
    
    // 9.패스워드 인코더로 사용할 빈 등록
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {

        return new BCryptPasswordEncoder();
    }
}

 

설명한 내용이 많아 번호를 붙여 설명하겠습니다.

 

1.스프링 시큐리티의 인증, 인가 서비스는 모든 곳에 적용하지 않습니다.

일반적으로 정적 리소스(HTML,이미지 등등)에 설정하는데

정적 리소스는 클라이언트 측에서 사용되므로 , 스프링 시큐리티의 강력한

인증 및 권한 부여 기능이 필요하지 않아서 입니다.

 

2. 특정 HTTP 요청에 대해 웹 기반 보안을 구성합니다.

인증/인가 및 로그인,로그아웃 관련 설정을 할수있습니다.

 

3. 특정 경로에 대한 엑세스 설정을 합니다.

.requestMatchers() : 특정 요청과 일치하는 url에 대한 엑세스를 설정합니다.

premitAll() : 누구나 접근이 가능하게 설정합니다.

"/login,/signup,/user" 로 요청이 오면 인증/인가 없이도 접근할수있습니다.

anyRequest() : 위에서 설정한 url 이외의 요청에 대해서 설정합니다.

authenticated() : 별도의 인가는 필요하지 않지만 인증이 접근할 수 있습니다. 

 

4. 폼 기반 로그인 설정을 합니다.

loginPage() : 로그인 페이지 경로를 설정합니다.

defalutSuccessUrl() : 로그인이 완료되었을 때 이동할 경로를 설정합니다.

 

5. 로그아웃 설정을 합니다.

logoutSuccessUrl() : 로그아웃이 완료되었을 때 이동할 경로를 설정합니다.

invalidateHttpSession() : 로그아웃 이후에 세션을 전체 삭제할지 여부를 설정합니다.

 

6. CSRF 설정을 비활성화합니다.

CSRF 공격을 방지하기 위해서는 활성화하는게 좋지만 설정을

편리하게 하기 위해 비활성화합니다.

 

7. 인증 관리자 관련 설정입니다.

사용자 정보를 가져올 서비스를 재정의하거나 , 인증 방법

예를 들어 LDAP , JDBC 기반 인증 등을 설정합니다.

 

8. 사용자 서비스를 설정합니다.

userDetailService() : 사용자 정보를 가져올 서비스를 설정합니다.

서비스 클래스는 반드시 UserDetailService를 상속받은 클래스여야만 합니다.

 

9. 패스워드 인코더를 빈으로 등록합니다.

 

시큐리티 설정이 완료되었으니 회원가입을 구현하겠습니다.

회원 정보를 추가하는 서비스 메서드를 작성한 뒤에 회원 가입 컨트롤러를 구현합니다.

 

dto 디렉터리에 사용자 정보를 담고 있는 객체를 생성합니다.

AddUserRequest.java 파일을 추가하고 아래 코드를 작성합니다.

@Getter
@Setter
public class AddUserRequest {
    private String email;
    private String password;
}

 

이어서 AddUserRequest 객체를 인수로 받는 회원 정보를 추가하는 메서드를 작성합니다.

service 디렉터리에 UserService.java 파일을 생성하고 코드를 작성합니다.

@RequiredArgsConstructor
@Service
public class UserService {

    private final UserRepository userRepository;
	private final BCryptPasswordEncoder bCryptPasswordEncoder;
    
    public Long save(AddUserRequest dto) {
        return userRepository.save(User.builder()
                .email(dto.getEmail())
                // 패스워드 암호화
                .password(encoder.encode(dto.getPassword()))
                .build()).getId();
    }
}

 

패스워드를 저장할 때 시큐리티를 설정하며 패스워드 인코딩용으로 등록한

빈을 사용해서 암호화 한 후에 저장합니다.

 

회원 가입 폼에서 회원 가입 요청을 받으면 서비스 메서드를 사용해

사용자를 저장한 뒤 로그인 페이지로 이동하는 signup() 메서드를 작성합니다.

 

 controller 디렉터리에 UserApiController.java 파일을 만든후 코드를 작성합니다.

 

@RequiredArgsConstructor
@Controller
public class UserApiController {

    private final UserService userService;

    @PostMapping("/user")
    public String signup(AddUserRequest request) {
        userService.save(request); // 회원 가입 메서드 호출
        return "redirect:/login"; // 회원 가입이 완료된 이후에 로그인 페이지로 이동
    }
}

 

회원 가입 처리가 된 다음 로그인 페이지로 이동하기 위해 redirect: 를 붙여

회원 가입 처리가 끝나면 강제로 /login URL에 해당하는 화면으로 이동합니다.

 

로그인 / 회원가입 경로로 접근하면 뷰 파일을 연결하는 컨트롤러를 생성합니다.

controller 디렉터리에 UserViewController.java 파일을 만든후 코드를 작성합니다.

 

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

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

 

/login 경로로 접근하면 login() 메서드가 login.html을 불러오고

/signup 경로에 접근하면 signup() 메서드는 signup.html를 반환합니다.

 

templates 디렉터리에 login.html을 생성한후 아래 코드를 작성합니다.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>로그인</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css">

    <style>
        .gradient-custom {
            background: linear-gradient(to right, rgba(106, 17, 203, 1), rgba(37, 117, 252, 1))
        }
    </style>
</head>
<body class="gradient-custom">
<section class="d-flex vh-100">
    <div class="container-fluid row justify-content-center align-content-center">
        <div class="card bg-dark" style="border-radius: 1rem;">
            <div class="card-body p-5 text-center">
                <h2 class="text-white">LOGIN</h2>
                <p class="text-white-50 mt-2 mb-5">서비스를 사용하려면 로그인을 해주세요!</p>

                <div class = "mb-2">
                    <form action="/login" method="POST">
                        <input type="hidden" th:name="${_csrf?.parameterName}" th:value="${_csrf?.token}" />
                        <div class="mb-3">
                            <label class="form-label text-white">Email address</label>
                            <input type="email" class="form-control" name="username">
                        </div>
                        <div class="mb-3">
                            <label class="form-label text-white">Password</label>
                            <input type="password" class="form-control" name="password">
                        </div>
                        <button type="submit" class="btn btn-primary">Submit</button>
                    </form>

                    <button type="button" class="btn btn-secondary mt-3" onclick="location.href='/signup'">회원가입</button>
                </div>
            </div>
        </div>
    </div>
</section>
</body>
</html>

 

이어서 signup.html을 생성하여 로그아웃 뷰를 작성해줍니다.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>회원 가입</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/css/bootstrap.min.css">

    <style>
        .gradient-custom {
            background: linear-gradient(to right, rgba(254, 238, 229, 1), rgba(229, 193, 197, 1))
        }
    </style>
</head>
<body class="gradient-custom">
<section class="d-flex vh-100">
    <div class="container-fluid row justify-content-center align-content-center">
        <div class="card bg-dark" style="border-radius: 1rem;">
            <div class="card-body p-5 text-center">
                <h2 class="text-white">SIGN UP</h2>
                <p class="text-white-50 mt-2 mb-5">서비스 사용을 위한 회원 가입</p>

                <div class = "mb-2">
                    <form th:action="@{/user}" method="POST">
                        <!-- 토큰을 추가하여 CSRF 공격 방지 -->
                        <input type="hidden" th:name="${_csrf?.parameterName}" th:value="${_csrf?.token}" />
                        <div class="mb-3">
                            <label class="form-label text-white">Email address</label>
                            <input type="email" class="form-control" name="email">
                        </div>
                        <div class="mb-3">
                            <label class="form-label text-white">Password</label>
                            <input type="password" class="form-control" name="password">
                        </div>

                        <button type="submit" class="btn btn-primary">Submit</button>
                    </form>
                </div>
            </div>
        </div>
    </div>
</section>
</body>
</html>

 

이번엔 로그아웃 기능을 구현합니다.

로그아웃을 마지막으로 회원 가입 , 로그인 , 로그아웃 테스트를 진행하겠습니다.

 

UserApiController에 logout() 메서드를 추가합니다.

    @GetMapping
    public String logout(HttpServletRequest request, HttpServletResponse response) {
        new SecurityContextLogoutHandler().logout(request, response,
                SecurityContextHolder.getContext().getAuthentication());
        return "redirect:/login";
    }
}

/logout GET 요청을 하면 로그아웃을 담당하는 핸들러인

SecurityContextLogoHandler의 logout() 메서드를 호출해서 로그아웃합니다.

 

블로그 글 목록 뷰 파일 articleList.html에 로그아웃 버튼을 추가합니다.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>블로그 글 목록</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
</head>
<body>
<div class="p-5 mb-5 text-center</> bg-light">
    <h1 class="mb-3">My Blog</h1>
    <h4 class="mb-3">블로그에 오신 것을 환영합니다.</h4>
</div>

<div class="container">
    <button type="button" id="create-btn"
            th:onclick="|location.href='@{/new-article}'|"
            class="btn btn-secondary btn-sm mb-3">글 등록</button>
    <div class="row-6" th:each="item : ${articles}">
        <div class="card">
            <div class="card-header" th:text="${item.id}">
            </div>
            <div class="card-body">
                <h5 class="card-title" th:text="${item.title}"></h5>
                <p class="card-text" th:text="${item.content}"></p>
                <a th:href="@{/articles/{id}(id=${item.id})}" class="btn btn-primary">보러가기</a>
            </div>
        </div>
        <br>
    </div>

    <button type="button" class="btn btn-secondary" onclick="location.href='/logout'">로그아웃</button>
</div> <!--로그아웃 버튼 추가 -->

<script src="/js/article.js"></script>
</body>

 

실행 테스트를 해보겠습니다.

서버를 실행하고 http://localhost:8080/articles에 접속합니다.

 

articles는 인증된 사용자만 들어갈 수 있는 페이지이기에 /login 으로 리다이렉트됩니다.

 

아직 아이디가 없기에 회원가입 버튼을 눌러 회원가입을 합니다.

 

 

Submit 버튼을 누르면 회원가입이 된후 다시 /login으로 리다이렉트됩니다.

DB를 확인해볼까요??

 

패스워드 인코딩을 통한 패스워드 암호화까지 정상적으로 된 상태로

회원가입이 된걸 확인할수있습니다.

 

회원가입이 됐으니 다시 로그인 시도를 해보면 정상적으로

/articles에 접근할수있는걸 확인할수있습니다.

 

 

왼쪽 하단에 추가했던 로그아웃 버튼을 클릭하여

제대로 로그아웃이 되는지 확인하겠습니다.

 

 

로그아웃 버튼을 누르니 다시 로그인 페이지로 이동하는걸 확인할수있었습니다.

 

다음 포스팅은 JWT를 이용한 로그인/로그아웃 구현을 해보겠습니다.

'Spring boot' 카테고리의 다른 글

Spring Boot JWT TestCode  (1) 2024.01.06
Spring Boot JWT  (1) 2024.01.05
Spring Boot 각종 뷰  (1) 2023.12.31
Spring Boot 화면구성  (1) 2023.12.29
Spring Boot 수정 삭제  (0) 2023.12.28