[개발] - Spring/[개발예제] 시큐리티, JWT 입히기

시큐리티 설정, 로그인/로그아웃, 회원가입 구현

완벽한 장면 2023. 10. 17. 22:12

시큐리티 설정하기

WebSecurityConfig.java

// 시큐리티 설정하기
@RequiredArgsConstructor
@Configuration
public class WebSecurityConfig {

    private final UserDetailService userService;

	// 1. 스프링 시큐리티 기능 비활성화
    @Bean
    public WebSecurityCustomizer configure() {
        return (web) -> web.ignoring()
                .requestMatchers(toH2Console())
                .requestMatchers("/static/**");
    }
    
    // 2. 특정 HTTP 요청에 대한 웹 기반 보안 구성
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
                .authorizeRequests() //3. 인증, 인가 설정
                .requestMatchers("/login", "/signup", "/user").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin() //4. 폼 기반 로그인 설정
                .loginPage("/login")
                .defaultSuccessUrl("/articles")
                .and()
                .logout() //5. 로그아웃 설정
                .logoutSuccessUrl("/login")
                .invalidateHttpSession(true)
                .and()
                .csrf().disable() //6.csrf 비활성화
                .build();
    }

	// 7. 인증 관리자 관련 설정
    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity http, BCryptPasswordEncoder bCryptPasswordEncoder, UserDetailService userDetailService) throws Exception {
        return http.getSharedObject(AuthenticationManagerBuilder.class)
                .userDetailsService(userService)
                .passwordEncoder(bCryptPasswordEncoder)
                .and()
                .build();
    }

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

}

 

자세한 설명

은 스프링 시큐리티의 모든 기능을 사용하지 않게 설정하는 코드.

인증인가 서비스를 모든 곳에 모두 적용하지 않는다.

- 일반적으로 정적 리소스(이미지HTML 파일)에 설정한다.

- 정적 리소스만 스프링 시큐리티 사용을 비활성화하는 데 static 하위 경로에 있는 리소스와

h2의 데이터를 확인하는 데 사용하는 h2-console 하위 url을 대상으로 ignoring() 메서드를 사용한다.

 

는 특정 HTTP 요청에 대해 웹 기반 보안을 구성한다.

- 이 메서드에서 인증/인가 및 로그인로그아웃 관련 설정할 수 있다.

 

은 특정 경로에 대한 액세스 설정을 한다.

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

permitAll() : 누구나 접근이 가능하게 설정한다. , “/login”, “/signup”, “/user”로 요청이 오면 인증/인가 없이도 접근할 수 있다.

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

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

 

는 폼 기반 로그인 설정을 한다.

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

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

 

로그아웃 설정은 다음과 같다.

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

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

 

CSRF 설정을 비활성화한다.

CSRF 공격을 방지하기 위해서는 활성화하는 게 좋지만 실습을 편리하게 하기 위해 비활성화해 두었다. 여기서는.

 

인증 관리자 관련 설정이다.

사용자 정보를 가져올 서비스를 재정의하거나, 인증 방법, 예를 들어 LDAP, JDBC 기반 인증 등을 설정할 때 사용합니다.

 

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

userDetailsService() : 사용자 정보를 가져올 서비스를 서비스를 설정한다.

이 때 설정하는 서비스 클래스는 반드시 UserDetailsServiceS 상속받은 클래스여야 한다.

passwordEncoder(): 비밀번호를 암호화하기 위한 인코더를 설정합니다.

 

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

 

1. 회원 가입 구현하기

1-1. 서비스 코드 구현

1) DTO 설정. AddUserRequest.java

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

 

2) UserService 에 save() 메서드 구현

@RequiredArgsConstructor
@Service
public class UserService {

    private final UserRepository userRepository;

    public Long save(AddUserRequest dto) {
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

        // UserRepository를 통해 새 사용자를 저장.
        // - AddUserRequest에서 전달된 이메일과 비밀번호를 사용하여 새 사용자를 생성.
        // - 비밀번호는 BCryptPasswordEncoder를 사용하여 안전하게 해싱됨
        return userRepository.save(User.builder()
                .email(dto.getEmail())
                .password(encoder.encode(dto.getPassword()))
                .build()).getId(); // 저장된 사용자의 ID를 반환
    }
    /*
    이걸 리펙토링하면
    public Long save(AddUserRequest dto) {
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

        User user = userRepository.save(User.builder()
            .email(dto.getEmail())
            .password(encoder.encode(dto.getPassword()))  // 비밀번호 해싱
            .build());

        return user.getId();
    } 이렇게도 가능
     */

 

1-2. 컨트롤러 코드 구현

controller.UserApiController

@RequiredArgsConstructor
@Controller
public class UserApiController {

    private final UserService userService;

    @PostMapping("/user")
    public String signup(AddUserRequest request) {
        userService.save(request);
        return "redirect:/login";
    }

}

2. 회원 가입, 로그인 뷰 작성하기

2-1. 뷰 컨트롤러 구현

controller.UserviewController

@Controller
public class UserViewController {

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

 

2-2. 뷰 만들기

1) 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>

 

2) 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>

3. 로그아웃 구현하기

3-1. 컨트롤러 코드 추가

UserApiController.java

@RequiredArgsConstructor
@Controller
public class UserApiController {

    private final UserService userService;

    @PostMapping("/user")
    public String signup(AddUserRequest request) {
        userService.save(request);
        return "redirect:/login";
    }


	//추가
    
    @GetMapping("/logout")
    public String logout(HttpServletRequest request, HttpServletResponse response) {
        // SecurityContextLogoutHandler를 사용하여 사용자 로그아웃 처리를 수행
        new SecurityContextLogoutHandler()
                .logout(request, response, SecurityContextHolder.getContext().getAuthentication());

        // 로그아웃 처리 후, "/login" 경로로 리디렉션
        return "redirect:/login";
    }
}

 

3-2. 로그아웃 뷰 추가

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>

 

4. 실행 테스트하기

4-1. 환경 변수 설정하기

application.yml - 데이터베이스 연결 정보 추가

spring:
  jpa:
    show-sql: true
    properties:
      hibernate:
        format_sql: true
    defer-datasource-initialization: true
  // 데이터베이스 연결 정보 추가
  datasource: // 데이터베이스 정보 추가
    url: jdbc:h2:mem:testdb
    username: sa
  h2: // H2 콘솔 활성화
    console:
      enabled: true

 

 

4-2. 실제 실행 테스트하기

http://localhost:8080 만 쳐도 /login 으로 연결

1

회원가입 버튼을 누르면 화면 전환

 

회원가입 데이터 입력하고 가입 처리하면

3

로그인 화면으로 리다이렉트

4

 

h2 콘솔 접속해서 확인해보면

5

 

6

 

데이터베이스에도 문제 없이 잘 값이 들어와 있음을 확인할 수 있음.

로그인 버튼 누르면 글 목록으로 이동

7

 

로그아웃 버튼을 누르면

8

 

다시 메인 화면으로 이동함을 확인할 수 있음.

728x90
반응형