반응형
스프링 시큐리티

 

: 애플리케이션에서 보안 기능을 구현하는 데 사용되는 프레임 워크

 

 

 

  • 기본 보안 기능

- 인증

: 사용자의 정당성 확인

ex) 로그인

 

- 인가

: 리소스나 처리에 대한 접근 제어

ex) 권한

 

 

 

설정

 

 

pom.xml

</dependencies> 안에 추가 (의존 라이브러리 4개 추가)

		<!-- 스프링 시큐리티 설정을 도와줌 -->
		<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-config -->
		<dependency>
		    <groupId>org.springframework.security</groupId>
		    <artifactId>spring-security-config</artifactId>
		    <version>5.0.7.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>5.0.7.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>5.0.7.RELEASE</version>
		</dependency>
		
		<!-- 스프링 시큐리티 라이브러리 의존관계 정의 시작 -->

 

 

web.xml

root-context.xml의 </context-param> 안을 변경 (스프링 시큐리티 설정 파일 지정 후)

	<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
	<!-- contextConfigLocation에 스프링 시큐리티 설정 파일을 지정 -->
	<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>

 

</web-app> 안에 추가 (필터 낌)

	<!-- 스프링 시큐리티가 제공하는 서블릿 필터 클래스를 서블릿 컨테이너에 등록함 -->
	<!-- filter과  filter-mapping의 filter-name은 서로 같아야 함-->
	<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>

 

 

security-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<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/security http://www.springframework.org/schema/security/spring-security.xsd
      http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

	<security:http>
		<!-- 폼 기반 인증 기능을 사용 -->
		<security:form-login/>
	</security:http>
	
	<!-- authentication : 인증(로그인) -->
	<security:authentication-manager>
	</security:authentication-manager>
</beans>

 

=> 재기동 시 콘솔 창에서 에러나지 않으면 문제 없음

 

 

 

실습

 

  • 웹화면 접근 정책

 

 

BoardController.java

package kr.or.ddit.controller;

import org.springframework.http.HttpStatus;

import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import kr.or.ddit.dao.BoardDao;
import kr.or.ddit.vo.BoardVO;
import lombok.extern.slf4j.Slf4j;


@Slf4j
@Controller
@RequestMapping("/board")
public class BoardController {
	@RequestMapping(value="/register", method=RequestMethod.GET)
	public String registerForm() {
		log.info("registerForm에 왔다");
		
		//ModelAndView가 없음
		//mav.setViewname("board/register") 생략
		
		//forwarding : jsp
		return "board/register";
	}

	@RequestMapping("/list")
	public String list() {
		log.info("list에 왔다");
		
		// forwarding : /views/board/list.jsp
		return "board/list";
	}
}

 


list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<h2>누구나 접근 가능</h2>
<h3>/board/list.jsp</h3>

 


register.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<h2>로그인 한 회원만 접근 가능</h2>
<h3>board/register.jsp</h3>

 

 

NoticeController.java

package kr.or.ddit.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import lombok.extern.slf4j.Slf4j;

@RequestMapping("/notice")
@Slf4j
@Controller
public class NoticeController {

	//요청 URI : /notice/list
	@GetMapping("/list")
	public String list() {
		//forwarding : jsp
		return "notice/list";
	}
	
	//요청 URI : /notice/register
	@GetMapping("/register")
	public String register() {
		//forwarding : jsp
		return "notice/register";
	}
}

 

 

notice/list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<h2>누구나 접근 가능</h2>
<h3>/notice/list.jsp</h3>

 

 

notice/register.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<h2>로그인 한 관리자만 접근 가능</h2>
<h3>/notice/register.jsp</h3>

 

결과 화면1

 

결과 화면2

 

결과 화면3

 

결과 화면4

 

 

 

접근 제한 설정

 

 

security-context.xml

