불로구

[SpringFramework] - SpringSecurity를 이용한 로그인 설정 본문

프로그래밍/JAVA

[SpringFramework] - SpringSecurity를 이용한 로그인 설정

맹이맹이 2021. 3. 9. 11:22
반응형

스프링 시큐리티란?

  • 스프링 기반의 어플리케이션의 보안( = 인증, 권한)을 담당하는 F/W
  • 스프링 시큐리티를 통해 세션체크와 리다이렉트를 할 수 있다.
  • 스프링 시큐리티는 Filter 기반으로 동작하기 때문에 Spring MVC와 분리되어 관리 및 동작
  • 3.2버전 부터는 xml로 설정하지 않고 자바 bean 설정으로 가능

보안 용어

  • 접근 주체(Principal) : 보호된 대상에 접근하는 유저
  • 인증(Authenticate) : 현재 유저가 누구인지 확인 , 애플리케이션의 작업을 수행할 수 있는 주체임을 증명
  • 인가(Authorize) : 현재 유저가 어떤 서비스, 페이지에 접근할 수 있는 권한이 있는지 검삭
  • 권한 : 인증된 주체가 애플리케이션의 동작을 수행할 수 있도록 허락되어 있는지를 검사
    • 권한 승인이 필요한 부분으로 접근하려면 인증 과정을 통해 주체가 증명 되어야만 한다.
    • 권한 부여에도 두가지 영역이 존재하는데 웹 요청 권한, 메소드 호출 및 도메인 인스턴스에 대한 접근 권한 부여

인증 방법

  • 스프링 시큐리티는 쿠키-세션 방식을 사용한다.
    • 로그인 시도 -> AuthenticationFilter에서 user DB로 접근 -> 유효한 사용자면 SESSION 생성 -> SecurityContextHolder 세션 저장소에 저장 -> 유저에게 session ID와 응답 전달 -> 이후 요청시 요청쿠키에서 JSESSIONID를 검증 후 유효 시 세션ID로 바뀜

Spring Security의 Filter

  • SecurityContextPersistenceFilter : SecurityContextRepository에서 SecurityContext를 가져오거나 저장하는 역할
  • LogoutFilter : 로그아웃 처리
  • (UsernamePassword)AuthenticationFilter : (아이디 비밀번호의 form 기반 인증) , 설정된 로그인 url로 오는 요청 감시, 인증처리
    • AuthenticationManager로 인증
    • 인증 성공 시, 얻은 Authentication 객체를 SecurityContext에 저장 후 AuthenticationSuccessHandler 실행
    • 인증 실패 시, AuthenticationFailureHandler 실행
  • DefaultLoginPageGeneratingFilter : 인증을 위한 로그인 폼 URL을 감시
  • BasicAuthenticationFilter : HTTP 기본 인증 헤더를 감시하여 처리
  • RequestCacheAwareFilter : 로그인 성공 후, 원래 요청 정보를 재구성하기 위해 사용
  • AnonymousAuthenticationFilter : 이 필터가 호출되는 시정까지 사용자 정보가 인증되지 않았다면 인증 토큰에 사용자가 익명 사용자로 나타남
  • SessionManagementFilter : 인증된 사용자와 관련된 모든 세션 추적
  • FilterSecurityInterceptor : AccessDecisionManager로 권한부여 처리를 위임함으로써 접근 제어 결정을 쉽게 해줌

Authentication

  • 모든 접근 주체는 Authentication를 생성한다.
  • 생성된 Authentication은 SecurityContext에 보관되고 사용됨.
  • 즉, 시큐리티 세션들은 내부 메모리에 쌓고 꺼내 쓰는 것
  • Authentication 인터페이스
public interface Authentication extends Principal, Serializable { 
	Collection<? extends GrantedAuthority> getAuthorities(); // Authentication 저장소에 의해 인증된 사용자의 권한 목록 
	Object getCredentials(); // 주로 비밀번호 
	Object getDetails(); // 사용자 상세정보 
	Object getPrincipal(); // 주로 
	ID boolean isAuthenticated(); //인증 여부 
	void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException; 
}

pom.xml

  <dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-web</artifactId>
			<version>4.2.1.RELEASE</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-config -->
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-config</artifactId>
			<version>4.2.1.RELEASE</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-core -->
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-core</artifactId>
			<version>4.2.1.RELEASE</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-taglibs -->
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-taglibs</artifactId>
			<version>4.2.1.RELEASE</version>
		</dependency>
    // 버전은 상황에 맞게

