Side-project : Spring Security + Session 로그인하기 (1)

우진·2023년 5월 17일
0
post-thumbnail

👾 Spring Security

https://docs.spring.io/spring-security/reference/index.html

Spring Security를 사용하기 전에 아래 내용을 간단히 숙지하면 좋다.

  • 인증(Authentication): 해당 사용자가 본인이 맞는지를 확인하는 절차
  • 인가(Authorization): 인증된 사용자가 요청한 자원에 접근 가능한지를 결정하는 절차
  • Principal(접근 주체): 보호받는 Resource에 접근하는 대상
  • Credential(비밀번호): Resource에 접근하는 대상의 비밀번호

👾 Session Login with Spring Security form

Spring Security에서는 form을 통한 로그인 인증 방식을 제공하고 있다. 나는 통합된 프로젝트로 개발중이기 때문에 가장 간단해 보이는 form 로그인을 시도하게 되었다.


(1) 📁 build.gradle Setting

먼저 Spring Security 의존성을 추가해야 한다.

implementation 'org.springframework.boot:spring-boot-starter-security'

(2) 📁 SecurityConfig Setting

여기서 좀 당황했다; ?(°Д°≡°Д°)? 이전 프로젝트에서는 5.7.0-M2 이하 버전을 사용했기 때문에 WebSecurityConfigurerAdapter를 상속받아 메소드 오버라이딩을 하면 됐는데... WebSecurityConfigurerAdapterDeprecated 됐다ㅋ.ㅋ

component-based한 코드 작성을 위해서라고 한다.

자세한 내용은 아래문서 ⬇ ⬇ ⬇에서
https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter

공식문서 훑어보고 서치해가며 어찌저찌 고칠 수 있었다. 휴;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    //해당 메서드의 리턴되는 오브젝트를 IOC로 등록해줌
    @Bean
    public BCryptPasswordEncoder encodePwd() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .cors().and()
                //TODO: CorsConfigurationSource 만들어서 등록

                .authorizeHttpRequests((authz) -> authz
                        .requestMatchers("/boards/**").authenticated()
                        .anyRequest().permitAll()
                )

                .formLogin()
                .loginPage("/login")
                .usernameParameter("email")
                .loginProcessingUrl("/login")
                .defaultSuccessUrl("/boards");

        return http.build();
    }

}
  • .requestMatchers(): 이전의 .antMatchers() 대신 사용하도록 되어있다.
  • .authenticated(): 해당 url로의 모든 요청은 인증을 요구한다.
  • .anyRequest().permitAll(): 이외의 모든 요청은 인증을 요구하지 않는다.
  • .formLogin(): form 로그인을 사용한다.
  • .loginPage(): 로그인 페이지를 지정할 수 있다.
  • .usernameParameter(): username(아이디)에 해당하는 input 태그의 파라미터 이름을 지정할 수 있다. 해당 설정을 생략하면 디폴트값은 "username"이다.
  • .loginProcessingUrl(): POST 메소드로 요청되는 로그인 url을 지정할 수 있다.
  • .defaultSuccessUrl(): 로그인이 성공하면 지정한 url로 이동된다.

(3) 📁 PrincipalDetails Setting

form 로그인은 Spring Security Session 기반 로그인이다. 인증된 사용자의 정보는 Security 내부의 자체적인 Session에서 관리된다.

  1. POST "/login" 을 낚아채 로그인 진행
  2. 로그인이 완료되면 시큐리티 자체 session을 만들어 저장 (key: Security ContextHolder)
  3. 오브젝트 타입 : Authentication
  4. Authentication 안에 User(UserDetails) 객체를 가짐
  5. Security Session -> Authentication -> UserDetails

그러니 Principal/Authentication 객체로 현재 인증된 사용자 정보를 꺼내 쓰려면 UserDetails 인터페이스를 상속받는 User 클래스가 있어야한다. 나는 User 대신 `PrincipalDetails`를 만들었다.
public class PrincipalDetails implements UserDetails {

    private User user;

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

    //사용자 권한 반환
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> collect = new ArrayList<>();
        collect.add(new GrantedAuthority() {
            @Override
            public String getAuthority() {
                return user.getUserRole();
            }
        });

        return collect;
    }

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

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

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

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

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

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

(4) 📁 PrincipalDetailsService Setting

  1. POST "/login" 요청이 오면 UserDetailsService(인터페이스) 타입으로 IoC되어 있는 loadUserByUsername 함수 실행
  2. 들어온 아이디값(username)을 가지고 존재하는 사용자인지 체크
  3. 사용자가 존재하면 User 정보를 담은 UserDetails 객체를 반환
@Service
public class PrincipalDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        User userEntity = userRepository.findByUserEmailId(username);

        if(userEntity != null) {
            return new PrincipalDetails(userEntity);
        }

        return null;
    }
}

(5) 📁 Create login.html

<!--  loginForm  -->
  <form th:action="@{/login}" method="POST">
    <p>로그인</p>
    <input name="email" type="text" placeholder="이메일을 입력해주세요" required>
    <input name="password" type="password" placeholder="비밀번호를 입력해주세요" required>
    
    <button type="submit" th:text="로그인"></button>
  </form>

필요한 태그는 위와 같다. input 태그의 name부분이 중요한데 꼭❗ 위와 같이 입력해야한다. 여기서 email은 SecurityConfig에서 .usernameParameter()로 미리 지정한 값이다(디폴트는 "username")


(6) 📁 Create UserController

@Controller
public class UserController {

	//로그인 페이지로 이동
    @GetMapping({"", "/", "/login"})
    public String loginPage() {

        return "html/login";
    }
}

단순히 로그인 페이지로 이동시켜주는 로직이다. POST요청은 직접 작성할 필요없이, Spring Security에서 알아서 처리해준다. 너무 편하다 ദ്ദി⑉¯ ꇴ ¯⑉ ) 굳 .!


(7) 📁 Use

profile
백 개발을 시작한 응애개발자

0개의 댓글