<security:http> 안에 추가

		<!-- URI 패턴으로 접근 제한을 설정함 -->
		<!-- URL을 가로챔 -->
		<!-- 누구나 접근 가능 -> 생략 가능 -->
		<security:intercept-url pattern="/board/list" access="permitAll"/> <!-- permitAll : 누구나 접근 가능 -->
		<security:intercept-url pattern="/board/register" access="hasRole('ROLE_MEMBER')" /> <!-- ROLE_MEMBER 권한을 가졌을 때 : 회원만 접근 -->
		<security:intercept-url pattern="/notice/list" access="permitAll" />
		<security:intercept-url pattern="/notice/register" access="hasRole('ROLE_ADMIN')" /> <!-- ROLE_ADMIN 권한을 가졌을 때 : 관리자만 접근 -->

 

결과 화면5 : localhost/notice/register URL 입력 시 화면

 

 

 

로그인 처리

 

 

스프링 시큐리니티 5부터 기본적으로 PasswordEncoder를 지정해야 하는데, 그 이유는 사용자 테이블(USERS)에 비밀번호를 암호화하여 저장해야함.


우리는 우선 비밀번호를 암호화 처리 하지 않았으므로 암호화 하지 않는 PasswordEncoder를 직접 구현하여 지정하기로 함
noop : no option password

 

 

security-context.xml
</security:authentication-manager> 안에 추가

		<!-- 지정된 아이디와 패스워드로 로그인이 가능하도록 설정함 -->
		<security:authentication-provider> <!-- 인증 제공자 -->
			<security:user-service> <!-- 사용자 설정 -->
				<security:user name="member" password="{noop}1234" authorities="ROLE_MEMBER"/>
				<security:user name="admin" password="{noop}1234" authorities="ROLE_MEMBER,ROLE_ADMIN"/>
			</security:user-service>
		</security:authentication-provider>

 

결과 화면6-1 : localhost/notice/register ❘ member와 admin으로 로그인 가능

 

결과 화면6-2

 

=> 출력 결과

/board/register : admin 접근 o, member 접근 o
/notice/register : admin 접근 o, member 접근 x

 

 

 

접근 거부 처리

 

 

security-context.xml
</security:http> 안에 추가

		<!-- 접근 거부 처리자의 URI를 지정 -->
		<security:access-denied-handler error-page="/accessError"/>

 

 

SecurityController.java

package kr.or.ddit.controller;

import org.apache.catalina.authenticator.SpnegoAuthenticator.AuthenticateAction;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
public class SecurityController {
	
	//인증(Authentication) 거부
	//요청URI : /accessError
	@GetMapping("/accessError")
	public String accessError(Authentication auth, Model model) {
		//인증과 관련된 정보를 확인
		log.info("access Denied : " + auth);
		
		model.addAttribute("msg", "Access Denied");
		
		//forwarding : jsp
		return "accessError";
	}
}

 

 

accessError.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<h3>Access Denied</h3>
<!-- 403 오류가 났을 때 자동으로 발생 후 메시지 출력해줌 (자동) -->
<h2>${SPRING_SECURITY_403_EXCEPTION.getMessage()}</h2>
<h2>${msg}</h2>

 

결과 화면7-1 : localhost/notice/register 의 권한이 없는 member 로 로그인 진행

 

결과 화면7-2

 

 

 

사용자 정의 접근 거부 처리자

 

 

security-context.xml
</security:http> 안에 추가 및 접근 거부 처리자의 URI를 지정 주석처리

		<!-- 접근 거부 처리자의 URI를 지정 -->
<!-- 		<security:access-denied-handler error-page="/accessError"/> -->

		<!-- 등록한 사용자 정의 bean을 접근 거부 처리자로 지정함 -->
		<security:access-denied-handler ref="customAccessDenied"/>
		
	</security:http>


</beans> 안에 추가

	<!-- CustomAccessDeniedHandler customAccessDenied = new CustomAccessDeniedHandler(); 와 같은 문장임 -->
	<bean id="customAccessDenied" 
		class="kr.or.ddit.security.CustomAccessDeniedHandler"></bean>

 

 

CustomAccessDeniedHandler.java

package kr.or.ddit.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.slf4j.Slf4j;

@Slf4j
public class CustomAccessDeniedHandler implements AccessDeniedHandler{

	@Override
	public void handle(HttpServletRequest request, HttpServletResponse response,
			AccessDeniedException accessDeniedException) throws IOException, ServletException {
		log.info("handle");
		
		//DB작업
		//로그작업
		//메시지발송
		
		//redirect : 새로운 URI를 요청
		response.sendRedirect("/accessError");
	}
	