security-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
    
    <!-- url 검사 -->
	<http auto-config="true" use-expressions="true">    // use-expressions=true -> <intercept-url>의 access 속성에 표현식 사용 , false이면 access="USER"로 값을 사용해야 함
		<intercept-url pattern="/login/**" access="permitAll" />
		<intercept-url pattern="/admin/**" access="hasRole('ROLE_ADMIN')" />  // <intercept-url> 요소는 url에 대한 access 속성의 권한 요구
		<intercept-url pattern="/**" access="permitAll" />                    // 여러개 사용 시 순서대로 매칭되어 사용 됨 , 가장 특수한 경우를 위쪽에 놓고 일반적인 것은 아래에 작성
	<!-- 	<security:form-login />  -->
		<form-login login-page="/login/loginForm.do"  // 로그인을 위해서 폼을 사용 , 로그인처리 기본 URL은 "/login"
					default-target-url = "/home.do"   //로그인 후 기본으로 보여 질 페이지 지정
					authentication-failure-url="/login/loginForm.do?error" //로그인 실패 시 보여질 체이지 지정 , 로그인 페이지 다시 보여 줌
					username-parameter="id" //로그인 폼에 아이디 입력 필드에 사용될 name , 기본값 : username
					password-parameter="password" /> //로그인 폼에 비밀번호 입력 필드에 사용 될 name, 기본 값 : password
		<logout logout-url="/logout"          //로그아웃 url 제공, 기본은 "/logout"
				logout-success-url="/home.do" />
		                                                             // <csrf disabled="false"/>라는 속성은 CSRF 공격 방어 
                                                                // POST방식으로 값을 넘기는 곳에는 모두 <input name="_csrf" type="hidden" value="fk2031a-sd~" 같은 숨겨진 값 전송
                                                                // csrf 방어 사용시 로그아웃을 post 방식으로 해야 함
                                                                
		<access-denied-handler error-page="/login/accessDenied.do" />
	</http> 
	
	<!-- provider -->
	<authentication-manager>
		<authentication-provider>
			<user-service>
				<user authorities="ROLE_USER" password="password" name="user" />
				<user authorities="ROLE_ADMIN" password="password" name="admin"/>
			</user-service>
		</authentication-provider>
	</authentication-manager>
	
</beans:beans>

  • access 속성
    • "permitAll" : 로그인 없이 접근 가능
    • "hasRole('ROLE_ADMIN')" : ADMIN 권한이 있어야 접근 가능
    • "hasAnyRole('ROLE_USER, ROLE_ADMIN')" : USER나 ADMIN이면 접근

web.xml

<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>
			/WEB-INF/spring/root-context.xml
			/WEB-INF/spring/security-context.xml
		</param-value>
	</context-param>
  
<!-- 서블릿 필터로 동작을 하기 때문에 요청의 앞단에서 피요한 체크 수행 -->
	<filter> 
		<filter-name>springSecurityFilterChain</filter-name> 
		<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> 
	</filter> 
	
	<filter-mapping> 
		<filter-name>springSecurityFilterChain</filter-name> 
		<url-pattern>/*</url-pattern> 
	</filter-mapping>

home.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> 
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> 
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %> // 스프링시큐리티 태그 사용
<!DOCTYPE html>
<html>
<head>
	<title>Home</title>
</head>
<body>
	<sec:authorize access="isAnonymous()"> //익명의 사용자 (로그인 안된 사용자)
		<p><a href="<c:url value="/login/loginForm.do" />">로그인</a></p>
	</sec:authorize>
	
	<sec:authorize access="isAuthenticated()"> //로그인 된 사용자
		<form:form action="${pageContext.request.contextPath }/login/logout.do" method="POST">
			<input type="submit" value="로그아웃" />
		</form:form>
	</sec:authorize>

	[<a href="<c:url value='/admin/adminHome.do' />">관리자 홈</a>]

</body>
</html>

loginForm.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
	<title>Home</title>
</head>
<body>
	<c:url value="/login" var="loginUrl" />
	<form:form name="f" action="${loginUrl }" method="POST" >
		<c:if test="${param.error != null }">
			<p>아이디와 비밀번호가 잘못되었습니다.</p>
		</c:if>
		<c:if test="${param.logout != null }">
			<p>로그아웃 하였습니다.</p>
		</c:if>
		<p>
			<label for="username">아이디</label>
			<input type="text" id="id" name="id" />  // name 을 기본값이 "username"이 아니라 "id" 로 변경
		</p>
		<p>
			<label for="password">비밀번호</label>
			<input type="password" id="password" name="password" />
		</p>
		<button type="submit" class="btn">로그인</button>
	</form:form>
</body>
</html>
반응형
Comments