본문 바로가기
SpringSecurity

[4강] Security 로그인

by 서영선 2023. 7. 26.

📙 지난 시간

 

formLogin.loginPage("/loginForm") 을 통해, 권한이 필요한 url 요청시 loginForm으로 이동하도록 했다.

 

아래 예시에서는 .authorizeRequests.requestMatchers("/user/**").authenticated(); 이므로, /user/**로 요청되는 주소는 loginForm으로 이동한다.

/admin/** 이나 /manager/**의 경우에는 Role확인이 필요하므로 403 에러가 뜬다. 

 

 @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
                .csrf(AbstractHttpConfigurer::disable)               // 사이트 위변조 요청 방지
                .authorizeHttpRequests((authorizeRequests) -> {      // 특정 URL에 대한 권한 설정.
                    authorizeRequests.requestMatchers("/user/**").authenticated();
                    authorizeRequests.requestMatchers("/manager/**")
                            .hasAnyRole("ADMIN", "MANAGER");   // ROLE_은 붙이면 안 된다. hasAnyRole()을 사용할 때 자동으로 ROLE_이 붙기 때문이다.
                    authorizeRequests.requestMatchers("/admin/**")
                            .hasRole("ADMIN");                       // ROLE_은 붙이면 안 된다. hasRole()을 사용할 때 자동으로 ROLE_이 붙기 때문이다.
                    authorizeRequests.anyRequest().permitAll();
                })

                .formLogin((formLogin) -> {
                    formLogin
                            .loginPage("/loginForm")        // 권한이 필요한 요청은 해당 url로 리다이렉트
                            .loginProcessingUrl("/login")   // login 주소가 호출되면 시큐리티가 낚아채서 대신 로그인을 해준다.
                            .defaultSuccessUrl("/");        //로그인 성공시 /주소로 이동

                })
                .build();
    }

 


📒 시큐리티 로그인 구현

 

이제, formLogin에 loginProcessingUrl과 defaultSuccessUrl을 추가해보자.

 

  • loginProcessingUrl("/login") : "/login" 주소가 호출되면 스프링 시큐리티가 낚아채서 대신 로그인을 진행해준다.
  • defaultSuccessUrl("/") : "/loginForm" 주소 요청해서 로그인 성공 시, "/" 주소로 이동한다. ( 단, "/user"와 같이 다른 주소를 요청했는데 loginForm으로 리다이렉트 된 경우에는 원래 요청했던 "/user" 주소로 이동한다. )

 

 

 

🧨 시큐리티 로그인 진행과정은 다음과 같다.

  1. /login 주소 요청이 와서 시큐리티가 낚아채서 로그인을 진행한다.
  2. 완료되면 시큐리티가 시큐리티 session을 만들어준다.
  3. 시큐리티 세션 안의 정보를 이용해 회원의 정보를 가져온다. 시큐리티 Session ((내부 Authentication ( 내부 UserDetails ))

 

따라서, Security Session => Authentication => UserDetails => 유저 정보를 통해 정보를 가져온다.

 

 


 

먼저, UserDetails 에서 유저 정보를 가져오는 클래스인 PrincipalDetails 클래스를 만들어보자.

 

public class PrincipalDetails implements UserDetails {

    private User user;  // Composition

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

    // 해당 User의 권한을 리턴하는 곳
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> collect = new ArrayList<>();
        collect.add(new GrantedAuthority() {
            @Override
            public String getAuthority() {
                return user.getRole();
            }
        });
        return null;
    }

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

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

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

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

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

    @Override
    public boolean isEnabled() {
        // 우리 사이트에서 1년동안 회원이 로그인을 안하면 휴먼 계정으로 하기로 함
        // 현재 시간 - 로그인 한 시간 >= 1년 이면 false 반환
        return true;
    }
}

PrincipalDetails 클래스는 UserDetails 인터페이스를 실행하므로 UserDetails(PrincipalDetails)의 구조이다.

 

 

 

 

이제, Session에서 UserDetails를 반환하는 클래스인 PrincipalDetailsService를 만들어보자. 

@Service
public class PrincipalDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    // 시큐리티 session ((내부 Authentication (내부 UserDetails))
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username);
        if (user!= null){
            return new PrincipalDetails(user);
        }
        return null;
    }
}

login 요청이 오면 자동으로 UserDetailsService 타입으로 IoC 되어있는 클래스의 loadByUsername이 실행된다.

로그인의 경우, 시큐리티가 자동으로 실행되므로, UserDetailsService 타입으로 되어있는 클래스만 생성하면 완료! 😄

 

 

 

 


이제 실행해보면, 로그인도 잘 실행되는 것을 볼 수 있다!!!

댓글