	/*
	공지사항 등록 화면(/notice/register)은 
	일반회원(member/java)이 접근할 수 없는 페이지이고,
	관리자(admin/java)만 접근 가능하므로..
	지정된 접근 거부 처리자(CustomAccessDeniedHander)에서 
	접근 거부 처리 페이지(/accessError)로 리다이렉트 시킴
	*/
	
}

 

결과 화면8 : localhost/notice/register ❘ 아이디 : member 비밀번호 : 1234로 로그인 시 화면

 

결과 화면9 : 접근 거부 처리 컨트롤러로 갔다는 것을 확인할 수 있음

 

 

 

사용자 정의 로그인 페이지

 

 

security-context.xml

</security:http> 안에 수정 및 추가

<!-- 폼 기반 인증 기능을 사용 -->
<!-- <security:form-login/> -->

<!-- 사용자가 정의한 로그인 페이지의 URI를 지정함 -->
<security:form-login login-page="/login"/>

 

 

LoginController.java

package kr.or.ddit.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class LoginController {
	
	@GetMapping("/login")
	public String loginForm() {
		//forwarding : jsp
		// /views/ + loginForm + ".jsp";
		return "loginForm";
	}
}

 

 

loginForm.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<div class="login-box" style="margin:auto;">
	<div class="card">
		<div class="card-body login-card-body">
			<p class="login-box-msg">Sign in to start your session</p>
			<form action="../../index3.html" method="post">
				<div class="input-group mb-3">
					<input type="email" class="form-control" placeholder="Email">
					<div class="input-group-append">
						<div class="input-group-text">
							<span class="fas fa-envelope"></span>
						</div>
					</div>
				</div>
				<div class="input-group mb-3">
					<input type="password" class="form-control" placeholder="Password">
					<div class="input-group-append">
						<div class="input-group-text">
							<span class="fas fa-lock"></span>
						</div>
					</div>
				</div>
				<div class="row">
					<div class="col-8">
						<div class="icheck-primary">
							<input type="checkbox" id="remember"> <label
								for="remember"> Remember Me </label>
						</div>
					</div>
					<div class="col-4">
						<button type="submit" class="btn btn-primary btn-block">Sign
							In</button>
					</div>
				</div>
			</form>
		</div>
	</div>
</div>

 

결과 화면10

 

 

 

  • 기능 추가

 

필수로 설정해야 하는 것

 

 

loginForm.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>

<div class="login-box" style="margin:auto;">
	<div class="card">
		<div class="card-body login-card-body">
			<p class="login-box-msg">Sign in to start your session</p>
			<form action="/login" method="post">
				<div class="input-group mb-3">
					<!-- 아이디 
					name="username" 으로 꼭 써야함 -->
					<input type="text" name="username" id="username" class="form-control" placeholder="아이디">
					<div class="input-group-append">
						<div class="input-group-text">
							<span class="fas fa-envelope"></span>
						</div>
					</div>
				</div>
				<div class="input-group mb-3">
				<!-- 비밀번호
				name="password" 으로 꼭 써야함 -->
					<input type="password" name="password" id="password" class="form-control" placeholder="비밀번호">
					<div class="input-group-append">
						<div class="input-group-text">
							<span class="fas fa-lock"></span>
						</div>
					</div>
				</div>
				<div class="row">
					<div class="col-8">
						<div class="icheck-primary">
							<input type="checkbox" id="remember"> <label
								for="remember"> Remember Me </label>
						</div>
					</div>
					<div class="col-4">
						<button type="submit" class="btn btn-primary btn-block">Sign In</button>
					</div>
				</div>
				
				<!-- csrf : Cross Site Request Forgery 
				sec: 를 사용하기 위해선 상단에 추가해야함
				taglib prefix="sec" uri="http://www.springframework.org/security/tags -->
				<sec:csrfInput/>
			</form>
		</div>
	</div>
</div>

 

결과 화면11 : http://localhost/notice/register 로 들어올 시 자동으로 이동됨

 

 

반응형