카테고리 없음

Spring 실습 17일차(AOP, 시큐리티)

choco2706 2024. 5. 16. 12:10

AOP(Aspect Oriented Programming : 관점 지향 프로그래밍)

AOP는 관점(Aspect)지향 프로그래밍으로, 관점을 기준으로 다양한 기능을 분리하여 보는 프로그래밍이다. 관점(Aspect)이란, 부가 기능과 그 적용처를 정의하고 합쳐서 모듈로 만든 것

 

관점지향 프로그래밍은 객체지향 프로그래밍을 보완하기 위해 쓰인다. 기존 객체(Object) 지향은 목적에 따라 클래스를 만들고 객체를 만들었다.

 

따라서 핵심 비즈니스 로직이든, 부가 기능의 로직이든 하나의 객체로 분리하는데 그치고, 그래서 이 기능들을 어떻게 바라보고 나눠쓸지에 대한 정의가 부족하다는 단점이 있다.

환경 설정

pom.xml

<!-- AOP(Aspect Oriented Programming : 관점 지향 프로그래밍) 시작 
1) aspectjrt => 이미 있으므로 생략
2) aspectjweaver => 없으므로 의존 관계를 정의
-->
<!-- https://mvnrepository.com/artifact/aspectj/aspectjweaver
1.5.4 -->
<!-- AspectJ Weaver -->
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>${org.aspectj-version}</version>
</dependency>
<!-- AspectJ Tools -->
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjtools</artifactId>
  <version>${org.aspectj-version}</version>
</dependency>
<!-- AOP(Aspect Oriented Programming : 관점 지향 프로그래밍) 끝 -->

 

AOP를 사용하기 위한 의존관계 라이브러리 등록

 

root-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:context="http://www.springframework.org/schema/context"
   xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
   xmlns:aop="http://www.springframework.org/schema/aop"
   xmlns:tx="http://www.springframework.org/schema/tx"
   xsi:schemaLocation="http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring-1.2.xsd
      http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
      http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
      http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd">

 

맨 위에서부터 beans를 덮어씌워준다. 아래는 AOP를 활성화하겠다는 코드. 맨 아래에 추가해준다.

<!-- 스프링 AOP 활성화 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!-- kr.or.ddit.aop 패키지를 컴포넌트 스캔 대상으로 등록 -->
<context:component-scan base-package="kr.or.ddit.aop"></context:component-scan>

 

위 패키지 경로(kr.or.ddit.aop)대로 AOP를 사용하기 위한 패키지를 만들어준다.

패키지 경로

 

패키지 안에 ServiceLoggerAdvice라는 이름의 Class를 하나 만들어준다.

 

@Before

조인 포인트 전에 실행됨. 예외가 발생하는 경우만 제외하고 항상 실행됨

package kr.or.ddit.aop;

import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

