chanhee01 2024. 1. 8. 20:09

SecurityConfig.class

@Configuration
@EnableWebSecurity // 스프링 시큐리티 필터가 스프링 필터체인에 등록이 된다.
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf(CsrfConfigurer::disable);
        http.authorizeHttpRequests(authorize ->
                authorize
                        .requestMatchers("/user/**").authenticated()
                        .requestMatchers("/manager/**").hasAnyRole("ADMIN", "MANAGER")
                        .requestMatchers("/admin/**").hasAnyRole("ADMIN")

                        .anyRequest().permitAll()
        );
        http.formLogin(formLogin -> formLogin
                .loginPage("/loginForm")
                .loginProcessingUrl("/login")
                // login 주소가 호출되면 시큐리티가 낚아채서 대신 로그인 진행
                .defaultSuccessUrl("/"));
                // 로그인 시 기존의 페이지로 접근
                // 만약에 로그인이 안된 상황에서 user에 접근하다 loginForm으로 튕겨지면
                // 다시 로그인 시 root 페이지가 아니라 user path로 접근

        return http.build();
    }
}

SecurityConfig에서 loginForm 페이지에서 로그인을 통해 login 페이지를 호출하면 스프링 시큐리티가 낚아채서 대신 로그인을 진행하는 부분을 추가했다.

 

 

시큐리티가 /login 주소 요청이 오면 낚아채서 로그인을 진행시켜주는데, 진행이 완료되면 시큐리티 session을 만들어준다. 이 때의 오브젝트 타입은 Authentication 타입 객체이다.

 

Authentication 안에 User의 정보가 있어야하는데, 이 때의 User 오브젝트 타입은 UserDetails 타입의 객체이다.

 

 

쉽게 말하면 시큐리티 세션이 있는데 여기 들어갈 수 있는 객체가 Authentication 객체이다.

이 객체 안에 user를 저장하는 것은 UserDetails 객체인 것이다.

 

나중에 꺼내서 사용할 때에 Session에서 Authentication 객체를 꺼내고, 그 안에서 UserDetails 객체를 꺼내면 user의 object에 접근할 수 있는 것이다.

 

 

PrincipalDetails.class

public class PrincipalDetails implements UserDetails {
    // UserDetails를 implements 했으니
    // Security Session => Authentication => UserDetails(PrincipalDetails)

    private User user;

    public PrincipalDetails(User user) {
        this.user = user;
    }

    // 해당 User의 권한을 리턴
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        // 해당 반환타입으로 변환을 위해 collect라는 ArrayList를 만들고 안에 user.getRole()을 넣기
        Collection<GrantedAuthority> collect = new ArrayList<>();
        collect.add(new GrantedAuthority() {
            @Override
            public String getAuthority() {
                return user.getRole();
            }
        });
        return collect;
    }

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

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

    // 계정이 만료되었으면 false
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    // 계정이 잠겨있으면 false
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    // 자격 증명이 만료되었으면 false
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    // 계정이 활성화되지 않았으면 false
    @Override
    public boolean isEnabled() {
        return true;
    }
}

위의 코드에서는 UserDetails를 implement 했으니 PrincipalDetails에서 user의 object에 접근할 수 있다.

 

 

 

 

PrincipalDetialsService.class

@Service
public class PrincipalDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User userEntity = userRepository.findByUsername(username);
        if(userEntity != null) {
            return new PrincipalDetails(userEntity);
        }
        return null;
    }
}

시큐리티 설정에서 loginProcessingUrl("/login"); 이라고 된 부분이 있다.

위의 코드는 login 요청이 오면 자동으로 UserDetailsService 타입으로 IoC 되어있는 loadUserByName 함수가 실행되는 것이다.

 

만약에 userEntity가 없으면 null을 반환하고 userEntity가 있다면 PrincipalDetials에 userEntity를 넣어서 반환해준다.

 

이 값들이 스프링 시큐리티의 session에 들어가는 것이다.

 

loginForm에서 로그인 버튼을 누르면 <form action="/login"method="POST">를 통해 /login이 요청이 되는데, 스프링 시큐리티는 UserDetailsSerivce가 implement 되어있는 PrincipalDetailsService를 찾아서 loadUserByUsername을 호출한다.

 

 

이러한 방식으로 로그인을 진행하면 세션에 user의 object가 담기는 것이다.