SpringSecurity(2-3. 로그인 예시)

Walker·2021년 7월 20일
0

SpringSecurity

목록 보기
4/4
post-thumbnail

지난 글이 추상적인 부분이 있어 실제 Code 흐름을 따라가며
SpringSecurity를 사용한 로그인을 이해해보고자 한다.

위와 같이 Root로 접근해도 아래와 같이 /login.do로 url이 바뀌며
로그인 페이지로 넘어가게 되는데

이유는 SpringSecurity의 configure 설정 때문이다.

저번 편에서 말한 것과 같이 SpringSecurity의 SecurityContext
인증 객체가 담기지 않은 상태는 모두 미인증 상태로 취급되고
위에 설정한 /login.do로 이동하게 된다.

유저의 정보를 입력하고 로그인을 하게되면
다음은 loginProcessingUrl() 과정을 거치게 되는데
여기서의 URL("/loginCheck.do")은 컨트롤러를 향한 요청이 아니다.

로그인 과정에서 실제로 다음에 동작하는 것은 AuthenticationManager
AuthenticationProvider를 생성하는데
여기서는 커스텀으로 만든 AuthenticationProvider를 사용하고자 한다.

위에서 호출한 CustomAuthenticationProvider는 비밀번호 Encoder를 포함하는데
이는 입력받은 사용자의 비밀번호DB상의 비밀번호를 대조 할 때 사용된다.

CustomAuthenticationProvider에서는 authenticate() 메소드를 실행하며
UsernamePasswordAuthenticationToken을 받아오는데
이 토큰안에는 우리가 로그인 폼에서 입력받은 아이디/비밀번호 정보는 들어있지만
아직 인증을 받지 못한 상태(authentication의 authenticated 필드가 false인 상태)이다.

이후 사용자의 다른 정보들(아이디/패스워드 외)이 필요할 경우
loadUserByUsername() 메소드를 활용하여 DB를 조회한다.

loadUserByUsername()의 경우 SpringSecurity의 인터페이스 중 하나인
UserDetailService를 상속받은 후 사용하면 된다.
(굳이 이렇게 상속받지 않고 따로 서비스를 만들어서 DB를 조회하면 안되려나?)

일단 그렇게 많은 정보를 가져오지는 않았지만 그 중에서 권한 정보(user_auth)를 가지고 왔다.


이러한 DB의 조회 결과를 가져오는 User 클래스UserDetails 인터페이스를 상속 받는데
이는 SpringSecurity의 인터페이스이다.

위의 메소드는 UserDetails 인터페이스에서 오버라이드한 메소드들로
UserDetails 인터페이스를 굳이 상속 받아야하는 이유는 SpringSecurity에게
'나 이 클래스를 사용자 정보로 사용할테니 이 클래스 안의 정보를 인증/인가시에 사용해'라고
지정 해주는 것이라 할 수 있다.

DB에서 사용자 정보를 가져온 후 String 형태의 인가(권한) 정보를 다시 UsernamePasswordAuthenticationToken의 파라미터 형태인
Collection<? extends GrantedAuthority>으로 바꾸어 넣어준다.
(더 좋은 방법이 없을까??)

이후 입력받은 비밀번호(encoder로 암호화)와 DB에서 가져온 비밀번호(암호화 된)
비교하여 불일치시 BadCredentialsException을 발생시키고
일치시에는 UsernamePasswordAuthenticationToken 객체를 새롭게 생성한다.

위와 같이 UsernamePasswordAuthenticationToken 객체를 생성하며
authenticated 필드를 true로 바꾸어 만들어진다.

UsernamePasswordAuthenticationToken 객체의
authenticated 정보가 true라는 것은 Authentication 객체가 true라는 것과 같은데 이유는 UsernamePasswordAuthenticationToken이 Authentication의 구현체이기 때문이다.

인증 객체가 만들어진 이후에는 defaultSuccessUrl()로 이동하게 되고
successHandler()가 작동하게 된다.

successHandler()에서는 인증 객체(id, pw, auth)SecurityContext에 담게되고
이후 만약 로그인 Log 같은 것을 DB에 남기고 싶다면 이 단계에서 진행하는 것이 적합하다.

만약 실패했을 경우라면 failureHandler()를 사용하게 되며
여기에서 로그인 시도 Count 정보실패 log 정보를 갱신하면 된다.

로그인이 되었다는 것은 인증이 되었다는 것이고 다음은 인가(권한)에 대해서 살펴보자.

현재 DB 상에는 위와 같이 User 테이블Admin 권한만 컬럼으로 저장되어 있는 상황이고
권한을 별도의 테이블로 분리하지는 않았다.

아까 위에서 본 것과 같이 DB에서 가져온 권한을 인증 객체에 넣어서 생성한 후

configure에서 특정 url 접근시권한을 확인(hasAnyAuthority())하도록 하면
권한이 없는 경우 403 에러가 발생하게 된다.

위와 같이 각 컨트롤러의 메소드마다 설정하는 것도 가능하나
전체적인 관리를 위해서 config에서 설정하는 것이 편리 할 것 같다.

일단은 인증과 인가를 우당탕탕 구현했는데 더 좋은 방법이 있을 것 같다...
다음에 더 공부하게 된다면 로그아웃 부분과 csrf 토큰 부분도 추가!

profile
I walk slowly, but I never walk backward. -Abraham Lincoln-

0개의 댓글