[스프링 시큐리티] 시큐리티 로그인을 해보자!

코린이서현이·2024년 5월 26일
0
post-thumbnail

들어가면서

아! 졸리다!! 하루를 더 알차게 쓰려고 노력해보자!! 

로그인을 해보자.

먼저 우리는 시큐리티에게 보안을 위임한걸 기억하자! 즉, 시큐리티가 로그인까지 대신 해준다는 이야기다!!

어떻게 할까? : 시큐리티가 로그인을 하는 방법

ㅎ 여기저기서 참 많이 본 사진이죠? 

💡 시큐리티 또한 Service를 통해서 User(데이터)를 가져오고, 세션에 등록하는 과정을 거친다.
위 그림의 엄청 많은 단계는 대부분 시큐리티가 알아서 해준다! (따봉리티야 고마워~ 🦔)

하지만 이 유저가 정확한 유저인지, 우리의 회원리스트에 있는지!는 우리가 직접 구현해야한다. (따봉개발자야 고마워~ 🧑‍💻)

그렇게 우리가 "이 유저는 올바른 유저야~" 라고 user 정보를 전달해주면 시큐리티는 이 user를 세션에 등록해준다.

하지만 인생은 내마음대로 되는 것은 아니지! 😼 시큐리티가 요구하는 타입!에 맞춰서 우리는 값을 전달하고, 사용해야한다~

시큐리티는 우리에게 무엇을 요구하는가

  • 세션에 들어갈 내부 객체는 Authentication이다.
  • Authentication에 들어갈 내부 객체는 UserDetails이다.
  • 세션에 들어갈 내부 객체를 반환해주는 service(DAO)는 UserDetailsService이다!

🧑‍💻 그래서 우리가 해야할 일은?

  1. 우리가 사용하는 유저객체를 UserDetails타입(인터페이스)의 구현 클래스에 넣는다.
    UserDetails을 구현한 클래스를 보통 PrincipalDetails이라고 한다.
  1. UserDetails를 반환해주고, DB에 접근할 UserDetailsService 타입(인터페이스)의 클래스 로직을 짠다.
    UserDetailsService을 구현한 클래스를 보통 PrincipalDetailsService라고 한다. 이 클래스는 결과적으로 PrincipalDetails를 반환해야하고, 이 반환 메서드는 loadUserByUsername이다.

추가적으로!
시큐리티에게 로그인에 사용할 url, 로그인 페이지에 사용할 페이지, 로그인이 성공했을 때, 실패했을 때 이동할 url을 알려줘야한다.

그러면 우리가 해보자!!

config 설정하기

    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .csrf(csrf -> csrf.disable())
                .authorizeRequests((authorizeRequests) -> authorizeRequests
                                .requestMatchers("/user/**").authenticated()
                                .requestMatchers("/admin/**").hasRole("ADMIN")
                                .requestMatchers("/manager/**").hasAnyRole("ADMIN", "MANGER"))
                .formLogin(formLogin -> formLogin
                        .loginPage("/loginForm") //-> 사용자 정의 로그인 페이지
                        .loginProcessingUrl("/login") //이 주소가 호출 되면 시큐리티가 낚아채서 로그인 ㄱㄱ
                        .defaultSuccessUrl("/")); // -> 로그인 성공 후 이동 페이지



        return http.build();
        } 

UserDetails 구현 클래스 PrincipalDetails 만들기

package com.jsh.securitystudy.config.auth;

import com.jsh.securitystudy.model.User;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;

/*
시큐리티가 /login 주소 요청이 오면 낚아채서 로그인을 진행시킨다.
로그인 진행이 완료가 되면?? session을 만들어준다.
=> Security ContextHoder

이 Session에 들어갈 수 있는 타입이 정해져있다. 바로바로 ~!~!~!
    => Authentication 타입 객체
        => 이 객체 안에는 user정보를 담기위한 또 타입이 정해져있다..
            => 그게 바로 UserDetails 타입 객체

Security Session ->  Authentication -> UserDetails
 */
public class PrincipalDetails implements UserDetails {

    //여기임
    private User user;

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

    //해당 유저의 권한을 리턴하는 곳!
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        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();
    }

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

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

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

    @Override
    public boolean isEnabled() {
        //휴면 계정 사용할 때...
        return true;
    }
}

UserDetailsService 구현 클래스 PrincipalDetailsService 만들기

package com.jsh.securitystudy.config.auth;

import com.jsh.securitystudy.model.User;
import com.jsh.securitystudy.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

/*
시큐리티 설정에서 loginProcessingUrl("/login");
-> login 요청이 오면 자동으로 UserDetailsService 타입으로 IoC되어 있는 loadUserByUsername 실행~!!!
*/
@Service
public class PrincipalDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;


    //⭐PrincipalDetails를 반환해준다!!
    /*
    시큐리티 session = Authentication = UserDetails
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User userEntity = userRepository.findByUsername(username);

        System.out.println("username = " + username);
        System.out.println("userEntity = " + userEntity);

        if (userEntity != null) { //해당 유저가 존재할 때, 즉 로그인이 가능할 때
            return new PrincipalDetails(userEntity);
        }
        else {
            return null;
        }
        /*
        무엇을 return 해주죠? ??
        => PrincipalDetails를요!! 이것은 곧??? UserDetails

        이 메서드는 어디로 리턴할까??
        바로 Authentication의 내부로 리턴되어서 들어간다.

        Authentication이건 session으로 또 가고!!

         */

    }
}

결과 확인하기

먼저 회원가입을 해야하고, 로그인을 했을 때 의도대로 잘 움직이는 걸 확인했다.
아이디나 비밀번호를 틀리면 작동하지 않는 것도 확인!




만약 틀린거 하면 ?

작동 안된다~!

마무리하면서

아주아주 문제가 많은 코드다! 
테스트 코드도 작성하지 않았고, 에러 처리도 하지 않았다! 

차근차근하면 된다

참고 자료

https://taetoungs-branch.tistory.com/204

🌿 인프런의 최주호 강사님의 스프링부트 시큐리티 & JWT 강의를 참고하는 중
🔗 깃허브 링크

profile
24년도까지 프로젝트 두개를 마치고 25년에는 개발 팀장을 할 수 있는 실력이 되자!

0개의 댓글