스프링 스큐리티로 로그인/ 로그아웃, 회원가입 구현

심주흔·2023년 9월 18일
0
post-thumbnail

스프링 기반의 애플리케이션의 보안(인증. 인가)를 담당하는 스프링 하위 프레임워크인 스프링 시큐리티를 사용해서 회원가입, 로그인, 로그아웃 기능을 구현함.

🥎 회원 도메인 만들기

1. 의존성 추가하기

dependencies{
	 //스프링 시큐리티를 사용하기 위한 스타터 추가
    implementation 'org.springframework.boot:spring-boot-starter-security'
    
    // 타임리프에서 스프링 시큐리티를 사용하기 위한 의존성 추가
    implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
    
    // 스프링 시큐리티를 테스트 하기 위한 의존성 추가
    testImplementation 'org.springframework.security:spring-security-test'
}

2. Entity 만들기

domain 패키지에 User.java파일을 생성하고 UserDetails 클랙스를 상속하는 User 클래스를 만든다. UserDetails 클래스는 스프링 시큐리티에서 사용자의 인증 정보를 담아두는 인터페이스이다. 스프링 시큐리티에서 해당 객체를 통해 인증 정보를 가져오므로 필수 오버라이드 메서드가 있다는 특징이 있음

3. User 엔터티에 대한 리포지터리를 repository 디렉터리에 만들기

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

이메일은 사용자를 식별할 수 있다. > 사용자의 정보를 가져오기 위해서는 스프링 시큐리티가 이메일을 받아야 함. 스프링 데이터 JPA는 메서드 규칙에 맞춰 메서드를 선언하면 이름을 분석해 자동으로 쿼리를 생성해 줌.

<자주 사용하는 쿼리 메서드와 명명 규칙>

코드설명쿼리
findByName()"name"컬럼의 값 중 파라미터로 들어오는 값과 같은 데이터 반환...WHERE name =? 1
findByNameAndAge()파라미터로 들어오는 값 중 첫 번째 값은 "name"컬럼에서 조회하고, 두번쨰 값은 "age" 컬럼에서 조회한 데이터 반환...WHERE name =? 1 AND age =? 2
findByNameOrAge()파라미터로 들어오는 값 중 첫 번째 값이 "name"컬럼에서 조회되거나 두 번째 값이 "age"dㅔ서 조회되는 데이터 반환...WHERE name =? 1 Or age =? 2
findByAgeLessThan()"age"컬럼의 값 중 파라미터로 들어온 값보다 작은 데이터 반환...WHERE age <? 1
findByAgeFreaterThan()"age"컬럼의 값 중 파라미터로 들어온 값보다 큰 데이터 반환...WHERE age >? 1
findByName(Is)NULL"name"칼럼의 갑 중 ull인 데이터 반환...WHERE name IS NULL

4. 서비스 메소드 코드(스프링 시큐리티에서 로그인을 진행하 때 사용자 정보를 가져오는 코드) 작성

@RequiredArgsConstructor
@Service
// 스프링 시큐리티에서 사용자 정보를 가져오는 인터페이스
public class UserDetailService implements UserDetailsService {

    private final UserRepository userRepository;

//사용자 이름(email)으로 사용자의 정보를 가져오는 메서드
    @Override
    public User loadUserByUsername (String email) {
        return userRepository.findByEmail(email)
                 .orElseThrow(() -> new IllegalArgumentException((email)));
    }
}

스프링 시큐리티에서 사용자의 정보를 가져오는 UserDetailsService 인터페이스를 구현. 필수로 구현해야 하는 loadUserByUsername() 메서드를 오버라이딩해서 사용자 정보를 가져오는 로직을 작성한다.

5. 시큐리티 설정

Config 패키지를 새로 만들어서 WebSecurityConfig.java를 만들었다.

//스프링 시큐리티 기능 비활성화
   @Bean
    public WebSecurityCustomizer configure() {
        return (web) -> web.ignoring()
                .requestMatchers(toH2Console())
                .requestMatchers("/static/**");
    }

스프링 시큐리티의 모든 기능을 사용하지 않게 설정함. 인증, 인가 서비스를 모든 곳에 모두 적용하지 않고 일반적으로 정적 리소스(이미지, HTML파일)에 설정. 정적 리소스만 스프링 시큐리티 사용을 비활성화하는데 static 하위 리소스와 h2의 데이터를 확인하는데 사용하는 h2-console 하위 url을 대상으로 ignoring() 메서드를 사용한다.

//특정 HTTP 용청에 대해 웹 기반 보안을 구성함. 인증/인가 및 로그인, 로그아웃 관련 설정
    @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();
    }

requestMatchers(): 특정 요청과 일치하는 url에 대한 엑세를 설정
permitAll(): 인증/인가 없이 모든 접근 가능
anyRequest(): 위에서 설정한 url이외의 요쳥에 대해서 설정
authenticated(): 별도의 인가는 필요하지 않지만 인증이 접근할 수 있음.

loginPage(): 로그인 페이지 경로를 설정
defaultSuccessUrl(): 로그인이 완료 되었을 때 이동할 경로를 설정

logoutSuccessUrl(): 로그아웃이 완료되었을 때 이동할 경로를 설정
invalidateHttpSession(): 로그아웃 이후에 세션을 전체 삭제할지 여부를 설정

userDetailsService(): 사용자 정보를 가져올 서비스를 설정. 설정하는 서비스 클래스는 반드시 UserDetailsService를 상속받은 클래스이어야 함.
passwordEncoder(): 비밀번호를 암호화하기 위한 인코더 설정

🥎 회원가입 구현하기

1. 서비스 메서드 코드 작성하기

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

Dto 디렉터리에 Request파일을 추가함

@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();
    }
}

2. 컨트롤러 작성

@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";
    }

}

userService.save(request): 회원 가입 메서드 호출
return "redirect:/login: 회원 가입이 완료된 이후에 로그인 페이지로 이동

🥎 회원가입, 로그인 뷰 작성하기

1. 뷰 컨트롤러 구현하기

@Controller
public class UserViewController {

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

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

}

/login 경로로 접근하면 login() 메서드가 login.html을, /signup 경로에 접근하면 signup() 메서드는 signup.html을 반환

2. 뷰 작성하기

templates 디렉터리에 html 파일을 생성하여 html 코드를 작성한다.

🥎 로그아웃 구현

1. 로그아웃 메서드 추가

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

/logout GET 요청을 하면 로그아웃을 담당하는 핸들러인 SecurityContextLogooutHandler의 logout() 메서드를 호출해서 로그아웃

2.로그아웃 뷰 추가

해당 파일에 html(버튼 생성)을 추가

profile
이봐... 해보기는 했어?

0개의 댓글