티스토리 뷰

이번장에는 Spring Security에 대해 정리해보고자 한다.
Spring Security란 스프링에서 제공하는 인증, 권한, 보안 등을 제공해주는 강력한 프레임워크이다.
기존에 필자가 Spring으로 인증 및 권한체크를 구현했을땐 HttpSession과 Interceptor를 이용하여 구현한적 있었는데 이렇게 구 현해놓으니까 중복코드가 너무많이 발생하기도하고 유지보수도 힘든적이 있었다. 그래서 어떻게하면 이보다 편리하게 구현할 수 있을까 생각하다 Spring Security를 적용했었다.

 

Spring Security를 어느정도 알고있는 상태에서 사용하면 편하지만 필자처럼 무작정 모르고 도입했다가 멘탈이 나갈(?) 수 있으 니 이번장에서 자세히 알아보자.

 

Security 의존성 추가

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

Security를 적용하기 위해 해당 의존성을 추가하자.

Security Config 추가

 

Line 40 : 사용자의 요청중 /resources/로 시작하는 요청은 제외한다.
Line 46 : 사용자의 요청중 /main/에 해당하는 요청은 권한없이 누구든 접근이 가능하다.
Line 47 : 사용자의 요청중 /study/로 시작하는 요청은 인증된 사용자만 접근 할 수 있다.

