카테고리 없음

[TaskApp] Spring Security 로그인 구현 하기

MoveForward 2024. 6. 8. 16:27

Spring Security 에서 로그인을 구현하는 하려고 한다.

내가 가장 중요하게 생각 하는 부분은 "DB 에 있는 회원으로 로그인" 되어야 한다는 것이다.

 

Spring Security 는 로그인 처리에 대한 로직을 사용자가 직접 전통적인 방식으로 구현하지 않고, 이관 받아 처리하기 때문에, 어느 정보로 로그인 할 것인가? 에 대한 답을 주어야 한다.

 

0. 로그인 사전준비

 

[Member Entity]

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Member extends BaseEntity{

    /**
     * member_id(PK)
     * username
     * password
     * email
     * phone
     * userRole
     */

    @Id @GeneratedValue
    @Column(name = "member_id")
    private Long id;

    private String username;
    private String password;

    private String email;
    private String phone;

    private UserRole userRole = UserRole.USER;

    @OneToMany(mappedBy = "member")
    private List<Task> tasks = new ArrayList<>();
    
}

이와 같이 정의된 Member Entity 로 로그인 할 것이다.

 

 

[Test Data 주입]

 

 

[DB에 저장된 member]

정상적으로 DB에 저장된 것을 확인 할 수 있다.

 

[로그인 화면]

이와 같은 로그인 화면에

 

Username : member1

password : 12345678

 

Username : member2

password : 12345678

 

위 정보로 로그인이 가능해야 한다.

 

자, 사전 준비는 끝났다. 이제 DB에 저장된 정보로 로그인이 가능하도록 해보자.


1. Spring Security가 이해하도록 번역하기

"사용자의 인증 정보를 데이터베이스에서 조회하여 스프링 시큐리티가 이해할 수 있는 형태로 변환하는 역할" 을 하는 클래스를 구축해야 한다.

여기서 DB에서 조회하는 데이터는 " DB 에 저장된 회원(Member)" 정보 이다.

 

CustomUserDetailsService.class 를 구축해보자.

 

[ CustomUserDetailsService.class ]

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired private MemberRepository memberRepository;

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Member member = memberRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("User not found with name: " + username));

        return new User(member.getUsername(), member.getPassword(), getAuthorities(member));
    }

    private Collection<? extends GrantedAuthority> getAuthorities(Member member) {
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority("ROLE_" + member.getUserRole().name()));
        return authorities;
    }
}

loadUserByUsername 메서드는 사용자의 이름(username)을 파라미터로 받아 Spring Security 의 User 클래스의 인스턴스를 생성하여 반환 한다. 이 객체는 사용자 이름(username), 비밀번호(password), 권한 목록 을 포함한다.

 

getAuthorities 메서드는 사용자(member) 를 파라미터로 받아 해당 사용자가 가지고 있는 권한 목록을 반환한다.

권한 명 앞에 "ROLE_" 을 붙이는데, 이는 Spring Security 가 일반적으로 사용하는 권한 관리 방식을 따른 것이다.

 

이를 통해 Spring Security 가 DB 에 있는 Member 정보를 이해할 수 있는 형태로 변환 하였다.

 

 


2. 로그인 방식 설정하기

로그인 방식을 설정해야 한다. 여기서 말하는 로그인 방식은 방금 구축한 CustomUserDetailsService.class 를 통해,

"DB 에 저장된 Member 데이터로 로그인 할꺼야" 라는 것을 Spring Security 에게 알려 주는 것이다.

@Autowired private CustomUserDetailsService customUserDetailsService;

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

        http
                .authorizeHttpRequests((authorizeRequests) ->
                        authorizeRequests
                                .requestMatchers("/", "/members/login", "/members/join").permitAll()
                                .anyRequest().authenticated()
                )
                .formLogin((form) ->
                        form
                                .usernameParameter("username")
                                .passwordParameter("password")
                                .loginPage("/members/login")
                                .loginProcessingUrl("/login")
                                .defaultSuccessUrl("/", true)
                                .permitAll()
                )
                .userDetailsService(customUserDetailsService)
                .logout(logout ->
                        logout
                                .logoutUrl("/logout") //로그아웃 처리 URL
                                .logoutSuccessUrl("/") //로그아웃 성공 후 리다이렉트 할 URL
                                .invalidateHttpSession(true)
                                .deleteCookies("JSESSIONID")
                                .permitAll()
                )
                .csrf(csrf ->
                        csrf
                                .ignoringRequestMatchers("/api/**") // /api/** 경로에 대한 CSRF 보호를 비활성화
                )
        ;

        return http.build();
    }

.formLogin 메서드 바로 뒤에 있는

.userDetailsService 메서드에 customUserDetailsService 를 입력하여, 로그인 방식을 설정 한다.

 

로그인에 성공한 후 "/" 로 리다이렉트 된다.

 

※ filterChain 에 대한 자세한 설명은 다음 글을 통해 정리하겠다.


3. 로그인 해보기

 

[username : member1 , password : 12345678 로그인]

로그인에 성공한 모습을 볼 수 있다.

 

 

[username : member2 , password : 12345678 로그인]

로그인에 성공한 모습을 볼 수 있다.

 

 

[username : member777 , password : 12345678 로그인]

로그인에 실패한 모습을 볼 수 있다. (/members/login?error)