본문 바로가기

Proj/webClass

로그인 구현

1. SecurityConfig

 

@Configuration
@EnableWebSecurity
public class SecurityConfig {



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

        http
                .authorizeHttpRequests((auth) -> auth             //모든 경로에 대하여 권한을 부여->이후 수정 필요
/*                        .requestMatchers("/**").permitAll()*/
                        .anyRequest().authenticated())
                .formLogin(formLogin -> formLogin                   //로그인 페이지 지정
                        .loginProcessingUrl("/login")
                        .usernameParameter("userId")
                        .failureHandler(((request, response, exception) -> {
                            response.sendError(401);
                        }))
                        .permitAll())               //로그인 페이지는 누구나 접근 가능
                .logout(logout -> logout
                        .logoutUrl("/logout"))
                .csrf(auth -> auth.disable());          //개발 환경에서 임시적으로 비활성화




        return http.build();

    }

}

 

    authorizeHttpRequests((auth) -> auth.anyRequest().authenticated())

        - 모든 request들은 인증이 되어야 한다.

    /*requestMatchers("/**).permitAll()

        - 슬래시 아래 모든 경로로의 접근을 허용한다.

   

     formLogin(formLogin->formLogin...)

         - 폼 로그인 방식을 사용

    loginProcessingUrl("/login")    

        - 로그인 처리 url을 설정

    usernameParameter("userId")

        - 스프링 시큐리티에서는 기본적으로 id의 파라미터 명을 username으로 인식

        - 하지만 Member 엔티티에서는 userId를 로그인 파라미터로 사용

        - 따라서 해당 함수를 사용하여 파라미터 명을 설정한다.

    .failureHandler(((request, response, exception) -> {
        response.sendError(401);
    }).permitAll()

       - 로그인에 실패할 경우 401 에러를 전송한다.

       - 로그인폼은 모든 접근을 허가한다(로그인을 하기 위해 로그인이 필요하다면 모순)

 

    .logout(logout -> logout.logoutUrl("/logout"))

        - 로그아웃 url 설정

 

   .csrf(auth -> auth.disable())

        - 이 기능이 활성화되어있을 경우 request에는 csrf 토큰이 반드시 필요함

            *csrf(Cross Site Request Forgery): 사용자가 자신의 의도와 상관없이 공격자가 의도한 행위를 요청하게 하는 공격

        - 스프링 시큐리티에서는 기본적으로 이 기능이 활성화되어 있음

        - 개발 환경에서는 테스트의 편의를 위해 일시적으로 비활성화

        - 추후 배포 시에는 이를 제거하여 보안 기능을 활성화해야 함.

 

2. CustomUserDetailsService

/**
 * 스프링 시큐리티 인증 관련 서비스
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class CustomUserDetailsService implements UserDetailsService {

    private final MemberRepository memberRepository;

    @Override
    public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException {
        Member member = memberRepository.findByUserId(userId);

        if (member != null) {
            log.info("find member: {}", userId);
            return new MemberDetails(member);
        }
        log.info("find no member: {}", userId);
        return null;
    }

}

 

    - 로그인 요청이 들어올 경우 스프링 시큐리티는 UserDetailService.loadByUsername 함수를 이용하여 아이디에 해당          하는 유저 정보를 찾음

    - 따라서 해당 인터페이스를 구현해야 함

    - 해당 함수는 UserDetails를 반환해야 하므로 Member에 맞는 UserDetails 인터페이스를 구현해야 함.

 

3. MemberDetails

@RequiredArgsConstructor
public class MemberDetails implements UserDetails {

    private final Member member;

    //컬렉션에 해당 회원의 권한을 담아 반환
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> collections = new ArrayList<>();
        collections.add(()->{
            return member.getTeacherOrStudent().name();
        });


        return collections;
    }

    @Override
    public String getPassword() {
        return member.getPassword();
    }

    @Override
    public String getUsername() {
        return member.getUserId();
    }

    @Override
    public boolean isAccountNonExpired() {
        return LocalDate.now().isBefore(member.getLastLoginDate().plusMonths(3));
    }

    @Override
    public boolean isAccountNonLocked() {
        return LocalDate.now().isBefore(member.getLastLoginDate().plusYears(1));
    }

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

    @Override
    public boolean isEnabled() {
        return !member.isWithdraw();
    }
}

 

    - 스프링 시큐리티에서 해당 인터페이스(UserDetails)의 구현체를 반환하므로 이를 구현

    - 아래 함수들은 모두 UserDetails의 함수

    - 스프링 시큐리티는 이 정보들을 토대로 요청과 비교하여 권한 부여 여부를 판단

 

4. 테스트

    //테스트를 위한 임시 추가
    @PostConstruct
    public void init() {

        SignupBody signupBody = new SignupBody("nam", "theperz", "abcd1234",
        LocalDate.of(1999, 8, 3), Gender.M, 4, "이쪽", "상",
        "ckdgh7884@gmail.com", "01013243253", false, false);
        memberService.signUp(signupBody);

    }

 

 

   - 테스트를 위해 임시 데이터를 추가(userId: theperz, password: abcd1234)

 

로그인 요청

    - 데이터를 넣고 아이디와 비밀번호를 입력 후 요청 전송

    - formLogin 방식을 사용할 경우 x-www-form-urlencoded 방식만 사용가능

 

요청 결과

    - 정상적으로 JSESSIONID를 받는 것을 확인할 수 있음

    - 404에러는 페이지가 없어서 생긴 오류

 

 

참고) 컨트롤러 단에서의 로그인 구현은?

    - "/login" 경로로 POST 요청이 오면 스프링 시큐리티 내부적으로 UsernamPasswordAuthenticationFilter가 동작

    - 이때 AuthenticationProvider에 의해 CustomUserDetailsService의 loadUserByUsername을 호출하여 DB에 있는 유저를 조회

    - 위의 과정은 직접 커스텀하지 않아도 내부적으로 자동 등록되어 동작하기 때문에 우리가 직접 구현할 필요는 없다

'Proj > webClass' 카테고리의 다른 글

고객센터 Q&A crud 구현  (0) 2024.04.10
회원가입 개발 및 간단한 리팩토링  (0) 2024.03.28
Hello Spring Security  (1) 2024.03.27