카테고리 없음

Spring 08 - Spring Web Security 上

나주나주 2024. 3. 11. 10:39

 

로그인 체크

: 쿠키(클라이언트)나 세션(서버)를 이용하는 방식으로, 사용자의 권한이나 등급에 기반을 둔다

 

Security Context 시큐리티 객체

  • Authentication 객체가 저장되는 보관소로 필요 시 Authentication 객체를 꺼내 쓸 수 있다
  • 인증 완료 시 HttpSession에 저장, 전역적 참조 가능

 

Spring Security

: 스프링 시큐리티는 서블릿의 필터와 Interceptor(스프링에서 필터와 유사한 역할)를 이용해서 처리한다

 

 

좌: filter로 실행   우: interceptor로 실행

Spring Security는 인터셉터와 필터를 이용하여 컨텍스트를 생성해서 처리

관여  filter 필터 interceptor 인터셉터
명칭 Servlet Context 서블릿 컨텍스트 Spring Security 스프링 시큐리티
관리 서블릿 자원 빈 객체
분류 스프링과는 무관(관리 X)
서블릿 컨텍스트에 속한다
스프링 컨텍스트 내 동작(관리 O)
컨텍스트에 포함된 빈 이용 → 다양한 방식의 인증 처리

 

Authentication 인증 객체 인터페이스

  • 인증 용도 또는 인증 후 세션에 담기 위한 용도
  • 인증 시 id와 pw를 담고 인증 검증을 위해 전달되어 사용
  • 인증 후 최종 인증 결과(user 객체, 권한 정보)를 담고 SecurityContext에 저장되어 전역적 참조 가능
Authentication 구조 설명
Principal(Object 타입) 사용자 아이디 혹은 User 객체 저장
Credentials 사용자 비밀번호
authorities 인증된 사용자의 권한 목록
details 인증 부과 정보
Authenticated 인증 여부

 

1. 초기 설정

pom.xml

<!-- 시큐리티 코어 -->
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-core</artifactId>
			<version>5.0.6.RELEASE</version>
		</dependency>
		<!-- 시큐리티 config -->
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-config</artifactId>
			<version>5.0.6.RELEASE</version>
		</dependency>
		<!-- 시큐리티 web -->
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-web</artifactId>
			<version>5.0.6.RELEASE</version>
		</dependency>
		<!-- 시큐리티 taglibs -->
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-taglibs</artifactId>
			<version>5.0.6.RELEASE</version>
		</dependency>

 

2. security-context.xml 생성

5.0의 XML Namespaces

<!-- 5.0 네임스페이스 버그 해결 -->
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:security="http://www.springframework.org/schema/security"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
	http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">
    
    <security:http> <!-- 스프링 시큐리티 시작 지점 -->
    	<security:form-login/>
    </security:http>
    
    <security:authentication-manager> <!-- 권한 매니저 -->
    </security:authentication-manager>
</beans>

 

3. web.xml 설정

스프링 시큐리티를 MVC에서 사용하기 위해 필터를 이용, 스프링 동작에 관여하도록 설정

	<!-- http://localhost/*에 반응하는 필터 -->
    <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> 
    
    <!-- 빈 springSecurityFilterChain이 설정되지 않았음 -->
    <context-param>
		<param-name>contextConfigLocation</param-name>
        <!-- security-context.xml 로딩 -->
		<param-value>/WEB-INF/spring/root-context.xml
					 /WEB-INF/spring/security-context.xml
		</param-value>
	</context-param>

 

4. URI 설계

SampleController.java (맵핑)

@Controller
@RequestMapping("/sample/*") // http://localhost/sample/
@Log4j2
public class SampleController {
	// 시큐리티 분기 담당

	@GetMapping("/all")
	public void doAll() { //void의 경우 동일한 이름의 jsp 찾음
		log.info("do all can access everybody...........");
		log.info("all 메서드 실행중............");
	}

	@GetMapping("/member")
	public void doMember() {
		log.info("멤버 메서드 실행중............");
		log.info("로그인한 멤버만 진입 가능............");
	}
	
	@GetMapping("/admin")
	public void doAdmin() { //void인 경우 매핑과 같은 jsp를 찾음
		log.info("어드민 메서드 실행중............");
		log.info("관리자만 진입 가능............");
	}

 

2. 인증과 권한 부여(인가)

Authentication 인증 Authorization 인가
자신을 증명 남이 자격을 부여

 

다양한 방식의 인증 처리

 

 


로그인과 로그아웃 처리

 

security-context.xml

<security:http>
	<!-- 접근 제한 설정 모든/멤버/관리자 -->
	<security:intercept-url pattern="/sample/all" access="permitAll"/>
    <!-- ROLE_MEMBER 권한이 있는 사용자만 접근 권한(access), 호출 시 로그인 페이지 이동 -->
    <security:intercept-url pattern="/sample/member" access="hasRole('ROLE_MEMBER')"/>
    
    <security:form-login />
    
<security:http>

<security:authentication-manager>
	<security:authentication-provider>
    	<security:user-service>
		
        	<!-- PasswordEncoder의 지정이 없어 에러 발생, PasswordEncoder 지정 or 임시적인 noop 지정 -->
		<security:user name="member" password="{noop}member" authorities="ROLE_MEMBER"/>
        
			<!-- 2개의 권한 -->
		<security:user name="admin" password="{noop}admin" authorities="ROLE_MEMBER, ROLE_ADMIN"/>            
        <security:user-service>
        