/*
Aspect(애스팩트) : AOP(Aspect Oriented Programming)의 단위가 되는 횡단 관심사
- 횡단 관심사(Cross-Cutting Concern) : 핵심(core) 비즈니스 로직(삼겹살구어먹기, 빵또아의 아이스크림)과
          다소 거리가 있지만,
         여러 모듈에서 공통적이고 반복적인 처리를 요구하는 내용(불판닦기, 불판교체, 빵또아의 빵)
- 횡단 관심사 분리(Separation Of Cross-Cutting Concern) : 횡단 관심사에 해당하는 
        부분(불판닦기, 불판교체, 빵또아의 빵)을 분리해서 한 곳으로 모으는 것을 의미
- Component : 골뱅이Aspect와 짝궁. component-scan시 "여기 봐주세요"라는 의미
- JoinPoint : 어드바이스가 적용될 수 있는 위치
- Advice(로그 출력 한다) : 어떤 부가기능(불판닦기)을 언제(삼겹살을 굽기 전(Before)에) 사용할지 정의
        * 언제? 
        - Before : 조인포인트(createPost()) 전에 실행. (삼겹살을 굽기 직전에)
        - After : 조인 포인트(createPost())에서 처리가 완료된 후 실행(삽겹살을 굽고 먹은 직후 실행)
        - Around : 조인 포인트(createPost()) 전후에 실행(삽겹살을 굽기 직전과 먹은 직후 실행)
        - After Returning : 조인 포인트(createPost())가 정상적으로 종료 후 실행
        - After Throwing : 조인 포인트(createPost())에서 예외 발생 시 실행. 예외가 발생안되면 실행 안함
*/
@Slf4j
@Component
@Aspect
public class ServiceLoggerAdvice {
//로보트에 : AOP대상(로그, 보안, 트랜잭션, 에러)
//포인트컷 표현식. *..*(..)
//excution : 포인트컷(대상(메소드)을 선별하는 것) 지정자
//(* : 임의의 1개의 리턴타입
//.. : 임의의 0개 이상
//kr.or.ddit.*..*(..) :
//          패키지 밑의 각각의 패키지가 있고
//         그 하위에 모든 파일/패키지
//         각각의 메소드가 있고
//         (..) : 모든 파라미터
//결론 : 포인트컷에 포함된 메서드를 대상으로 그 메서드가 실행되기 전에 로그를 출력해보자
//Before어드바이스 : 조인 포인트 전에 실행됨. 예외가 발생하는 경우만 제외하고 항상 실행됨
@Before("*execution(* kr.or.ddit,*..*(..))")
public void startLog(JoinPoint jp) {
    log.info("startLog");
    // .getSignature() : 어떤 클래스의 어떤 메서드가 실행되었는지 보여줌. 파라미터 타입은 무엇인지 보여줌
    // kr.or.ddit.service.BoardService.register(BoardVO)
    log.info("startLog : " + jp.getSignature());

    // .getArgs() : 전달 된 파라미터 정보를 보여줌
    // [BoardVO [boardNo=127,title=개똥이]]
    log.info("startLog : " + Arrays.toString(jp.getArgs()));
}

}

콘솔 실행 결과

 

 

@AfterReturning

조인 포인트(메소드) 가 정상적으로 종료한 후에 실행된다. 예외 발생 시 실행 안 됨

// 어드바이스
// 조인 포인트(메소드)가 정상적으로 종료한 후에 실행. 에러 발생시 실행 X
@AfterReturning("execution(* kr.or.ddit.*..*(..))")
public void logReturning(JoinPoint jp) {
    log.info("logReturning");

    log.info("logReturning : " + jp.getSignature());
}

콘솔창

 

 

@AfterThrowing

조인 포인트(메서드) 에서 예외 발생 시 실행, 예외가 발생 안 되면 실행 안 됨

@AfterThrowing(pointcut="execution(* kr.or.ddit.*..*(..))", throwing="e" )
public void logException(JoinPoint jp, Exception e) {
  log.info("logException");
  //.getSignature() : 어떤 클래스의 어떤 메서드가 실행되었는지 보여줌. 파라미터 타입은 무엇인지 보여줌
   // kr.or.ddit.service.BoardService.register(BoardVO)
   log.info("logException : " + jp.getSignature());
   // 에외 메세지
   log.info("logException : " + e);
}

 

 

@After

조인 포인트(메서드)를 완료한 후 실행, 예외 발생이 되더라도 항상 실행

@After("execution(* kr.or.ddit.*..*(..))")
public void endLog(JoinPoint jp) {
  log.info("endLog");

  log.info("endLog : " + jp.getSignature());

  log.info("endLog : " + Arrays.toString(jp.getArgs()));
}

콘솔창

 

 

@around

ProceedingJoinPoint : around 어드바이스에서 사용함
횡단관심사 - 포인트컷 대상 core메소드 - 횡단관심사
스프링프레임워크가 컨트롤 하고 있는 비즈니스로직 호출을 가로챔. 책임이 around 어드바이스로 전가됨
그래서 비즈니스 메소드에 대한 정보를 around 어드바이스 메소드가 가지고 있어야 하고
그 정보를 스프링 컨테이너가 around 어드바이스 메소드로 넘겨주면
ProceedingJoingPoint 객체로 받아서 around 어드바이스가 컨트롤 시 활용함

