💙 들어가며
인증정보만 가지고 정보를 줄 수 있으나
추가적인 역할을 주어서 페이지 접속권한에 차등을 주어보자.
✏️ 학습내용 정리
#필터(Filter)
인증했어? 라는 부분은 컨트롤러로 오기 전에
사전에 체크되어야 하는 부분이고,
인증을 했으면 권한에 대한 처리도 필요한데,
권한을 확인하는 부분이 없다.
이렇듯 목적지가 서블릿이라면
사용자 요청이나 이런것들은
서블릿으로 가기 전에 가공이 필요하다.
이렇게 사전에 해야할 것들(실제 업무로직이 진행되기 전에),
하나의 코드 영역으로 필요한 부분을
따로 모아서 집중화 할 수 있는 방법이 있다.
바로 '필터'!
컨트롤러가 아니라 필터에
이런 사전에 필요한 코드들을 집중화해보자.
#서블릿 필터 만들기
filter라는 패키지를 만들고 클래스를 만들자.
filter기능은 스프링이 가지고 있는게 아니라
서블릿이 가지고 있는 기능이다.
서블릿으로 필터를 만든다면
@WebFilter("필터하고 싶은 url")로 하겠지만
우리는 스프링을 사용하기 때문에
@Component로 bean을 만들기만 하면 된다.
implements Filter를 하고
doFilter라는 메소드를 사용해보자.
#FilterChain
필터는 서블릿이 POJO로 가는 길목에 있는 것이 아니라
아예 서블릿으로 가기 전의 순서에 위치한다.
필터체인은 필터들의 연결이라고 볼 수 있겠다.
서블릿 컨테이너에 의해서 관리된다.
클라이언트에서 요청이 들어왔거나
서버에서 클라이언트로 응답을 보내는 전 단계에서
다음을 갈지 말지 중간에서 정하는 역할을 한다.
어떤 부분에서는 마치 forward처럼 느껴지지만,
forward는 서블릿끼리 호출되는 것이고,
filterChain은 다음 진행될 서블릿에게 보내는 것이다.
💡 필터체인이란?출처: 챗지피티(https://chat.openai.com)
다음 서블릿으로 넘어가기 전에
필터가 계속해서 요청되는 모습을
확인해볼 수 있다.
#스프링에서 Filter
과거에는 필터에서
인증과 관련된 모든 것을 처리하려고 했다.
필터란 녀석에게 지시서를 마련하고
통과 시킬지/말지/로그인으로 보낼지를
다 일일이 만들었었다.
하지만 스프링이 이것 또한 모두 만들어줬다.
(스프링이 설정을 가미해서 만들어놓은 라이브러리가 있음)
@Component를 지우고 스프링이 제공하는 필터를 사용해보자.
#스프링 시큐리티
스프링 시큐리티는
스프링에서 기본제공되는 라이브러리는 아니고
외부 라이브러리로 취급된다.
(나중에 추가된 것임)
우리는 부트를 사용하고 있으니,
스프링 스타터에서 옵션만 추가하면 됨!
스프링부트의 starters는
필요한 라이브러리를 하나로 합쳐놓았고
설정도 포함되어 있다.
pom에 정상적으로 들어간 것 확인하고
라이브러리를 추가했으면
무조건 서버 재시작 해야한다!
(실행중이라면 무조건 재시작, 안그러면 인식 못함)
서버를 재시작하면 자동으로 임시 패스워드가 생성되는데,
이것은 스프링에서 기본으로 제공하는 계정의 비밀번호다.
(ID는 user로 접속하면 됨!)
만약 추가로 패스워드를 내가 원하는 것으로
고정하고 싶다면,
이것은 .propertiese나 .yml에 추가하면 된다.
(이름, 비밀번호, 역할)
사실상 이렇게 사용하는 일은 거의 없지만
계정을 하나만 가지고 있으면 되는 경우처럼
간단하게 사용하는 경우에는 이렇게 할 수 있다.
(혹은 슈퍼user, 루트user가 필요할 때~!)
그런데 보통은 DB에 있는 것을 사용하기 때문에
이런 방법은 거의 사용하지 않음
#(번외) 스프링 시큐리티에서 지원하는 기능들
1. 오픈 인증(OAuth)
공신력 있는 사이트들을 대상으로 함
(EX: 구글로 로그인, 네이버로 로그인)
2. 싱글사인인(SAML 두 개를 모두 지원함)
계열사들 간에 대상으로 함
(EX: 아모레퍼시픽에서 통합회원 가입하겠느냐 이런것들)
어떤 옵션으로 하느냐에 따라서,
아래와 같은 alert 형태의 로그인 화면도 만들 수 있다.
이제 디테일한 설정을 하면 되는데,
아래의 사진에서 처럼 Java Config와 XML 방식이 있다.
하지만, XML로 설정하는 방법은 거의 안쓴다.
우리도 자바로 설정하는 방법으로 테스트할 예정!
(참고) 아래와 같이 두 가지 방식이 모두 가능하다.
인증과 관련된 부분은 스프링에게 맡기되,
인증이 되면 어떤 권한을 가질 것인가에 대해서는
따로 지시사항을 알려주어야 한다.
특이한 것은 최초에 스프링이
역할 키워드에 "ROLE_"이라는 것을
붙여서 만들었었기 때문에
DB에 있는 데이터에 "ROLE_"이라는 접두사를
붙여주어야 한다는 것이다.
지금의 스프링에서는 "ROLE_"을 빼고
AUTHOR라고만 해도 인식할 수 있게 개선이 되었지만,
여전히 DB에는 "ROLE_"이라는 키워드를
따로 설정해야 한다.
이제 이 역할을 바탕으로
어떤 url에 대한 접근을 허락하고 막을 것인지에 대한
설정을 할 수 있게 되었다.
로그인 하는 logic인 POST 매핑도
스프링이 만들어 준 것을 사용해보자.
GET도 만약 우리가 만든거 쓸거 아니라면
주석처리 해도 된다.
그런데 우리는 우리가 만든거 쓸거라서(form) get은 살려놓음!
#외부 라이브러리 직접 객체 만들기: @Bean
소스코드가 없는 외부 것들은
우리가 직접 @Bean을 사용해서
만들어주어야 한다.
그러려면 @Bean을 담고 있는 클래스도
객체여야 하니까 @Configuration 붙이는 것 잊지말기!
💡 요즘의 트렌드는 build()
세터(setter): 일일이 다 따로 불러줘야함
생성자(constructor): 순서를 지키지 않으면 치명적
그래서 build()를 많이 사용해서 객체를 생성한다.
build를 사용하면
1. 필요한 것들에 대해서만 생성할 수 있고,
2. 지켜야 할 순서도 없으며,
3. 따로따로 set할 필요 없이 한 번에 이어서 처리가 가능하다.
@Configuration //설정을 가지고 있는 녀석이라서 @Component 말고 Configuraiton이라고 특화된 이름 사용
public class RlandSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
return http.build();
}
}//class ends
우리는 이미 스프링 시큐리티 라이브러리가 만든
필터체인을 직접 생성해서 콩자루에 담기만 하면 된다.
(우리는 객체만 만들면 됨!)
우리가 소스코드를 건들 수 없기 때문에
직접 생성을 해야 하는데
이것을 @Bean을 이용해서 만들었고
http라는 도구를 사용해서 필터체인을 만들 것이다.
지금은 모든 설정을 다 막어놓은 건데
이제 우리는 특정 url을 허용할지 안할지를
설정해야 한다.
http를 이용해서 막을 url을 설정하고
특정 role에 대해서만 url 접근을 허용해보자.
(지정한 url에 접속하는 role중에
hasRole에 저장한 role이 아니면 다 차단될 예정)
안의 내용을 설정하는 방법은
아래를 참고해보자!
요즘은 람다식으로 표현한다고 함!
아래와 같이 람다식으로
표현하는 방법으로 바뀌었으니
참고하기!
/member/**로 해야 member의 하위 모든 url로 인식된다.
만약 내 role이 ADMIN, MEMBER가 아니라면
다음과 같은 403 오류가 뜰 것이다.
아래와 같이 설정을 한 번 더 해주면
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
http.csrf(csrf->csrf.disable())
.authorizeHttpRequests(
authorize->authorize
.requestMatchers("/member/**")
.hasAnyRole("ADMIN","MEMBER")
.anyRequest()
.permitAll())
.formLogin(
form->form
.loginPage("/user/login")
);
return http.build();
}
이제는 403 오류가 발생하지 않고
우리가 만든 로그인 페이지가 나온다.
#사용자 정보 설정하기: UserDetails
그렇다면 사용자 정보를 어떻게 넣어야 할까?
스프링 Security에서는 사용자 정보를 UserDetails라고 한다.
그래서 서비스이름도 UserDetailsService라고 한다.
(우리는 기존에 member details라고 했으나 이제 달라지는 것)
그럼 userdetail을 주기 위한 객체가 있어야 하고
그걸 다루기 위한 service가 있어야 한다.
UserDetails의 User라는 것을 사용하여 builder할 수 있다.
(set, 생성자로 하면 오히려 오류난다!)
(아래는 테스트용이라서 반환타입이 void다. 신경쓰지 말자!)
먼저 DB를 사용하지 않고
직접 사용자정보를 지정하는 방식은
아래와 같이 처리할 수 있다.
#UserDetailsService 만들기
위의 사용자정보를 다룰 수 있게
스프링 시큐리티에게
UserDetailsService라는 것을 알려 주기만 하면 끗~!
3가지 방식이 있는데
1. InMemoryUserDetailsManager를 통해 서비스 객체를 만들 수도 있다.
(메모리 상에 사용자 정보를 가지고 있다. 정적으로 박아놓고 사용할 때)
DB사용하는 것 아님, 경량화된 방식으로 1~2개 정도의 계정정보만 필요할 때
하지만 설정한 계정정보로 로그인을 해도
아직 403에러가 나는 것을 알 수 있다.
여기서 나는 보안오류는 아이디/비밀번호/권한 등에 대한 오류가 아니다.
(아이디, 비밀번호, 권한 다 맞는데 나는 오류임)
#403에러의 이유: CSRF(Cross Site Requset Forgery)
이것은 인증이 제대로 이루어지지 않아서 난 오류가 아니라
포스트에 대한 오류인 것이다.
권한오류인 403이 떠서 헷갈리겠지만
POST를 할 때는 CSRF에 대한 설정이 꼭 필요함을 잊지말자.
💡 일단 실습을 위해서 토큰을 각각 주지 않고 막고 하겠다.
http.csrf(csrf->csrf.disable())로 설정하면 일단 csrf 무시할 수 있다.
공격에 무방비하므로.... 토큰을 심어주는 것이 바람직하다.
※토큰을 심는 방법은 간단!
input type="hidden"에다가 스프링이 제공하는 것을 넣어주기만 하면 됨
이전에 GET과 관련된 오류는 CORS였으니
두 가지도 잘 비교해두는 것이 좋겠다.
💡 CORS와 CFRS
CORS(Cross Orgin Resource Sharing): 겟요청을 허락하지 않겠다.
내가 허락하지 않은 곳에서 문서를 달라고 하는 것을 막겠다.
CSRF(Cross Site Request Forgery): 포스트요청을 허락하지 않겠다.
내가 허락하지 않은 곳에서 데이터를 입력하는 것을 막겠다.
#CSRF 오류의 이유
다른 사이트에서 사용하는 정보를
입력하려는 행위를 공격으로 인식해서 막는 것이다.
POST는 항상 GET을 가져가게 되어 있다.
(입력폼이 잇는 문서를 먼저 받아야 함)
서버에서는 내가 준 문서에서만 POST하는 것이 기본이다.
내가 GET으로 준 문서에 대한 POST인지를 인식해야
공격으로 받아들이지 않는다.
그래서 등록폼을 줄 때 인식할 수 있는 토큰을 심어서 줘야한다.
login페이지에 토큰을 넘겨주어야
POST해서 넘어올 때 내가 넘겨준 문서인지를 확인할 수 있다.
토큰을 심어서 주지 않으면
CSRF에 대한 공격에 무방비 상태가 줄 수 있다.
#(번외) CSRF 해결한 뒤 뜨는 인코딩 오류
그 이후에는 이런 오류가 뜬다.
비밀번호를 직접 가지고 잇으면 법적으로 문제가 되기 때문에
비밀번호에 대한 추가 작업이 필요하다.
#(번외) 비밀번호 인코딩 에러가 바로 안뜬 이유
HTML에서 input 태그의 name은
userDetails의 username, password와 똑같이 적어주어야 한다.
안그러면 아예 인식을 못한다.
💙 마치며
1.
400오류는 파라미터 오류
404오류는 페이지가 없거나 url이 잘못 되었을 때 오류
403오류는 권한이 없는 페이지에 접속했을 때 발생!
405오류는 url은 있으나 메소드가 없는것
post 메소드가 없는데 post요청을 한다는 등...