[Spring Security] 시큐리티 로그인

yesjm·2022년 12월 14일
0
post-thumbnail

인프런 스프링부트 시큐리티 & JWT 강의를 수강하며 작성하는 내용입니다.


소스코드
SecurityConfig

  • loginProcessingUrl: login 주소가 호출이 되면 시큐리티가 낚아채서 대신 로그인 진행해주기 때문에 login api가 필요없어졌다.
  • defaultSuccessUrl : login이 완료되면 이동하는 Url을 적어준다.
@Configuration
@EnableWebSecurity // 스프링 시큐리티 필터가 스프링 필터체인에 등록됨
class SecurityConfig {
    @Bean
    fun encodePwd(): BCryptPasswordEncoder {
        return BCryptPasswordEncoder()
    }

    @Bean
    @Throws(Exception::class)
    fun filterChain(http: HttpSecurity): SecurityFilterChain? {
        http
            .authorizeRequests { authz ->
                authz
                    .antMatchers("/user/**").authenticated()
                    .antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")
                    .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
                    .anyRequest().permitAll()
                    .and()
                    .formLogin()
                    .loginPage("/loginForm")
//                    .usernameParameter("userNickname") -> username 으로 파라미터 안받으려면 설정 필요
                    .loginProcessingUrl("/login") 
                    .defaultSuccessUrl("/")
            }
            .httpBasic(withDefaults())
            .cors().and()
            .csrf().disable()

        return http.build()
    }
}

loginForm

  • action="/login" method="POST" 설정을 추가하면 시큐리티가 낚아챌 수 있게 된다.
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>로그인 페이지</title>
</head>
<body>
  <h1>로그인 페이지</h1>
  <hr/>
  <form action="/login" method="POST">
    <input type="text" name="username" placeholder="Username"> <br/>
    <input type="password" name="password" placeholder="Password"> <br/>
    <button>로그인</button>
  </form>
<a href="/joinForm">회원가입을 아직 하지 않으셨나요?</a>
</body>
</html>

PrincipalDetails

  • UserDetails을 상속받아 오버라이딩 해준다.
  • 시큐리티가 /login 주소 요청이 오면 낚아채서 로그인 진행하는데, 로그인 진행이 완료되면 시큐리티 session을 만들어준다.
  • 세션과 공간은 같지만 시큐리티만의 세션 공간을 가짐 - (Security ContextHolder 키값을 담아 세션 정보를 저장함)
  • 오브젝트는 Authentication 타입 객체로 정해져 있고, Authentication 안에는 User 정보가 있어야 하고, User 오브젝트는 UserDetails 타입의 객체다.
    - Security Session => Authentication => UserDetails(PrincipalDetails)
class PrincipalDetails(
    private val user: User // 콤포지션
) : UserDetails {

    // 해당 User 의 권한을 리턴하는 곳
    override fun getAuthorities(): MutableCollection<out GrantedAuthority> {
        val authorities: MutableList<GrantedAuthority> = ArrayList()
        authorities.add(SimpleGrantedAuthority(user.role))
        return authorities
    }

    override fun getPassword(): String {
        return user.password.toString()
    }

    override fun getUsername(): String {
        return user.username.toString()
    }

    override fun isAccountNonExpired(): Boolean {
        return true
    }

    override fun isAccountNonLocked(): Boolean {
        return true
    }

    override fun isCredentialsNonExpired(): Boolean {
        return true
    }

    override fun isEnabled(): Boolean {
        return true
    }
}

PrincipalDetailsService

  • 시큐리티 설정에서 loginProcessingUrl("/login")을 해놓았기 때문에, /login 요청이 오면 자동으로 UserDetailsService 타입으로 IoC되어 있는 loadUserByUsername이 실행된다.
@Service
class PrincipalDetailsService(
    private val userRepository: UserRepository
) : UserDetailsService {

    @Throws(UsernameNotFoundException::class)
    override fun loadUserByUsername(username: String): UserDetails? {
        println("username: $username")
        val userEntity = userRepository.findByUsername(username)
        if (userEntity != null) {
            return PrincipalDetails(userEntity)
        }
        return null
    }
}


실행결과

  • username: user인 경우 role이 ROLE_USER이므로 .antMatchers("/user/**").authenticated() 설정에 의해
    user 페이지는 정상적으로 동작한다.
  • admin, manager 의 경우 ROLE_ADMIN or ROLE_MANAGER가 필요하기 때문에 403 에러가 뜨는 것을 확인할 수 있다.



profile
yesjm's second brain

0개의 댓글