//ProceedingJoinPoint : around 어드바이스에서 사용함
// 횡단관심사 - 포인트컷 대상 core메소드 - 횡단관심사
// 스프링프레임워크가 컨트롤 하고 있는 비즈니스로직 호출을 가로챔. 책임이 around 어드바이스로 전가됨
// 그래서 비즈니스 메소드에 대한 정보를 around 어드바이스 메소드가 가지고 있어야 하고
// 그 정보를 스프링 컨테이너가 around 어드바이스 메소드로 넘겨주면
// ProceedingJoingPoint 객체로 받아서 around 어드바이스가 컨트롤 시 활용함
@Around("execution(* kr.or.ddit.*..*(..))")
public Object timeLog(ProceedingJoinPoint pjp) throws Throwable{

  //메소드 실행 직전 시간 체킹
  long startTime = System.currentTimeMillis();
  log.info("pjpStart : " + Arrays.toString(pjp.getArgs()));

  //메소드 실행
  Object result = pjp.proceed();

  //메소드 실행 직후 시간 체킹
  long endTime = System.currentTimeMillis();
  log.info("메소드 실행 시간 >>> " + pjp.getSignature().getName() + " : " + (endTime - startTime));

  //직후 시간 - 직전 시간 => 메소드 실행 시간
  log.info(pjp.getSignature().getName() + " : " + (endTime - startTime));

  return result;
}

실행 결과

 

 

트랜잭션 설정

환경 설정

root-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:context="http://www.springframework.org/schema/context"
   xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
   xmlns:aop="http://www.springframework.org/schema/aop"
   xmlns:tx="http://www.springframework.org/schema/tx"
   xsi:schemaLocation="http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring-1.2.xsd
      http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
      http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
      http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd">

위에서 미리 설정해놓은 코드

<!-- 트랜잭션 관리자의 빈을 정의 -->
<bean id="transactionManager"
  class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 애너테이션 기반의 트랜잭션 제어를 활성화 함 -->
<tx:annotation-driven/>

 

위 코드를 추가해준다.

 

employeeControleer

@Transactional

위 추가한 트랜잭션을 처리하기 위한 어노테이션

@Transactional
@RequestMapping(value="/create",method=RequestMethod.POST)
public ModelAndView createPost(
        @Validated EmployeeVO employeeVO,
        BindingResult brResult)

 

  • CheckedException or 예외가 없을 때는 Commit
  • UncheckedException이 발생하면 Rollback
  • 클래스, 메소드에 @Transactional이 선언되면 해당 클래스에 트랜잭션이 적용된 프록시 객체 생성
  • 프록시 객체는 @Transactional이 포함된 메서드가 호출될 경우, 트랜잭션을 시작하고 Commit or Rollback을 수행

 

 

예외 처리

처리되는 동안 특정 문제가 발생했을 때 처리를 중단하고 다른 처리를 시작하는 것

 

환경설정

web.xml

   HTTP 오류 코드 정리
   - 400 : Bad Request. 문법 오류(잘못 입력한 url)
   - 404* : Not Found. 요청한 문서를 찾지 못함(url확인 및 캐시 삭제가 필요한 상태)
   - 405 : Method not allowed. 메소드 허용 안됨(메소드 매핑이 안 될 때 발생)
   - 415 : 서버의 요청에 대한 승인 거부. (ContentType, Content Encoding 데이터 확인 필요)
   - 500* : 서버 내부 오류. (웹 서버가 요청사항을 수행할 수 없을 때 발생)
   - 505 : HTTP Version Not Supported.

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1" xmlns="http://java.sun.com/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
   https://java.sun.com/xml/ns/javaee/web-app_3_1.xsd">

 

맨 위에 코드를 덮어 씌워주고

