Notice
Recent Posts
Recent Comments
«   2025/01   »
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
Archives
Today
Total
관리 메뉴

충분히 쌓여가는

스프링 시큐리티를 사용한 인증, 인가 본문

Spring/project

스프링 시큐리티를 사용한 인증, 인가

빌드이너프 2023. 12. 25. 12:15

의존성 추가

build.gradle

 

dependencies {  
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
    testImplementation 'org.springframework.security:spring-security-test'
}

 

엔티티 생성

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

 

 

src>main>java>com.enough.project에 domain 디렉터리 생성>User.java 생성

@Table(name = "users")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Entity
public class User implements 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", nullable = false)
    private String password;

    @Builder
    public User(String email, String password, String auth) {
        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;
    }

    @Override // 패스워드 만료 여부 반환
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override // 게정 사용 가능 여부 반환
    public boolean isEnabled() {
        return true;
    }
}

 

 

src>main>java>com.enough.project에 repository 디렉터리 생성>UserRepository.java 생성

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

 

 

src>main>java>com.enough.project에 service 디렉터리 생성> UserDetailService.java 생성

(로그인 진행 시 사용자 정보를 가져옴)

@RequiredArgsConstructor
@Service
public class UserDetailService implements UserDetailsService {

    private final UserRepository userRepository;

    @Override
    public User loadUserByUsername(String email) {
        return userRepository.findByEmail(email)
                .orElseThrow(() -> new IllegalArgumentException((email)));
    }
}

 

시큐리티 설정

src>main>java>com.enough.project에 config 패키지 생성>WebSecurityConfig.java 생성

@RequiredArgsConstructor
@Configuration
public class WebSecurityConfig {

    private final UserDetailService userService;

    @Bean
    public WebSecurityCustomizer configure() {
        return (web) -> web.ignoring()
                .requestMatchers(toH2Console())
                .requestMatchers("/static/**");
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
                .authorizeRequests()
                .requestMatchers("/login", "/signup", "/user").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .defaultSuccessUrl("/articles")
                .and()
                .logout()
                .logoutSuccessUrl("/login")
                .invalidateHttpSession(true)
                .and()
                .csrf().disable()
                .build();
    }

    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity http, BCryptPasswordEncoder bCryptPasswordEncoder, UserDetailService userDetailService) throws Exception {
        return http.getSharedObject(AuthenticationManagerBuilder.class)
                .userDetailsService(userService)
                .passwordEncoder(bCryptPasswordEncoder)
                .and()
                .build();
    }

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

 

 

회원가입 구현

서비스 메서드 코드 작성(사용자 정보를 담고 있는 객체)

src>main>java>com.enough.project에 dto 디렉터리 생성>AddUserRequest.java 생성

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

 

객체를 인수로 받는 회원 정보 추가 메서드 작성

src>main>java>com.enough.project>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(bCryptPasswordEncoder.encode(dto.getPassword()))
                .build()).getId();
    }
}

 

 

컨트롤러 작성

src>main>java>com.enough.project>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";
    }
}

 

회원가입, 로그인 뷰 작성

view 컨트롤러

src>main>java>com.enough.project>controller>UserViewController.java 생성

@Controller
public class UserViewController {

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

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

 

 

view 생성

src>main>resources>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>

 

src>main>resources>templates>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>

 

로그아웃 구현

src>main>java>com.enough.project>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";
    }

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

 

실행 테스트

src>main>resources>application.properties를 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:
    console:
      enabled: true

 

build.gradle에 h2 추가

dependencies {
    // ...
    runtimeOnly 'com.h2database:h2'
}

 

 

테스트 실행

서버 실행 후 localhost:8080 입력 -> redirect되어 http://localhost:8080/login 으로 이동됨

 

localhost:8080/signup 이동

 

회원가입 후 로그인 실행 시

 

확인 작업

localhost:8080/h2-console

 

로그아웃

localhost:8080/logout 입력 시 로그인 페이지로 이동됨

'Spring > project' 카테고리의 다른 글

글 작성 view 구현  (1) 2023.12.25
게시판 기능(CRUD)  (0) 2023.12.25
게시판 리스트 페이지 생성  (0) 2023.12.24
thymeleaf를 사용하여 레이아웃 적용  (0) 2023.12.24
프로젝트 생성 및 기본 index 페이지  (0) 2023.12.24