본문 바로가기
Spring Boot

[Spring Boot] Spring Security를 활용해서 회원가입 / 로그인 서비스 만들기

by ZIAHO 2024. 7. 16.

안녕하세요

이번시간에는 Spring Boot가 제공하는 Spring Security를 활용하여 회원가입 및 로그인 서비스를 구현해보도록 하겠습니다.

Spring Security에 대해서는 추후 다른 게시글로 자세하게 다뤄보도록 하겠습니다.


그래도 간략하게 Spring Security에 대해서 알아보고 넘어가겠습니다.

Spring Security란?

인증, 권한관리, 데이터 보호 기능을 포함하여 웹 개발 과정에서 필수적인 사용자 관리 기능을 구현하는데 도움을 주는 Spring의 프레임워크입니다.

 

📄 Spring Security 적용하기

Spring Security를 사용하기위해 build.gradle에 의존성을 추가해줍니다.

implementation 'org.springframework.boot:spring-boot-starter-security'	// Spring Security 사용하기 위한 스타터
testImplementation 'org.springframework.security:spring-security-test'	// Spring Security 테스트를 위한 의존성 추가

 

📄 Spring Security Config

Spirng Security를 설정하기 위한 config 파일을 만들어줍니다.

Spring Security 5.7.0-M2 버전부터는 component-based security configuration 을 권장하기 위해, 기존에 사용했던 WebSecurityConfigurerAdapter 라는 추상 클래스가 *deprecated 상태로 되었으며 대신 SecurityFilterChain 을 등록하도록 업데이트되었습니다.

이에 따라, WebSecurityConfigurerAdapter를 상속받아 메소드를 재정의하는 방식에서 SecurityFilterChain 클래스를 반환하는 메소드와 비밀번호 암호화에 필요한 BCryptPasswordEncoder 클래스를 Bean으로 등록하는 방식으로 진행하였습니다.

 

SecurityConfig.java

@Configuration
@RequiredArgsConstructor
@EnableWebSecurity
public class SecurityConfig {

  @Bean
  public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

    http
        .csrf((csrfConfig) ->
            csrfConfig.disable()
        )
        .authorizeHttpRequests((authorizeRequests ->
            authorizeRequests
                .requestMatchers("/", "/member/**").permitAll()
                .requestMatchers("/").permitAll()
        ));

    http
        .formLogin(login -> login
          .loginPage("/") // 로그인 default 페이지
          .loginProcessingUrl("/member/login")
          .usernameParameter("email")
          .passwordParameter("password")
          .defaultSuccessUrl("/member/successLogin") // 로그인 성공시
          .failureForwardUrl("/member/failureLogin") // 로그인 실패시
          .permitAll()
        );

    return http.build();
  }

  // 패스워드 암호화로 사용할 bean
  @Bean
  public PasswordEncoder getPasswordEncoder() {
	return new BCryptPasswordEncoder();
  }

}

 

  • crsf : Cross-site request forgery 사이트간 요청 위조를 막아주는 기능입니다. (프로젝트 테스트를 위해 임시로 disable 설정하였습니다.
  • authorizeRequest() : 인증, 인가가 필요한 URL을 지정합니다.
  • antMatchers(URL)
    • authenticated() : 해당 URL에 진입하기 위해서 Authentication(인증, 로그인)이 필요합니다.
    • hasAuthority() : 해당 URL에 진입하기 위해서 Authorization(인가, ex)권한이 ADMIN인 유저만 진입 가능)이 필요합니다.
    • URL에 ** 사용 : ** 위치에 어떤 값이 들어와도 적용시킵니다.
    • antMatchers(HttpMethod.POST, URL) : 이런 식으로 특정 HttpMethod만 검사 할 수도 있습니다.
  • anyRequest() : 그 외의 모든 URL
    • permitAll() : Authentication, Authorization 필요 없이 통과
  • formLogin() : Form Login 방식 적용
    • usernameParameter() : 로그인할 때 사용되는 ID를 적어줌 (해당 프로젝트에서는 email을 id값으로 받을거기 때문에 설정해주었습니다)
    • passwordParameter() : 로그인할 때 사용되는 password를 적어줍니다.
    • loginPage() : 로그인 페이지 URL
    • defaultSuccessURL() : 로그인 성공 시 이동할 URL
    • failureURL() : 로그인 실패 시 이동할 URL
    • logout() : 로그아웃에 대한 정보

 

📄 DTO 생성

MemberDto.java

@Data
@Getter @Setter
@NoArgsConstructor
@AllArgsConstructor
public class MemberDto {

  private String email;
  private String password;
  private String auth; // 권한 Role
  private int enable;

}

 

 

📄 Controller

MemberController.java

@Slf4j
@Controller
public class MemberController {

  private final MemberService memberService;

  public MemberController(MemberService memberService) {
    this.memberService = memberService;
  }

  // register화면 이동
  @GetMapping("/member/register")
  public String register() {
    return "member/register";
  }