    </<security:authentication-provider>
</security:authentication-manager>

Tip) 스프링 시큐리티에서 User인증 정보와 권한을 가진 객체로 일반적인 경우에 사용하는 사용자와는 다른 의미입니다

Tip) UserDetailsService: 인증과 권한에 대한 실제 처리 담당 객체

 

CommonController.java

@GetMapping("/customLogin")
	public void loginInput(String error, String logout, Model model) {
		log.info("error: " + error);
		log.info("logout: " + logout);
		
		if (error != null) {
			model.addAttribute("error", "로그인 에러. 계정을 확인하세요");
		}
		
		if (logout != null) { //logout객체가 넘어올 때
			model.addAttribute("logout", "logout 실행");
		}
	}

 

accessError.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 uri="http://www.springframework.org/security/tags" prefix="sec" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h1>권한이 없는 페이지입니다.</h1>
	<h2><c:out value="${SPRING_SECURITY_403_EXCEPTION.getMessage()}"/></h2>
	<h3><c:out value="${msg}"/></h3>
</body>
</html>

 

1. 접근 제한에 대한 AccessDeniedHandler 인터페이스를 구현하는 경우

: 쿠키나 세션에 특정한 작업, HttpServletResponse에 특정한 헤더 정보 추가

 

org.zerock.security > CustomAccessDeniedHandler.java

//security가 가진 AccessDeniedHandler 인터페이스를 직접 구현
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
	@Override
	public void handle(HttpServletRequest request, HttpServletResponse response,
			AccessDeniedException accessDeniedException) throws IOException, ServletException {
		log.error("CustomAccessDeniedHandler의 handler() 실행");
		log.error("Redirect.........");
		response.sendRedirect("/accessError");
	} 
}

 

2. 커스텀 로그인 페이지

security-context.xml

		<!-- 접근 제어 자동 처리, 권한 없는 url 접근 제어 -->	
		<!-- <security:access-denied-handler error-page="/accessError"/> --> 
		
		<!-- 접근 제어 수동 처리 -->
		<security:access-denied-handler ref="customAccessDenied" /> 
		<security:form-login login-page="/customLogin"/> <!-- 수동으로 만든 로그인 폼 -->
	</security:http>

login-page 속성의 uri는 반드시 GET 방식으로 접근하도록 함

 

CommonController.java

	@GetMapping("/customLogin")
	public void loginInput(String error, String logout, Model model) {
		log.info("error: " + error);
		log.info("logout: " + logout);
		
		if (error != null) {
			model.addAttribute("error", "로그인 에러. 계정을 확인하세요");
		}
		
		if (logout != null) { //logout객체가 넘어올 때
			model.addAttribute("logout", "logout 실행");
		}

 

customLogin.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h1>내가 만듦!!!!!!!!</h1>
	<!-- 에러, 로그아웃 메시지 출력 -->
	<h2>
		<c:out value="${error}" />
	</h2>
	<h2>
		<c:out value="${logout}" />
	</h2>

	<form method="post" action="login">
		<div>
			<input type="text" name="username" value="admin">
		</div>
		<div>
			<input type="password" name="password" value="admin">
		</div>
		<div>
			<input type="submit">로그인
		</div>
		<!-- csrf 해킹 공격을 막는 토큰 변조 기술 -->
		<input type="hidden" name="${_csrf.parameterName }"
			value="${_csrf.token }" />
	</form>
</body>
</html>

에러 메시지와 로그아웃 메시지를 파라미터로 사용

 

3. CSRF(Cross-site request forgery)

  • CSRF 공격
  • 스프링 시큐리티가 적용된 사이트의 모든 POST 방식에는 CSRF토큰이 사용되는데 사이트간 위조 방지를 목적으로 한다. CSRF공격은 서버에서 받아들이는 정보가 사전 조건을 검증하지 않는다는 점을 이용한다. 이는 사용자의 요청에 대한 출처를 의미하는 referer 헤더를 체크하거나 REST 방식에서 사용되는 PUT, DELETE와 같은 방식을 이용하는 방법을 고려해 볼 수 있다.

 

  • CSRF 토큰
  • 서버에 데이터를 전송할 때 CSRF 토큰을 같이 전송하는데, 브라우저에서 전송된 토큰의 값과 서버가 보관하고 있는 토큰의 값을 비교한다

임의로 변하는 토큰값

 

<!-- csrf 해킹 공격을 막는 토큰 변조 기술 -->
<input type="hidden" name="${_csrf.parameterName }"
value="${_csrf.token }" />

 

4. 로그인 성공과 AuthenticationSuccessHandler

 

AuthenticationSuccessHandler.java

@Log4j2
public class CustomLoginSuccessHandler implements AuthenticationSuccessHandler {

	@Override
	public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp,
			Authentication auth) throws IOException, ServletException {
		log.warn("Login Success");
		
		//권한명
		List<String> roleNames = new ArrayList<>();
		
		auth.getAuthorities().forEach(authority -> {
			roleNames.add(authority.getAuthority());
		});
		
		log.warn("ROLE NAMES:" + roleNames);
		
		if (roleNames.contains("ROLE_ADMIN")) {
			resp.sendRedirect("/sample/member");
			return; //권한이 있다면 회원열람페이지 이동 
		}
		
		if (roleNames.contains("ROLE_MEMBER")) {
			resp.sendRedirect("/sample/member");
			return;
		}
		
		resp.sendRedirect("/");
	}
}

 

P642

 

 

P644 ㄱㄴㅈ ㅡㅡ^