<error-page>
    <error-code>400</error-code>
    <location>/error/error400</location>
</error-page>
<error-page>
    <error-code>404</error-code>
    <location>/error/error404</location>
</error-page>
<error-page>
    <error-code>500</error-code>
    <location>/error/error500</location>
</error-page>

 

하단부에 위 코드를 추가해준다.

각각의 에러코드(400,404,500)이 발생했으면 해당 에러코드의 location으로 이동시키는 코드다.

 

utils패키지에 ErrorController class를 만들어준다.

package kr.or.ddit.utils;

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

@Controller
public class ErrorController {
	
	@GetMapping("/error/error400")
	public String error400() {
		return "error/error400";
	}
	
	@GetMapping("/error/error404")
	public String error404() {
		return "error/error404";
	}
	
	@GetMapping("/error/error500")
	public String error500() {
		return "error/error500";
	}
}

 

views 안에 error폴더와 jsp들을 만들어주자.

경로

 

tiles의 조건이 맞기 때문에 적용이 될 것이다.

 

error400.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<div class="content-wrapper pt-5">
  <section class="content">
    <div class="error-page">
      <h2 class="headline text-warning">404</h2>
      <div class="error-content">
        <h3><i class="fas fa-exclamation-triangle text-warning"></i> Oops! Bad Request.</h3>
        <p>
          	잘못된 요청 정보입니다. <a href="{% link index.md %}">return to index</a>.
        </p>
      </div>
    </div>
  </section>
</div>

 

error404.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<div class="content-wrapper pt-5">
  <section class="content">
    <div class="error-page">
      <h2 class="headline text-warning">404</h2>
      <div class="error-content">
        <h3><i class="fas fa-exclamation-triangle text-warning"></i> Oops! Page not found.</h3>
        <p>
          	페이지를 찾을 수 없습니다. <a href="{% link index.md %}">return to 메인</a>.
        </p>
      </div>
    </div>
  </section>
</div>

오류 페이지(404)

 

error500.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<section class="content">
      <div class="error-page">
        <h2 class="headline text-danger">500</h2>

        <div class="error-content">
          <h3><i class="fas fa-exclamation-triangle text-danger"></i> Oops! Something went wrong.</h3>

          <p>
            We will work on fixing that right away.
            Meanwhile, you may <a href="../../index.html">return to dashboard</a> or try using the search form.
          </p>

          <form class="search-form">
            <div class="input-group">
              <input type="text" name="search" class="form-control" placeholder="Search">

              <div class="input-group-append">
                <button type="submit" name="submit" class="btn btn-danger"><i class="fas fa-search"></i>
                </button>
              </div>
            </div>
            <!-- /.input-group -->
      </form>
    </div>
  </div>
  <!-- /.error-page -->

</section>

오류 페이지(500)

 

 

예외 타입을 사용하여 오류 페이지 설정

web.xml

<!-- 예외 타입을 사용한 에러 페이지 설정 시작 
  웹 컨테이너(tomcat서버) 설정 파일(web.xml)의 exception-type 태그 요소에 예외 타입을 설정하고
location 요소에 이동 대상 페이지 및 URI를 지정함

IOException, SQLException, NullPointerException, ArrayIndexOutOfBoundsException,
ArtimeticException(0으로 나눌경우)
-->
<error-page>
    <exception-type>java.lang.ArtimeticException</exception-type>
    <location>/error/errorException</location>
</error-page>
<!-- 예외 타입을 사용하여 오류 페이지 설정 끝 -->

 

 

ErrorController.class

@GetMapping("/error/errorException")
public String errorException() {
    return "error/errorException";
}

 

 

ErrorException.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<div class="content-wrapper pt-5">
  <section class="content">
    <div class="error-page">
      <h2 class="headline text-warning">errorException</h2>
      <div class="error-content">
        <h3><i class="fas fa-exclamation-triangle text-warning"></i> Oops! Bad Request.</h3>
        <p>
          	숫자를 0으로 나눌 수 없습니다. <a href="/">return to 메인</a>.
        </p>
      </div>
    </div>
  </section>