  // 로그인 성공 화면 이동
  @PostMapping("/member/successLogin")
  public String successLogin() {
    return "member/successLogin";
  }

  // 로그인 실패 화면 이동
  @PostMapping("/member/failureLogin")
  public String failureLogin() {
    return "error/loginError";
  }

  // 회원가입
  @PostMapping("/member/insertMember")
  public String insertMember(MemberDto memberDto) {
    log.info("insertMember ::: {}", memberDto);
    int res = memberService.insertMember(memberDto);
    log.info("res ::: {}", res);
    if (res == 1) {
      return "redirect:/index";
    } else {
      return "redirect:/error/registerError";
    }
  }

 

config에서 설정했던 로그인 성공/실패 시 화면을 성공, 실패 화면으로 이동시켜주는 기능을 추가해주었습니다.

 

 

📄 Service

MemberService.java

@Service
public class MemberService {

  private final MemberMapper memberMapper;

  @Autowired
  private PasswordEncoder passwordEncoder;

  @Autowired
  public MemberService(MemberMapper memberMapper) {
    this.memberMapper = memberMapper;
  }

  public int insertMember(MemberDto memberDto) {
    // 패스워드 암호화
    memberDto.setPassword(passwordEncoder.encode(memberDto.getPassword()));

    return memberMapper.insertMember(memberDto);
  }

 

Spring Security는 회원가입시 반드시 패스워드가 암호화되어야 하기 때문에,

config에서 암호화를 사용하기 위해 등록했던 encoder를 활용해 패스워드를 암호화합니다.

 

📄 Mapper

MemberMapper.java

@Mapper
public interface MemberMapper {

  int insertMember(MemberDto memberDto);

  CustomMemberDetails selectMemberByEamail(String email);

}

 

MemberMapper.xml

  <select id="selectMemberByEamail" parameterType="String" resultType="wt.worktiger.security.CustomMemberDetails">
    SELECT  *
    FROM    MEMBER
    WHERE   EMAIL = #{email}
  </select>

 

📄 UserDetails, UserDetailsService

로그인 기능을 구현하기 위해 Spring Secrutiy에 기본적으로 탑재된 UserDetails와 UserDetailsService를 커스텀하여 구현합니다.

 

CustomMemeberDetails.java

@RequiredArgsConstructor
public class CustomMemberDetails implements UserDetails {

  private String email;
  @Setter
  private String password;
  private String auth;
  private int enabled;

  @Override
  public Collection<? extends GrantedAuthority> getAuthorities() {
    ArrayList<GrantedAuthority> authList = new ArrayList<GrantedAuthority>();
    authList.add(new SimpleGrantedAuthority(auth));
    return authList;
  }

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

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

  @Override
  public boolean isAccountNonExpired() {
    return true;
  }

  @Override
  public boolean isAccountNonLocked() {
    return true;
  }

  @Override
  public boolean isCredentialsNonExpired() {
    return true;
  }

  @Override
  public boolean isEnabled() {
    return enabled == 1;
  }

}

 

CustomMemberDetailsService.java

@RequiredArgsConstructor
@Service
public class CustomMemberDetailsService implements UserDetailsService {

  private final MemberMapper memberMapper;
  private BCryptPasswordEncoder passwordEncoder;

  @Override
  public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
    CustomMemberDetails members = memberMapper.selectMemberByEamail(email);
    if(members == null) {
      throw new UsernameNotFoundException("email " + email + " not found");
    }
    System.out.println("**************Found user***************");
    System.out.println("   email : " + members.getUsername());
    System.out.println("***************************************");

    return members;
  }

}

 

로그인 프로세스 진행 시 Spring Security의 loadUserByUsername 이라는 메소드가 프로세스를 가로채 입력받은 id 값으로 해당 유저의 정보를 조회하고 로그인을 진행합니다.


 

해당 프로젝트를 기존 MyBatis를 사용하는것에서 JPA를 사용하는것으로 변경되어 변경 전 구현했던 내용들을 위주로 간략하게 작성하였습니다.

추후 JPA를 사용하는 버전으로 수정한 뒤 업데이트 예정입니다.

 

추가로 이렇게 구현했을때 로그인이 계속 실패만 뜨고있어서 해당 부분도 수정할 예정입니다.


출처

https://velog.io/@kyu0/Spring-Security-%EB%A1%9C-%ED%9A%8C%EC%9B%90-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0

 

Spring Security 로 회원가입, 로그인 기능 구현하기

Spring Security 로 회원가입, 로그인 기능 구현하기

velog.io

 

https://chb2005.tistory.com/176

 

[Spring Boot] Spring Security를 사용한 로그인 구현 (Form Login)

Spring Security 란? Spring에서 제공하는 애플리케이션의 보안 (권한, 인증, 인가 등)을 담당하는 프레임워크 Spring Security는 인증과 권한에 대한 부분을 Filter의 흐름에 따라 처리 [Spring Boot] Session을 사

chb2005.tistory.com