Spring security에서 리소스별 접근권한을 정해줄때는 위에서부터 아래대로 순차적으로 적용된다.
만일 46 Line이 /**로 되있다면 모든경로의 접근을 허용하게되지만 47 Line에서 /study/로 시작하는 설정이 있으니 해당요청은 인증된 사용자만 접근가능하다.

Line 49 : csrf 속성을 disable 시킨다. security가 적용된 애플리케이션에서 POST 방식으로 요청시
csrf값을 전송해야 하는데 그러지 않기위해서 disable 시켜놓는다.

 

Line 52 : 해당url로 접근하면 로그인페이지로 이동하게된다.

loginPage 설정을 하지않으면 Security에서 제공해주는 default 로그인페이지가 나오게 된다

Line 53 : 사용자가 로그인화면에서 아이디/비밀번호를 입력후 전송버튼 클릭시 전송되는 url이다.
form태그의 action값과 매핑된다.

Line 54 / 55 : 로그인폼에 아이디와 비밀번호의 파라미터(name속성)명을 적어준다.
Line 56 : 로그인이 정상적으로 이뤄지게되면 동작되는 Handler를 등록한다.
Line 57 : 로그인이 정상적으로 이뤄지지않을때 동작되는 Handler를 등록한다.

 

Line 60 / 62 : /logout으로 요청이 오면 로그아웃을 시켜버리고 / 경로로 리다이렉트 해주며
SecurityContextHolder에 저장된 세션정보를 지운다.

 

Line 64 : 로그인 요청이 들어올 시 동작되는 Provider를 등록한다.
Line 69 : 사용자의 정보를 가져올 UserDetailsService를 등록한다.

루트경로 매핑 및 로그인 컨트롤러, 페이지 생성

 

좌측사진 : 사용자가 / 경로로 요청시 /login요청으로 리다이렉트 해준다.
중앙사진 : /login으로 요청시 들어오면 login페이지를 응답한다.
우측사진 : 사용자에게 보여질 login페이지를 생성한다.

구현클래스 생성

AuthenticationProvider 구현체 생성

 

로그인 요청시 AuthenticationProvider로 요청이 전달되며 authenticate() 메서드가 호출되며 인증받기전의 사용자의 정보가 authentication 객체에 담긴다.
supports() 메서드는 인자로 Class< ? > authentication을 받지만 실제로는 Class< ? extends Authentication> 이다.
즉, supports() 메서드가 authenticate() 메서드로 전달되는지에 대한 여부만 확인한다.
UsernamePasswordAuthenticationToken 클래스는 Authentication 인터페이스의 구현체이다.

AuthenticationSuccessHandler 구현체 생성

 

로그인이 정상적으로 동작하게되면 인증받은 사용자의 정보가 Authentication객체에 담기게 된다.

AuthenticationFailureHandler 구현체 생성

 

로그인이 정상적으로 동작되지않을때 어떠한 이유로 동작이 되지않았는지에 대한 예외가 AuthenticationException객체에 담긴다.

UserDetails 구현체 생성

 

사용자의 정보를 담고있는 VO 클래스이며 UserDetails를 구현한다.
Authentication의 구현체인 UsernamePasswordAuthenticationToken을 생성하기 위해 사용된다.

UserDetailsService 구현체 생성

 

사용자의 정보를 DB에서 조회하는 단 하나의 메소드 loadUserByUsername()를 오버라이딩한다. 해당 메서드의 리턴타입은 UserDetails 타입이다.

사용자 정보조회

 

사용자의 아이디를 인자로 받아서 DB에 사용자 정보를 조회한다.
등록된 사용자가 없다면 UsernameNotFoundException을 발생시킨다.

사용자 인증처리

사용자의 아이디로 DB에서 정보를 조회한다. 그리고 입력받은 비밀번호와 디비에 저장된 비밀번호(BCrypt 방식으로 해싱된)가맞지않으면 BadCredentialsException을 발생시킨다.
DB에서 얻어온 사용자의 권한을 부여한다. 이때 권한명에 'ROLE_'를 붙혀주자. 이건 시큐리티에서 default prefix로 정의해둔 권한값이다.
그리고 사용자의 정보로 UsernamePasswordAuthenticationToken을 생성한뒤 인증이 완료된 Authentication을 리턴한다.

입력받은 비밀번호는 어떻게 비교 할까?

 

똑같은 데이터를 암호화해도 BCrypt로 해싱을 하게되면 해싱과정에 salt값이 들어가기 때문에 암호화를 할때마다
다른값이 나온다. 평문데이터와 비교를 할때도 내부적으로 salt값을 이용해 비교하기 때문에 암호화된 데이터는 다르지만 암호화전 평문데이터로 비교하게 되도 true를 리턴한다.

로그인 성공 및 실패처리

 

로그인에 성공하게되면 /study/list로 리다이렉트를 하고 실패하게되면 어떤 익셉션이 발생되었는지 콘솔에 찍어보자.

테스트할 사용자 계정 생성

 

아이디 : test, 비밀번호 : 1234(해싱값), 권한 : USER를 부여한 테스트 계정을 생성하였다. 이 아이디로 테스트해보자.

테스트

 

아이디 test, 비밀번호 1234를 입력후 로그인을 요청한 뒤 사용자 존재여부 및 비밀번호 체크후 정상적으로 로그인이 되어 CustomSuccessHandler에 의해 /study/list로 이동하였다. 이제 두가지 실패케이스를 한번 만들어보자.

아이디를 잘못입력하기

 

비밀번호를 잘못입력하기

 

아이디가 없으면 UsernameNotFoundException, 비밀번호가 맞지않으면 BadCredentialsException을 발생되야하는게 맞는데 뭔가 동작이 정상적으로 되지않는 것 같다.

 

에러메세지를 출력해주는 DaoAuthenticationProvider의 136 Line, 323 Line에 가보자 한번 가보자.

 

 

Exception이 DaoAuthenticationProvider의 상위클래스인 AbstractUserDetailsAuthenticationProvider에서 발생되는걸 확인했다. Exception을 throw 했는데 CustomFailureHandler가 아닌 저기로 throw 되는걸까....설정파일을 확인해보니 필자가 다음과 같은 설정을 했었다.

해당설정 확인

위 사진의 설정코드는 지우도록 하자.

 

아이디, 비밀번호 재 테스트

 

정상적으로 테스트가 완료되었다. 

 

하지만 이런 방법은 추천해주고 싶지않다.

만일 시큐리티로 자동로그인을 구현할때 default로 설정된 userDetailsService를 조회해서 사용자 정보를 조회하는 코드가 있는데 해당설정(auth.userDetailsService)을 제거하면 NPE가 발생된다. 

 

그것보다는 위 사진 AbstractUserDetailsAuthenticationProvider의 아이디 또는 패스워드가 틀렸을 경우

DaoAuthenticationProvider나 AbstractUserDetailsAuthenticationProvider에서 예외메세지를 던지는데 MessageSourceAccessor객체의 getMessage() 메서드를 호출하여 해당 예외코드에 맞는 예외메세지를 가져오게끔 되있다.

만일 예외코드가 존재하지않으면 default message가 출력된다. 우리는 이 예외코드에 매핑되는 예외메세지를 설정하자

 

 

시큐리티 메세지 커스텀하기

아이디 또는 패스워드가 틀릴경우 MeesageSourceAccesor객체에 

'AbstractUserDetailsAuthenticationProvider.badCredentials' 라는 code로 매핑된 message값을 가져온다.

만일 등록된게 없다면 'Bad crecredentials' 라는 default Message를 던져준다.

 

 

 

이렇게 설정해두면 해당 code에 대한 예외메세지를 개발자가 직접 커스텀 할 수 있다.

MessageSource 설정하는법은 아래링크를 통해 확인할 수 있다.

 

kim-jong-hyun.tistory.com/26

 

14. Spring - MessageSource로 메세지 및 다국어 관리하기

이번장에는 Spring에서 제공해주는 MessageSource에 대해 알아보자. 웹개발을 하면서 화면단에 alert함수를 이용해 클라이언트에게 특정메세지를 보여줘야 할때가 많다. 이때 java에서 메세지값을 하드

kim-jong-hyun.tistory.com

 

 

아이디, 비밀번호 2차 테스트

정상적으로 테스트가 완료되었다.

 

728x90
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함