</div>

 

LprodController의 list 부분에 일부러 오류를 내보자

System.out.println(10/0);

error Exception 페이지

 

500에러가 떠야 하지만 우선순위가 errorException이 더 높기 때문에 errorException 오류 처리 페이지로 넘어온다

 

 

에러처리 어노테이션

exception 패키지와 클래스를 만들어주자

 

package kr.or.ddit.exception;

import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import lombok.extern.slf4j.Slf4j;

// 스프링 컨트롤러에서 발생하는 예외를 처리하는 클래스임을 명시함
// web.xml, errorPage. try-catch ... 말고 
@Slf4j
@ControllerAdvice
public class CommonExceptionHandler {
	
	// 괄호 안에 설정한 예외 타입을 해당 메서드가 처리한다는 의미
	// IOException, SQLException, NullPointerException,
    // ArrayIndexOutOfBoundsException,
    // ArithmeticException
	@ExceptionHandler(ArithmeticException.class)
	public String handle(Exception e, Model model) {
		log.error("CommonExceptionHandler >>>> handle : " + e.toString());
		
		// 예외에 대한 내용을 Model 객체를 이용해서 전달하며
		// 뷰(view) 화면에서 출력 가능
		model.addAttribute("exception", e);
		
		return "error/errorCommon";
	}
}

 

@ControllerAdvice : 스프링 컨트롤러에서 발생하는 예외를 처리하는 클래스임을 명시함

 

@ExceptionHandller : 괄호 안에 설정한 예외 타입을 해당 메서드가 처리한다는 의미,

IOException, SQLException, NullPointerException등...

 

ArithmeticException : 나누기 0 을 하면 발생하는 에러라고 보면 된다.

 

errorCommon.jsp

마찬가지로 tiles가 적용되고 있다.

<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<p>${exception.getMessage()}</p>
<p>
	<c:forEach var="stack" items="${exception.getStackTrace()}" varStatus="stat">
		<h3>${stack.toString()}</h3>
	
	</c:forEach>
</p>

forEach로 꺼낸 오류들

 

 

404 오류 처리

404를 프로그래밍적으로 처리하고 싶다면 404 발생 시 예외를 발생시키도록 설정해야 한다.
(기본적으로 404는 exception 상황이 아니다.) 


이를 위해 web.xml에서 DispatcherServlet을 등록할 때 throwExceptionIfNoHandlerFound 

초기화 파라미터를 true로 설정한다

<!-- 404오류를 처리할 수 있도록 설정 -->
<init-param>
    <param-name>throwExceptionfNoHandleFound</param-name>
    <param-value>true</param-value>
</init-param>

 

다시 CommonExceptionHandler로 돌아와서

