스프링 시큐리티의 내부 구조는 상당히 복잡하지만, 실제 사용은 약간의 설정만으로도 처리가 가능하다.
앞에서 설계한 몇 개의 URI에 대한 접근 제한을 통해서 간단히 스프링 시큐리티의 적용 방법을 알아보자
접근 제한 설정
security-context.xml에 아래와 같이 접근 제한을 설정한다.;
<security:http>
<security:form-login />
<security:intercept-url pattern="/sample/all"
access="permitAll" />
<security:intercept-url
pattern="/sample/member" access="hasRole('ROLE_MEMBER')" />
</security:http>
특정한 URI에 접근할 때 인터셉터를 이용해서 접근을 제한하는 설정은 <security:intercept-url>를 이용한다.
pattern이라는 속성과 access라는 속성을 지정해야만 한다. pattern속성은 말 그대로 uri의 패턴을 의미하고, access의 경우는 권한을 체크한다.
이경우 /sample/member라는 uri는 role_member라는 권한이 있는 사용자만이 접근가능하게 된다.
access의 속성값으로 사용되는 문자열은
1. 표현식
2. 권한명을 의미하는 문자열
을 이용할 수 있다.
이렇게 설정을 변경하고, 톰캣을 실행하고
samle/member를 접근하면 특이한 게나타난다.
이렇게 로그인 화면으로 넘어간다...
all 이나, admin 은 똑같이 아무화면도 안나타난다. 그리고 심지어 나는 login 화면을 만들지도 않았다.
이것은 스프링 시큐리티가 기본적으로 제공하는 페이지이다. 현실적으로 변돌의 로그인 페이지를 제작
해야만하므로 테스트하는 과정에서만 사용할만하다.
단순 로그인 처리
로그인 화면이 보여지기는 하지만, 로그인을 할 수 없는 상황이다.
추가적인 설정을 통해 아이디와 패스워드로 로그인이 가능하도록 설정을 추가해 주자
스프링 시큐리티에서 명심해야 하는 사항 중 하나는
username이나 User라는 용어의 의미가 일반적인 시스템에서의 의미와 차이가 있다.
일반 시스템에서 userid는 스프링 시큐리티에서는 username에 해당한다.
일반적으로 사용자이름을 username이라고 처리하는것과 혼동하면 안된다.!!
인증과 권한에 대한 실제 처리는 UserDetailsService라는 것을 이용해서 처리한다.
xml에서는 다음과 같이 지정할 수 있다.
<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<security:user name="member" password="member"
authorities="ROLE_MEMBER" />
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
추가된 설정의 핵심은 member라는 계정 정보를 가진 사용자가 로그인을 할 수 있도록 하는 것이다.
위의 설정을 추가한 후에 was를 통해
sample/member로 접근해서 로그인하면 예상과 달리 에러가 발생한다.
실행 결과에서 에러는 PasswordEndoder라는 존재가 없기 때문이다.
스프링 시큐리티 5버전부터 반드시 PasswordEncoder라는 존재를 이용하도록 변경되었다. 스프링
시큐리티 4버전까지는 PasswordEncoder의 지정이 없어도 동작했지만, 이제는 반드시 필요하다.
임시 방편으로 스프링 시큐리티 5버전에는 포맷팅 처리를 지정해서 패스워드 인코딩 방식을 지정할 수 있다.
만일! 패스워드의 인코딩 처리없이 사용하고 싶다면 패스워드 앞에 {noop}문자열을 추가한다.
<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<security:user name="member" password="{noop}member"
authorities="ROLE_MEMBER" />
</security:user-service>
</security:authentication-provider>
다시 톰캣을 실행해서 /sample/member 에 접근하고 member/member로 로그인하면 아래와 같이 동작하는 것을 확인 할 수 있다.
로그인하면 아래와 같이 동작하는 것을 확인 할 수 있다.
로그아웃 확인
스피링 시큐리티를 학습하다 보면 매번 로그아웃하고, 새롭게 로그인을 해야하는 상황이 자주 발생한다.
이에 대해 가장 확실한 방법은 브라우저에서 유지하고 있는 세션과 관련된 정보를 삭제하는 것이다.
개발자 도구에서 Application 탭을 확인해보면 Cookies항목에 JSESSIONID와 같이 세션을 유지하는데
사용되는 세션 쿠키의 존재를 확인할 수 있따.
로그아웃은 JSESSIONID쿠키를 강제로 삭제해서 처리한다.
쿠키를 삭제한 후에 로그인이 필요한 URI를 다시 호출해 보면 로그인이 필요한 것을 확인 할 수 있다.
여러 권한을 가지는 사용자 설정
정상적으로 로그인이 처리되는 것을 확인 했다면, /sample/admin을 처리하도록 해야하낟.
/sample/admin은 admin이라는 권한을 가진 사용자가 접근할 수 있돌고 지정하는데,
사용자는 admin과 member라는 2개의 권한을 가지도록 지정한다.
<security:http>
<security:form-login />
<security:intercept-url pattern="/sample/all"
access="permitAll" />
<security:intercept-url
pattern="/sample/member" access="hasRole('MEMBER')" />
<security:intercept-url pattern="/sample/admin"
access="hasRole('ADMIN')" />
</security:http>
<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<security:user name="member" password="{noop}member"
authorities="MEMBER" />
<security:user name="admin" password="{noop}admin"
authorities="MEMBER, ADMIN" />
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
security-context.xml 을 수정하였다. 새롭게 추가된 security:intercept-url은 /sample/admin에 대한
접근을 설정한다. <security:user>에 추가된 admin사용자는 member와 admin이라는 2개의 권한을 가진다.
톰캣을 실행해서
/sample/member 과/sample/admin 모두 실행하도록 하겠다.
admin으로 로그인을 시도했으나 위와같은 uri에 접근할 수 있는 권한이 없는 상황이라 발생하는
오류가 나타났다.
이 경우에는 접근 제한 에러메세지를 보게되는데 , 스프링 시큐리티에서는 접근 제한에 대해서
AccessDenieHandler를 직접 구현하거나, 특정 URI를 지정할 수 있다.
<security:http auto-config="true" use-expressions="true">
<security:intercept-url pattern="/sample/all"
access="permitAll" />
<security:intercept-url
pattern="/sample/member" access="hasRole('MEMBER')" />
<security:intercept-url pattern="/sample/admin"
access="hasRole('ADMIN')" />
<security:form-login />
<security:access-denied-handler
error-page="/accessError" />
</security:http>
인터페이스의 구현체를 지정하거나, error-page를 지정할 수 있다.
지금은 /accessError라는 Uri로 접근 제한 시 보이는 화면을 처리한다.
이를 위해 COmmonController클래스를 생성해서 accessError 를 처리하도록 만들어주자
@GetMapping("/accessError")
public void accessDenied(Authentication auth, Model model) {
log.info("access Denied : " + auth);
model.addAttribute("msg", "Access Denied");
}
간단히 사용자가 알아 볼 수 있는 에러메시지만을 Model에 담아 넘겨주도록하고,ㅡ
/accessEroor는 Authentication 타입의 파라미터를 받도록 설계해서 필요한 경우에 사용자 정보를 확인
할수있도록 한다.
view 폴더에는 accessError.jsp 파일을 생성한다.
<h1>접근 제한!</h1>
<h2><c:out value="${SPRING_SECURITY_403_EXCEPTION.getMessage()}"/></h2>
<h2><c:out value="${msg}"/></h2>
</body>
내용만 또 간단히 표시해주자
그리고 다시 실행하여 admin으로 접근을 시도하였더니
이 보이게된다.
AccessDeniedHandler 인터페이스를 구현하는 경우
다음과 같이 error-page만을 제공하는 경우에는 사용자가 접근했던URI 자체의 변화는 없다.
URI자체는 /sample/admin으로 되어있다, 화면의 내용은 /accessError에 해당하는 URI의 결과이다.
접근 제한이 된 경우에 다양한 처리를 하고 싶다면, 직접 AceessDemiedHandler 인터페이스를 구현하는 것이 좋다.
예를 들어 접근 제한이 되었을 때 쿠키나 세션에 특정한 작업을 하거나, HttpServletRsponse에 특정한 헤더 정보를
추가하는 등의 행위를 할 경우에는 직접 구현하는 방식이 있다.
이 기능을 위해 새롭게 security 패키지를 생성하고 CustomAccessDeniedHandler클래스를 추가하자
그리고 해당 클래스에 코드를 집어넣는다.
package org.study.security;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import lombok.extern.log4j.Log4j;
@Log4j
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
// TODO Auto-generated method stub
log.error("Access Demied Handler");
log.error("redirect...");
response.sendRedirect("/accessError");
}
}
해당 클래스에 인터페이스를 직접 구현한다. 인터페이스의 메서드는 handler()뿐이기 때문이고, HttpServletRequest,
HttpServletResponse를 파라미터로 사용하기 때문에 직접적으로 서블릿 API를 이용하는 처리가 가능하다.
지금의 코드에서는 접근 제한이 걸리는 경우 리다이렉트를 하는 방식으로 동작하도록 지정했다.
security-context.xml에서는 error-page속성 대신 CustomAccessDeniedHandler를 빈으로 등록해서 사용해보자
<bean id="customAccessDenied"
class="org.study.security.CustomAccessDeniedHandler"></bean>
빈은 이렇게, 그리고
<!-- <security:access-denied-handler -->
<!-- error-page="/accessError" /> -->
<security:access-denied-handler
ref="customAccessDenied" />
페이지를 직접지정해서 쓰던걸 주석처리하고 이젠 해당 방식으로 처리하기위해 수정해주자!
참조 속성 둘 중하나만을 사용한다. 이의 방식으로 한다면 어떤차이가 있는지 직접
톰캣을 다시 실행하고 결과를 통해 알아보자
오잉!? 이전과 달리 /accessError로 리다이렉트 되는 것을 확인 하였다.
'Spring공부 > 4-스프링시큐리티' 카테고리의 다른 글
커스텀 UserDetailsService (0) | 2021.10.19 |
---|---|
JDBC를 이용하는 간편 인증/ 권한처리 (0) | 2021.10.18 |
로그인과 로그아웃처리(2) (0) | 2021.10.18 |
Spring Web Security (0) | 2021.10.18 |
Spring Web Security를 이용한 로그인처리 (0) | 2021.10.18 |
댓글