스프링 시큐리티를 이용한 로그인 로그아웃 구현

charco·2021년 6월 8일
0

출처: 코드로 배우는 스프링 웹 프로젝트


URI 접근 제한하기

이전에 우리는 세개의 URI를 설계했다.

  • /sample/all => 모든 사용자에게 접근 허용
  • /sample/member => member권한을 가진 사용자만 접근 허용
  • /sample/admin => admin 권한을 가진 사용자만 접근 허용

현재 애플리케이션을 실행하면 모든 uri에 모든 사용자들이
접근할 수 있다. 이제 권한에 따른 접근 제한을 설정해보자.

설정은 security-context.xml 에서 한다.

<security:http>

  <!--인터셉터를 통해 접근 제한-->
  <!-- /sample/all 에는 모든 접근을 허용(permitAll)-->
  <security:intercept-url pattern="/sample/all" access="permitAll"/>
<!-- /sample/member 에는 ROLE_MEMBER 권한을 가진 사용자만 허용-->
  <security:intercept-url pattern="/sample/member" access="hasRole('ROLE_MEMBER')"/>

</security:http>

<security:authentication-manager>
  
</security:authentication-manager>

접근에 제한을 걸었으니 애플리케이션을 실행해 /sample/member 에 접근해보자. 당신은 /login으로 리다이렉트 될 것이다.
엥?
우리는 /login을 만든적이 없는데 생겼다.
이 페이지는 스프링 시큐리티가 기본으로 제공하는 페이지다. 하지만 우리만의 로그인 페이지를 만들어야 하기 때문에 테스트용으로만 사용하자.


단순 로그인 처리

로그인 화면이 보이긴 하지만 우리의 서버에는 기존 유저에 대한 정보가 아무것도 없다. 임시로 in-memory 유저를 만들어보자.

인증과 권한에 대한 실제 처리는 UserDetailsService라는 인터페이스를 구현해 처리한다. xml로는 다음과 같이 지정한다.

<security:http>
		
  <security:intercept-url pattern="/sample/all" access="permitAll"/>

  <security:intercept-url pattern="/sample/member" access="hasRole('ROLE_MEMBER')"/>
  
  <security:form-login />


</security:http>

<security:authentication-manager>
  
  <security-authentication-provider>
  	
    <security:user-service>
      <!--"member"라는 이름의 "MEMBER"권한을 갖는 in-memory유저 생성-->
      <security:user name="member" password="member" authorities="ROLE_MEMBER"/>
      
    </security:user-service>
    
  <security-authentication-provider>
  
</security:authentication-manager>

이제 애플리케이션을 실행해 /sample/member로 다시 접근한다음 로그인 해보자. PasswordEncoder가 없다는 에러가 뜬다. PasswordEncoder는 간단히 말해 패스워드를 랜덤한 긴 문자열로 암호화 시켜주는 도구다. 스프링 시큐리티 5부터는 PasswordEncoder를 필수로 이용하도록 돼있기 때문에 에러가 발생하는 것이다. 패스워드 앞에 {noop}을 붙혀 임시로 인코딩 없이 사용하자


      <security:user name="member" password="{noop}member" authorities="ROLE_MEMBER"/>

다시 서버를 시작하면 성공적으로 로그인 될 것이다.


로그아웃 확인

스프링 시큐리티를 배우다 보면 자주 로그인과 로그아웃을 해야 한다. 조금 뒤에 웹 화면을 이용해 로그아웃 처리를 해 볼 것이니 일단은 임시로 JSESSIONID 라는 쿠키를 강제로 삭제해 로그아웃 처리를 하자. 크롬에서 F12를 누르면 개발자 도구가 나온다. Application 탭을 선택해 좌측 Storage 의 Cookies 를 클릭하면 JSESSIONID가 있을 것이다. 마우스 우클릭 -> delete를 눌러 강제로 삭제하자. JSESSIONID 는 Tomcat에서 발행하는 쿠키의 이름이다. 쿠키를 삭제하고 다시 /sample/member 를 호출하면 다시 /login 으로 이동될 것이다.


여러 권한을 갖는 사용자 설정

전에 MEMBER 권한을 갖는 member 사용자를 만들엇다.
이번에는 MEMBER 과 ADMIN 권한을 모두 갖는 admin이라는 이름의 사용자를 만들어보자. xml을 다음과 같이 작성한다.