@ExceptionHandler(NoHandlerFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public String handle404(Exception e) {
    log.error("CommonExceptionHandler >>>> handle404 : " + e.toString());

    return "error/error404";
}

 

 

 

시큐리티

보안기능을 구현하는 사용되는 프레임워크

필터기반으로 동작하기 때문에 스프링 MVC와 분리도어 동작한다.

 

기본 보안 기능

  • 인증(Aythentication) : 애플리케이션 사용자의 정당성을 확인한다.
  • 인가(Authorization) : 애플리케이션의 리소스나 처리에 대한 접근을 제어한다.

 

환경설정

pom.xml

<!-- 스프링 시큐리티 라이브러리 의존관계 정의 시작 -->
      <!-- 스프링 시큐리티를 웹에서 동작하도록 해줌 -->
      <!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-web -->
      <dependency>
          <groupId>org.springframework.security</groupId>
          <artifactId>spring-security-web</artifactId>
          <version>5.0.7.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>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을 수정해준다.

<!-- 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>

 

spring폴더에 xml을 하나 만들어준다.

MaBatis로 만들어야 함

 

이름은 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">
</beans>

 

web.xml에 아래 코드 추가

<!-- 스프링 시큐리티가 제공하는 서블릿 필터 클래스를 서블릿 컨테이너에 등록 -->
    <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을 이어서 작성해준다.

 

 

웹 화면 접근 정책

 

views 폴더에 board폴더와 notice 폴더를 만들어 준 뒤 각각 list, register.jsp를 만들어준다.

board / list와 notice / list는 누구나 접속 가능하게, board / register는 로그인한 회원, notice / register는 로그인한 관리자만 접속되게 만들 예정이다.

각각의 list, register.jsp
각각의 controller

 

 

접근 제한 설정

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>
         <!-- URI 패턴으로 접근 제한을 설정함 -->
         <security:intercept-url pattern="/board/list" access="permitAll"/>                   <!-- access="permitAll" : 누구나 접근가능 -->
         <security:intercept-url pattern="/board/register" access="hasRole('ROLE_MEMBER')"/>      <!-- 회원만 접근가능 -->
         <security:intercept-url pattern="/notice/list" access="permitAll"/>                  <!-- access="permitAll" : 누구나 접근가능 -->
         <security:intercept-url pattern="/notice/register" access="hasRole('ROLE_ADMIN')"/>      <!-- 관리자만 접근가능 -->
         
      
         <!-- 폼 기반 인증 기능을 사용 -->
         <security:form-login/>
      </security:http>
      
      <!-- 지정된 아이디와 패스워드로 로그인이 가능하도록 설정-->
	   <!-- 스프링 시큐리니티 5부터 기본적으로 PasswordEncoder를 지정해야 하는데,
	      그 이유는 사용자 테이블(USERS)에 비밀번호를 암호화하여 저장해야 하므로..
	      우리는 우선 비밀번호를 암호화 처리 하지 않았으므로
	      암호화 하지 않는 PasswordEncoder를 직접 구현하여 지정하기로 함
	      noop : no option password
	    -->
      
      <!-- authentication : 인증 (로그인) -->
      <security:authentication-manager>
    	  
      </security:authentication-manager>
      
</beans>

notice,board/register

 

notice, board의 register에 들어가려 하면 login창이 자동으로 연결된다.

 

이제 각 id와 password를 설정해야한다.

<!-- authentication : 인증 (로그인) -->
<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>
</security:authentication-manager>

 

member에는 ROLE_MEMBER의 권한이 부여되어있고,

admin에는 ROLE_MEMBER, ROLE_ADMIN의 권한이 부여되어 있다.

 

authorities에는 위의 access="hasRole(' ')"에서 괄호 안에 들어있는 값을 적어주어야 한다.

admin 권한으로 로그인
실행 결과

 

board / register는 member권한을 가져야 들어갈 수 있고, admin은 두 개의 권한을 동시에 가지고 있으므로 접속이 가능하다.

 

※ 접근 실패 예시

member 아이디는 admin권한이 없어 notice / register에 접근할 수 없다.

접근 시도
403에러가 뜨며 접근이 금지된다.

 

고객의 입장에서는 403오류는 보기 싫기 때문에 해당 오류가 나면 페이지를 다른곳으로 보내는 처리를 해보자

 

SecurityController를 하나 만들어주자

package kr.or.ddit.controller;

import org.apache.catalina.authenticator.SpnegoAuthenticator.AuthenticateAction;
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(AuthenticateAction auth, Model model) {
		// 인증과 관련된 정보를 확인해보자
		log.info("access Denied : " + auth);
		
		model.addAttribute("msg","Access Denied");
		
		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>
<h2>${SPRING_SECURITY_403_EXCEPTION.getMessage()}</h2>
<h2>${msg}</h2>

 

다시 로그인을 시도해보자

※ 로그아웃 기능이 없기 때문에 서버를 재시작하고, 브라우저까지 전부 싹 껐다가 켜야 한다.

 

accessError 페이지

 

로그인 실패 시 accessError페이지로 넘어오게 된다.

 

 

17일차 종료