<로그인/로그아웃 기능 구현>

1) UserDetailsService 인터페이스

- 데이터베이스 사용자 정보를 불러오는 인터페이스

- loadUserByUsername() 메서드를 가지고 있으며 UserDetails 반환

@Service
@Transactional
@RequiredArgsConstructor
public class MemberService implements UserDetailsService {

    private final MemberRepository memberRepository;
    // 의존성 주입, 회원 정보에 대한 CRUD 연산을 데이터베이스에 위임

    public Member saveMember(Member member) {   // 새 회원 저장 메서드
        validateDuplicateMember(member);        // 먼저 중복 검사 실행
        return memberRepository.save(member);
    }

    private void validateDuplicateMember(Member member) {
        Member findMember = memberRepository.findByEmail(member.getEmail());
        if (findMember != null) {
            throw new IllegalStateException("이미 가입된 회원입니다.");
        }
    }

    @Override       // UserDetailsService 인터페이스에서 선언된 loadUserByUsername 메서드 오버라이드
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException{
        // 이메일을 기반으로 회원 정보 조회
        Member member = memberRepository.findByEmail(email);

        // 사용자 존재 여부 검사
        if(member == null){
            throw new UsernameNotFoundException(email);
        }

        // UserDetails 생성 및 반환
        return User.builder()
                .username(member.getEmail())
                .password(member.getPassword())
                .roles(member.getRole().toString())
                .build();
    }
}

2) SecurityConfig 시큐리티 설정

- 기존 Spring Security 중 deprecated 된 것이 많음, 람다식으로 사용 권장

@Configuration      // 클래스가 Spring IoC 컨테이너에 의해 Bean 정의의 소스로 사용됨을 나타냄
@EnableWebSecurity  // Spring Security 설정을 활성화
public class SecurityConfig {

     @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .csrf(csrf -> csrf.disable())           // CSRF(크로스 사이트 요청 위조) 보호 기능을 비활성화
                .authorizeRequests(auth -> auth         // 요청에 대한 접근 제어 설정
                        .requestMatchers("/members/login", "/members/new").permitAll()       // 회원 가입 페이지는 인증되지 않은 사용자도 접근 가능
                        .anyRequest().authenticated()   // 그 외의 모든 요청은 인증된 사용자만 접근 가능
                )
                .formLogin(form -> form                 // 폼 로그인 방식 설정
                        .loginPage("/members/login")     // 사용자 정의 로그인 페이지의 URL을 지정
                        .defaultSuccessUrl("/")         // 로그인 성공 시 리다이렉트될 기본 URL
                        .usernameParameter("email")     // 로그인 폼에서 사용자 이름 대신 이메일 사용
                        .failureUrl("/members/login/error")     // 로그인 실패 시 리다이렉트될 URL
                )
                .logout(logout -> logout                // 로그아웃 설정 정의
                        .logoutRequestMatcher(new AntPathRequestMatcher("/members/logout")) // 로그아웃 URL
                        .logoutSuccessUrl("/")          // 로그아웃 성공 시 리다이렉트될 URL
                );
        return http.build();                            // HttpSecurity 구성을 빌드하여 SecurityFilterChain 객체를 반환

    }

    @Bean
    // 비밀번호를 안전하게 저장하고 검증하는 메커니즘을 제공하는 인터페이스
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(); // BCrypt 해싱 함수를 사용하여 비밀번호를 인코딩
    }
}

 

<페이지 권한 설정>

1) 상품 등록 페이지에 접근하는 컨트롤러 ItemController 구현

package com.ecproject.onlinestore.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class ItemController {
  
  @GetMapping("admin/item/new")   //관리자 접근만 가능
  public String itemForm() {
    return "/item/itemForm";
  }
}

2) CustomAuthenticationEntryPoint 클래스: AuthenticationEntryPoint 인터페이스 구현

- Spring Security에서 인증되지 않은 사용자가 보호된 리소스에 접근하려고 할 때 실행되는 로직 정의

- commence 메서드: 사용자에게 HTTP 401 Unauthorized 에러와 "Unauthorized"라는 메시지를 전달하도록 구현

package com.ecproject.onlinestore.config;

// import문 생략

public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

  @Override
  public void commence(HttpServletRequest request, HttpServletResponse response,
      AuthenticationException authException) throws IOException,
      ServletException {
    response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
  }
}

3) SecurityConfig 보완

  @Bean
  public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .csrf(csrf -> csrf.disable())           // CSRF(크로스 사이트 요청 위조) 보호 기능을 비활성화
        .authorizeRequests(auth -> auth         // 요청에 대한 접근 제어 설정
            .requestMatchers("/members/login", "/members/new")
            .permitAll()       // 회원 가입 페이지는 인증되지 않은 사용자도 접근 가능
            .requestMatchers("/admin/**")
            .hasRole("ADMIN")  // 관리자 페이지는 'ROLE_ADMIN' 권한을 가진 사용자만 접근 가능
            .anyRequest().authenticated()   // 그 외의 모든 요청은 인증된 사용자만 접근 가능
        )
        .exceptionHandling(e -> e
            .authenticationEntryPoint(new CustomAuthenticationEntryPoint())
        )	// 인증되지 않은 사용자가 보호된 리소스에 접근을 시도할 때
          // 'CustomAuthenticationEntryPoint' 내의 'commence' 메서드가 실행됨
        .formLogin(form -> form                 // 폼 로그인 방식 설정
            .loginPage("/members/login")     // 사용자 정의 로그인 페이지의 URL을 지정
            .defaultSuccessUrl("/")         // 로그인 성공 시 리다이렉트될 기본 URL
            .usernameParameter("email")     // 로그인 폼에서 사용자 이름 대신 이메일 사용
            .failureUrl("/members/login/error")     // 로그인 실패 시 리다이렉트될 URL
        )
        .logout(logout -> logout                // 로그아웃 설정 정의
            .logoutRequestMatcher(new AntPathRequestMatcher("/members/logout")) // 로그아웃 URL
            .logoutSuccessUrl("/")          // 로그아웃 성공 시 리다이렉트될 URL
        );
    return http.build();                            // HttpSecurity 구성을 빌드하여 SecurityFilterChain 객체를 반환
  }

 

+ Recent posts