Spring 실습 17일차(AOP, 시큐리티)
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>
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>
예외 타입을 사용하여 오류 페이지 설정
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);
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>
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을 하나 만들어준다.
이름은 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는 로그인한 관리자만 접속되게 만들 예정이다.
접근 제한 설정
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에 들어가려 하면 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(' ')"에서 괄호 안에 들어있는 값을 적어주어야 한다.
board / register는 member권한을 가져야 들어갈 수 있고, admin은 두 개의 권한을 동시에 가지고 있으므로 접속이 가능하다.
※ 접근 실패 예시
member 아이디는 admin권한이 없어 notice / register에 접근할 수 없다.
고객의 입장에서는 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페이지로 넘어오게 된다.
17일차 종료