<security:http>
		
  <security:intercept-url pattern="/sample/all" access="permitAll"/>

  <security:intercept-url pattern="/sample/member" access="hasRole('ROLE_MEMBER')"/>
  
  <!--전에 생성했던 /sample/admin URI에 ADMIN 유저만 접근 할 수 있도록 설정한다.-->
  <security:intercept-url pattern="/sample/admin" access="hasRole('ROLE_ADMIN')"/>
  
  <security:form-login />


</security:http>

<security:authentication-manager>
  
  <security-authentication-provider>
  	
    <security:user-service>

      <security:user name="member" password="member" authorities="ROLE_MEMBER"/>
      
      <!--MEMBER와 ADMIN 권한을 갖는 admin 유저 생성-->
      <security:user name="admin" password="admin" authorities="ROLE_MEMBER, ROLE_ADMIN"
      
    </security:user-service>
    
  <security-authentication-provider>
  
</security:authentication-manager>

애플리케이션을 실행해 admin 사용자로 로그인하면
/sample/admin, /sample/member 에 모두 접근할 수 있을 것이다.


접근 제한 메시지 처리

우리가 위에서 만든 member 사용자로 로그인 하여
/sample/admin 을 요청하면 403 에러가 뜰 것이다.
이때 에러페이지를 그대로 보여주기 보다는 사용자가 쉽게 이해할 수 있는 특정한 화면을 보여주는 것이 더 좋다.
접근 제한에 대한 처리는 AccessDeniedHandler를 직접 구현하거나 특정한 URI를 지정할 수 있다.

<security:http>
		
  <security:intercept-url pattern="/sample/all" access="permitAll"/>

  <security:intercept-url pattern="/sample/member" access="hasRole('ROLE_MEMBER')"/>
  
  <security:intercept-url pattern="/sample/admin" access="hasRole('ROLE_ADMIN')"/>
  
  <security:form-login />

<!--접근이 거부됐을 때 보일 화면을 지정-->
<security:access-denied-handler error-page="/accessError"/>
  
</security:http>

URI를 지정했으니 매핑할 컨트롤러가 필요하다.
CommonController 클래스를 생성해 매핑해주자.

@Controller
public class CommonController {

   @GetMapping("/accessError")
    //Authentication 객체는 스프링 시큐리티에서 제공하는 사용자의 정보를 담고있는 객체다 (아이디, 권한 등)
    //필요한 경우 사용자의 정보를 화면에 보여주기 위해 파라미터로 받았다.
    public void accessDenied(Authentication auth, Model model){
    	//model에 접근이 제한됐다는 메시지를 담아 보낸다.
    	model.addAttribute("msg", "Access Denied")
    }

}

accessError.jsp 를 생성해 model에 담은 메시지를 보여주면 된다.

AccessDeniedHandler 구현하여 접근 거부 처리하기

위와 같이 에러페이지만을 제공하는 경우 사용자의 URI에는 변화가 없다. /sample/admin 으로 접근을 시도해 거부당했다면 그대로 /sample/admin 에서 accessError.jsp 의 화면이 보여진다

조금 더 복잡한 처리가 필요하다면 AccessDeniedHandler를 구현해야한다.

클래스를 생성하자.

public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    @Overried
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessException){
    	//단순히 만들었던 에러 페이지로 리다이렉트 한다.
        //이제 접근 권한이 없는 URI에 요청을 하면 사용자의 URI도 /accessError로 바뀔 것이다.
    	response.sendRedirect("/accessError");
    }

}

이 클래스의 메서드가 동작하려면 이 클래스가 스프링 빈에 등록돼야한다. security-context.xml 에 빈으로 추가해주자.

<bean id="customAccessDenied" class ="org.zerock.security.CustomAccessDeniedHandler"></bean>

<security:http>
		
  <security:intercept-url pattern="/sample/all" access="permitAll"/>

  <security:intercept-url pattern="/sample/member" access="hasRole('ROLE_MEMBER')"/>
  
  <security:intercept-url pattern="/sample/admin" access="hasRole('ROLE_ADMIN')"/>
  
  <security:form-login />

<!--<security:access-denied-handler error-page="/accessError"/>-->
  
  <!--위에 등록한 빈을 추가-->
  <security:access-denied-handler ref="customAccessDenied"/>
  
</security:http>

이제 애플리케이션을 실행해 member계정으로 로그인하여 /sample/admin 에 접근해보자. 이전과 달리 화면만 바뀌는 것이 아닌 /accessError 로 리다이렉트된다.

다음에는 시큐리티의 기본 로그인 페이지가 아닌 우리가 직접 로그인 페이지를 만들어보자!

profile
아직 배우는 중입니다

0개의 댓글