빙응의 공부 블로그

[Spring]스프링 시큐리티 - 간단한 실습 본문

Spring/개인공부_실습

[Spring]스프링 시큐리티 - 간단한 실습

빙응이 2024. 3. 24. 16:29

📝스프링 시큐리티 실습

목표 : 계정 역할 없이 로그인 창 구현 

  • 회원 가입을 위한 Validation도 포함 

 

생성 파일 

  • Config
    • SecurityConfig
  • controller
    • LoginController
  • domain
    • DTO
      • JoinDTO
    • Member
  • repository
    • MemberRepository
  • Service
    • LoginService
    • MemberService 
  • View
    • join.html
    • login.html
    • main.html
사용 의존성
- Spring Security
- Lombok
- Validation 
- Thymeleaf 
- MySQL 
- Jpa

🚩도메인 및 DTO 구성

Member
@Entity
@Getter
@Setter
public class Member {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  private String userName;

  private String password;
}

간단한 구성을 위해 아이디와 비번만 설정하였다.

 

JoinDTO
@Getter
@Setter
public class JoinDTO {
  @NotBlank(message = "이름은 필수 입력 값입니다.")
  @Length(min = 3,max = 16,message = "아이디를 8자 이상, 16자 이하로 입력해주세요")
  private String userName;

  @NotEmpty(message = "비밀번호는 필수 입력 값입니다.")
  @Length(min = 8,max = 16,message = "비밀번호는 8자 이상, 16자 이하로 입력해주세요")
  private String password;
}

회원가입 폼에서 사용할 DTO이며, Validation을 위한 제약조건도 설정하였다. 

MemberRepository
@Repository
public interface MemberRepository extends JpaRepository<Member,Long> {
  Member findByUserName(String userName);
}

JPA 접근을 위해 리포지토리 설정을 해주었다. 

🚩Spring Security 설정

밑에서 보는 것처럼 시큐리티 설정은 혼자 해보면서 하나하나 이해하는 것이 좋다. 

 

@Configuration
@EnableWebSecurity
public class SecurityConfig {
  @Bean
  PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder(); //보안을 위한 비밀번호 엔코더 
  }
  @Bean
  public SecurityFilterChain Security(HttpSecurity httpSecurity)throws Exception{
    httpSecurity
        .authorizeHttpRequests(authorizeRequests ->
            authorizeRequests
                .requestMatchers("/login","/","/loginProc","/join","/css/**").permitAll() //해당 위치는 접근 가능
                //.requestMatchers("/admin").hasRole("Admin") // 어드민 Role을 가지고 있는 사용자만 접근 가능
                .anyRequest().authenticated() // 나머지 모든 페이지는 인증 검사
        );
    httpSecurity
        .formLogin(login ->
            login
                .loginPage("/login")//로그인 페이지 설정
                .loginProcessingUrl("/loginProc")//시큐리티 로그인 처리 로직 url 지정
                .defaultSuccessUrl("/main",true)//로그인 인증 성공 시 이동할 페이지 
                .usernameParameter("userName")
                .passwordParameter("password")
                .permitAll()
        );
    httpSecurity
        .csrf((auth)->auth.disable()); //csrf 사용 거부 
    httpSecurity
        .logout(logout -> logout
            .logoutUrl("/logout")  // 로그아웃 URL 지정
            .logoutSuccessUrl("/loginPage")  // 로그아웃 성공 시 이동할 URL 지정
            .invalidateHttpSession(true)  // 세션 무효화
            .deleteCookies("JSESSIONID")// 쿠키 삭제
            .permitAll());
    return httpSecurity.build();
  }
}

 

🚩Service 구현 

@RequiredArgsConstructor
@Slf4j
@Transactional(readOnly = true) // 모든 메서드에 읽기 전용 트랜잭션 적용
public class MemberService {
  private final MemberRepository memberRepository;
  private final PasswordEncoder passwordEncoder;

  @Transactional // 쓰기 작업에 대한 트랜잭션 적용
  public void join(Member member){
    member.setPassword(passwordEncoder.encode(member.getPassword()));
    memberRepository.save(member);
  }

  /**
   * 회원가입 시 동일한 회원 아이디가 있는지 검사합니다.
   * @param userName
   * @return
   */
  public boolean checkDuplicateUsername(String userName){
    Member member = memberRepository.findByUserName(userName);
    return member != null;
  }
}

간단한 회원가입과 회원가입 시 아이디 중복 검사만 해주었다.

트랜잭션 효율화를 위해 읽기 전용으로 설정해 주었다. 

@Transactional(readOnly = true)

 

🚩Controller

@Controller
@Slf4j
@RequiredArgsConstructor
public class LoginController {

  private final MemberService memberService;

  @GetMapping("/login")
  public String loginPage(){
    return "login";
  }
  @GetMapping("/main")
  public String mainPage(Model model){
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    String userName = authentication.getName();
    model.addAttribute("userName", userName);
    return "main";
  }
  @GetMapping("/join")
  public String joinPage(Model model){
    model.addAttribute("joinDTO", new JoinDTO());
    return "join";
  }

  @PostMapping("/join")
  public String joinProc(@Valid @ModelAttribute JoinDTO joinDTO, BindingResult bindingResult){
    // 아이디 중복 체크
    if(memberService.checkDuplicateUsername(joinDTO.getUserName())) {
      bindingResult.reject("userName", "아이디가 중복됩니다.");
    }

    // 유효성 검사 및 오류 처리
    if (bindingResult.hasErrors()) {
      log.info("errors={}", bindingResult);
      return "join";
    }

    // 중복 아이디가 없는 경우 회원가입 처리
    Member member = new Member();
    member.setUserName(joinDTO.getUserName());
    member.setPassword(joinDTO.getPassword());
    memberService.join(member);

    return "redirect:/login"; // 로그인 페이지로 리다이렉트
  }
}

회원가입 시 컨트롤러를 이용해 유효성 검사(@Valid)와 아이디 중복 체크를 해주었다. 

만약 아이디가 중복되면 글로벌 오류로 등록하여 다시 join 페이지로 돌아가 오류를 표시하였다. 

 

 

 

 

Spring Security로 간단한 실습을 해보았다.

나는 정말 간단한 형태로 권한, 커스텀 정보 인증을 제외하였다.