관리 메뉴

거니의 velog

231211_SPRING 2 (14-1) 본문

대덕인재개발원_웹기반 애플리케이션

231211_SPRING 2 (14-1)

Unlimited00 2023. 12. 11. 08:23

[pom.xml]

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>kr.or</groupId>
	<artifactId>ddit</artifactId>
	<name>SecurityTest</name>
	<packaging>war</packaging>
	<version>1.0.0-BUILD-SNAPSHOT</version>
	<properties>
		<java-version>1.8</java-version>
		<org.springframework-version>5.3.25</org.springframework-version>
		<org.aspectj-version>1.6.10</org.aspectj-version>
		<org.slf4j-version>1.6.6</org.slf4j-version>
	</properties>
	<dependencies>
		<!-- Spring -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${org.springframework-version}</version>
			<exclusions>
				<!-- Exclude Commons Logging in favor of SLF4j -->
				<exclusion>
					<groupId>commons-logging</groupId>
					<artifactId>commons-logging</artifactId>
				 </exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${org.springframework-version}</version>
		</dependency>
				
		<!-- AspectJ -->
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjrt</artifactId>
			<version>${org.aspectj-version}</version>
		</dependency>	
		
		<!-- Logging -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>${org.slf4j-version}</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>jcl-over-slf4j</artifactId>
			<version>${org.slf4j-version}</version>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>${org.slf4j-version}</version>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>1.2.15</version>
			<exclusions>
				<exclusion>
					<groupId>javax.mail</groupId>
					<artifactId>mail</artifactId>
				</exclusion>
				<exclusion>
					<groupId>javax.jms</groupId>
					<artifactId>jms</artifactId>
				</exclusion>
				<exclusion>
					<groupId>com.sun.jdmk</groupId>
					<artifactId>jmxtools</artifactId>
				</exclusion>
				<exclusion>
					<groupId>com.sun.jmx</groupId>
					<artifactId>jmxri</artifactId>
				</exclusion>
			</exclusions>
			<scope>runtime</scope>
		</dependency>

		<!-- @Inject -->
		<dependency>
			<groupId>javax.inject</groupId>
			<artifactId>javax.inject</artifactId>
			<version>1</version>
		</dependency>
				
		<!-- Servlet -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>3.1.0</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>javax.servlet.jsp</groupId>
			<artifactId>jsp-api</artifactId>
			<version>2.1</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.2</version>
		</dependency>
	
		<!-- Test -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.7</version>
			<scope>test</scope>
		</dependency>        
	</dependencies>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-eclipse-plugin</artifactId>
                <version>2.9</version>
                <configuration>
                    <additionalProjectnatures>
                        <projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
                    </additionalProjectnatures>
                    <additionalBuildcommands>
                        <buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
                    </additionalBuildcommands>
                    <downloadSources>true</downloadSources>
                    <downloadJavadocs>true</downloadJavadocs>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.5.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <compilerArgument>-Xlint:all</compilerArgument>
                    <showWarnings>true</showWarnings>
                    <showDeprecation>true</showDeprecation>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.2.1</version>
                <configuration>
                    <mainClass>org.test.int1.Main</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

[web.xml]

<?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">

	<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>/WEB-INF/spring/root-context.xml</param-value>
	</context-param>
	
	<!-- Creates the Spring Container shared by all Servlets and Filters -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<!-- Processes application requests -->
	<servlet>
		<servlet-name>appServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
		
	<servlet-mapping>
		<servlet-name>appServlet</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
	
	<!--  
		한글 처리를 위한 UTF-8 필터 등록
		JSP나 서블릿 처리할 때마다 넘겨받은 request를 setCharacterEncoding으로 UTF-8 설정했던 부분을 필터로 대체함
	-->
	<filter>
		<filter-name>encordingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
		<init-param>
			<param-name>forceEncoding</param-name>
			<param-value>true</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>encordingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

</web-app>

- http://localhost/


package kr.or.ddit.controller;

public class SecurityController {

	/*
	 * [ 18장 : 스프링 시큐리티 ]
	 * 
	 * 		1. 스프링 시큐리티 소개
	 * 
	 * 			- 애플리케이션에서 보안 기능을 구현하는데 사용되는 프레임워크이다.
	 * 			- 스프링 시큐리티는 필터 기반으로 동작하기 때문에 스프링 MVC와 분리되어 동작한다.
	 * 
	 * 			# 기본 보안 기능
	 * 			- 인증 (Authentocation)
	 * 				> 애플리케이션 사용자의 정당성을 확인한다.
	 * 
	 * 			- 인가 (Authorization)
	 * 				> 애플리케이션의 리소스나 처리에 대한 접근을 제어한다.
	 * 
	 * 			# 시큐리티 제공 기능
	 * 			- 세션 관리
	 * 			- 로그인 처리
	 * 			- CSRF 토큰 처리
	 * 			- 암호화 처리
	 * 			- 자동 로그인
	 * 
	 * 			*** CSRF 용어 설명
	 * 			- 크로스 사이트 요청 위조는 웹 사이트 취약점 공격의 하나로, 사용자가 자신의 의지와는 무관하기 공격자가
	 * 			의도한 행위(수정, 삭제, 등록 등)를 특정 웹 사이트에 요청하게 하는 공격을 말한다.
	 * 
	 * 				> CSRF 공격을 대비하기 위해서는 스프링 시큐리티 CSRF Token을 이용하여 인증을 진행한다.
	 * 
	 * 			# 시큐리티 인증 구조
	 * 
	 * 				클라이언트에서 타겟으로 들어가기 위해서 요청을 진행한다. 이 때 타겟에 설정되어 있는 요청 접근 권한이 '사용자'
	 * 				등급일 때로 설정되어 있다고 가정하자. 타겟으로 접근하기 위한 요청을 날렸고 요청 안에 사용자 등급에 해당하는 인가
	 * 				정보가 포함되어 있지 않으면 스프링 시큐리티는 인증을 진행할 수 있도록 인증 페이지(로그인 페이지)를 제공하여
	 * 				사용자에게 인증을 요청한다. 사용자는 아이디, 비밀번호를 입력 후 인증을 요청한다.
	 * 				클라이언트에서 서버로 요청한 HttpServletRequest의 요청 객체를 AuthenticationFilter가 요청을
	 * 				인터셉터 하는데, UsernamePasswordAuthenticationToken을 통해 인증을 진행할 토큰을 만들어
	 * 				AuthenticationManager에게 위임한다. 넘겨 받은 id, pw를 이용해 인증을 진행하는 데 성공시,
	 * 				Authentication 객체 생성과 성공을 전달하고, 그렇지 않으면 Exception 에러를 발생시킨다.
	 * 				인증에 성공 후, 만들어진 Authentication 객체를 AuthenticationProvider에게 전달하고
	 * 				UserDetailService에게 넘겨받은 Authentication 객체 정보를 이용해서 Database에 일치하는 회원 정보
	 * 				를 조회하여 꺼낸다. 꺼낸 정보를 UserDetails로 만들고 최종 User 객체에 회원 정보를 등록한다.
	 * 				등록이 되면서 User Session 정보가 생성된다. 이후 스프링 시큐리티 내 SecurityContextHolder
	 * 				(시큐리티 인메모리)에 UserDetail 정보를 저장한다. 그리고 최종적으로 JSESSIONID가 유효한지를 검증 후
	 * 				인증을 완료 후 타겟 정보로 넘어가도록 돕는다.
	 * 
	 * 		2. 스프링 시큐리티 설정
	 * 
	 * 			# 환경 설정
	 * 				- 의존 라이브러리 설정(pom.xml 설정)
	 * 				> spring-security-web
	 * 				> spring-security-config
	 * 				> spring-security-core
	 * 				> spring-security-taglibs
	 * 
	 * 			# 웹 컨테이너 설정(web.xml)
	 * 				- 스프링 시큐리티가 제공하는 서블릿 필터 클래스를 서블릿 컨테이너에 등록하나.
	 * 				- 스프링 시큐리티는 필터 기반이므로 시큐리티 필터체인을 등록한다.
	 * 					> context-param의 param-value 추가
	 * 						(추가 파라미터: /WEB-INF/spring/security-context.xml)
	 * 					> SpringSecurityFilterChain 추가
	 * 
	 * 			# 스프링 시큐리티 설정
	 * 
	 * 				- 스프링 시큐리티 컴포넌트를 빈으로 정의한다.
	 * 				- spring/security-context.xml 설정
	 * 
	 * 			# 웹 화면 접근 정책
	 * 
	 * 				- 웹 화면 접근 정책을 정한다. (테스트를 위한 각 화면 당 접근 정책을 설정한다.)
	 * 
	 * 					대상			|	화면			|	접근 정책		
	 * 				------------------------------------------------------------
	 * 				일반 게시판			|	목록 화면		|	모두가 접근 가능하다.
	 * 								|	등록 화면		|	로그인한 회원만 접근 가능하다.
	 * 				------------------------------------------------------------
	 * 				공지사항 게시판		|	목록 화면		|	모두가 접근 가능하다
	 * 								|	등록 화면		|	로그인한 관리자만 접근 가능하다.
	 * 				------------------------------------------------------------
	 * 
	 * 			# 화면 설명
	 * 				- 컨트롤러
	 * 				> controller/SecurityBoardController
	 * 				> controller/SecurityNoticeController
	 * 
	 * 				- 화면
	 * 				> board/list, register
	 * 				> notice/list, register
	 * 
	 * 		3. 접근 제한 설정
	 * 
	 * 			- 시큐리티 설정을 통해서 특정 URI에 접근을 제한할 수 있다.
	 * 
	 * 				# 환경 설정
	 * 
	 * 				- 스프링 시큐리티 설정
	 * 					> URI 패턴으로 접근 제한을 설정한다.
	 * 					> security-context.xml 설정
	 * 						<security:intercept-uri pattern="/board/list" access="permitAll" />
	 * 						<security:intercept-uri pattern="/board/register" access="hasRole('ROLE_MEMBER')" />
	 * 					>  notice도 마찬가지로 설정...
	 * 
	 * 				# 화면 설명
	 * 				- 일반 게시판 목록 화면(모두 접근 가능하도록 되어 있음 : permitAll)
	 * 				- 일반 게시판 등록 화면(회원 권한을 가진 사용자만 접근 가능 : hasRole('ROLE_MEMBER'))
	 * 					> 접근 제한에 걸려 스프링 시큐리티가 기본적으로 제공하는 로그인 페이지로 이동한다.
	 * 				- 공지사항 게시판 목록 화면(모두 접근 가능하도록 되어 있음 : permitAll)
	 * 				- 공지사항 게시판 등록 화면(관리자 권한을 가진 사용자만 접근 가능 : hasRole('ROLE_ADMIN'))
	 * 					> 접근 제한에 걸려 스프링 시큐리티가 기본적으로 제공하는 로그인 페이지로 이동한다.
	 * 
	 * 		4. 로그인 처리
	 * 
	 * 			- 메모리 상에 아이디와 패스워드를 지정하고 로그인을 처리한다.
	 * 			- 스프링 시큐리티 5버전부터는 패스워드 암호화 처리기를 반드시 이용하도록 변경 되었다.
	 * 			- 암호화 처리기를 사용하지 않도록 "{noop}" 문자열을 비밀번호 앞에 사용한다.
	 * 
	 * 				# 환경 설정
	 * 
	 * 					- 스프링 시큐리티 설정
	 * 						> 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_ADMIN" />
								</security:user-service>
							</security:authentication-provider>
						</security:authentication-manager>
	 * 
	 * 				# 화면 설명
	 * 
	 * 					- 일반 게시판 등록 화면
	 * 						> 접근 제한에 걸려 스프링 시큐리티가 기본적으로 제공하는 로그인 페이지가 연결되고,
	 * 						일반 회원 등급인 ROLE_MEMBER 권한을 가진 member 계정으로 로그인 후 해당 페이지로 접근 가능.
	 * 					- 공지사항 게시판 등록 화면
	 * 						> 접근 제한에 걸려 스프링 시큐리티가 기본적으로 제공하는 로그인 페이지가 연결되고,
	 * 						관리자 등급인 ROLE_ADMIN 권한을 가진 admin 계정으로 로그인 후 해당 페이지로 접근 가능.
	 * 
	 * 		5. 접근 거부 처리
	 * 
	 * 			- 접근 거부가 발생한 상황을 처리하는 접근 거부 처리자의 URI를 지정할 수 있다.
	 * 
	 * 				# 환경 설정
	 * 
	 * 					- 스프링 웹 설정(servlet-context.xml 설정)
	 * 					> <context:component-scan base-package="kr.or.ddit.security..." />
	 * 					** 필요에 의한 패키지 라인을 bean으로 등록하여 사용해야 할 때 스프링 웹 설정에서 base-package를 설정할 수 있다.
	 * 
	 * 					- 스프링 시큐리티 설정(security-context.xml 설정)
	 * 					> 접근 거부 처리자의 URI를 지정
	 * 					> <security:access-denied-handler error-page="/accessError/" />
	 * 
	 * 				# 접근 거부 처리
	 * 
	 * 					- 접근 거부 처리 컨트롤러 작성
	 * 						> security/CommonController
	 * 
	 * 				# 화면 설명
	 * 					- 일반 게시판 등록 페이지
	 * 					> 접근 제한에 걸려 스프링 시큐리티가 제공하는 로그인 페이지가 나타나고, 회원 권한을 가진 계정으로
	 * 						접근 시 접근 가능
	 * 					- 공지사항 게시판 등록 페이지
	 * 					> 접근 제한에 걸려 스프링 시큐리티가 제공하는 로그인 페이지가 나타나고, 회원 권한을 가진 계정으로
	 * 						접근 시에 공지사항 게시판 등록 페이지는 관리자 권한으로만 접근 가능하므로 접근이 거부된다.
	 * 						이 때, access-denied-handler로 설정되어 있는 URI로 이동하고 해당 페이지에서 접근이
	 * 						거부 되었을 떄 보여질 페이지의 정보가 나타난다.
	 * 
	 * 		6. 사용자 정의 접근 거부 처리자
	 * 
	 * 			- 접근 거부가 발생한 상황에 단순 메시지 처리 이상의 다양한 처리를 하고 싶다면 AccessDeniedHandler를 직접 구현한다.
	 * 
	 * 				# 환경 설정
	 * 				- 스프링 시큐리티 설정(security-context.xml 설정)
	 * 					> id가 'customAccessDenied'를 가진 빈을 등록한다.
	 * 					> <security:access-denied-handler ref="customAccessDenied" />
	 * 
	 * 				# 접근 거부 처리자 클래스 정의
	 * 				- CustomAccessDeniedHandler 클래스 정의
	 * 				> AccessDeniedHandler 인터페이스를 참조받아서 handle 메소드를 재정의하여 사용한다.
	 * 				우리는 접근이 거부되었을 때 빈으로 등록해 둔 CustomAccessDeniedHandler 클래스가 발동해 해당 메소드가
	 * 				실행되고 response 내장 객체를 활용하여 /accessError URL로 이동하여 접근 거부시 보여질 페이지로 이동하지만
	 * 				이곳에서 더 많은 로직을 처리할 수도 있다. (request, response 내장 객체를 이용하여 다양한 처리 가능)
	 * 
	 * 				# 화면 설명
	 * 				- 일반 게시판 등록 페이지
	 * 				> 접근 제한에 걸려 스프링 시큐리티가 제공하는 로그인 페이지가 나타나고, 회원 권한을 가진 계정으로 접근 시 접근 가능
	 * 				- 공지사항 게시판 등록 페이지
	 * 				> 접근 제한에 걸려 스프링 시큐리티가 제공하는 로그인 페이지가 나타나고, 회원 권한을 가진 계정으로 접근 시에 공지사항
	 * 				게시판 등록 페이지는 관리자 권한만 접근 가능하므로 접근이 거부된다. 이 때, access-denied-handler로 설정
	 * 				되어 있는 ref 속성에 부여된 클래스 메소드로 이동하고 해당 페이지에서 접근이 거부되었을 때 페이지의 정보가 나타난다.
	 * 
	 * 		7. 사용자 정의 로그인 페이지
	 * 
	 * 			- 기본 로그인 페이지가 아닌 사용자가 직접 정의한 로그인 페이지를 사용한다.
	 * 
	 * 				# 환경 설정
	 * 				- 스프링 시큐리티 설정(security-context.xml 설정)
	 * 
	 * 					<security:form-login login-page="/login" /> 설정
	 * 					: 사용자가 직접 만든 로그인 페이지로 이동할 '/login' URL을 가지고 있는 컨트롤러 메소드를 정의
	 * 
	 * 				# 로그인 페이지 정의
	 * 
	 * 				- 사용자가 정의한 로그인 컨트롤러
	 * 					> controller 패키지 안에 LoginController 생성
	 * 				- 사용자가 정의한 로그인 뷰
	 * 					> views/loginForm.jsp
	 * 
	 * 				** 시큐리티에서 제공하는 기본 로그인 페이지로 이동하지 않고, 사용자가 정의한 로그인 페이지의 URI를 요청하여
	 * 				해당 페이지에서 권한을 체크하도록 한다. 인증이 완료되면 최초의 요청된 target URI로 이동한다.
	 * 				그렇지 않은 경우 사용자가 만들어놓은 접근 거부 페이지로 이동한다.
	 * 
	 * 		8. 로그인 성공 처리
	 * 
	 * 			- 로그인을 성공한 후에 로그인 이력 로그를 기록하는 등의 동적을 하고 싶은 경우가 있다.
	 * 				이런 경우 AuthenticationSuccessHandler라는 인터페이스를 구현해서 로그인 성공 처리자로 지정할 수 있다.
	 * 
	 * 				# 환경 설정
	 * 				- customLoginSuccess Bean 등록
	 * 				<security:form-login login-page="/login" authentication-success-handler-ref="customLoginSuccess" />
	 * 
	 * 				# 로그인 성공 처리자 클래스 정의
	 * 				- 로그인 성공 처리자
	 * 				> SavedRequestAwareAuthenticationSuccessHandler는 AuthenticationSuccessHandler의 구현 클래스이다.
	 * 					인증 전에 접근을 시도한 URL로 리다이렉트하는 기능을 가지고 있으며 스프링 시큐리티에서 기본적으로
	 * 					사용되는 구현 클래스이다.
	 * 				- 로그인 성공 처리자2
	 * 				> AuthenticationSuccessHandler 인터페이스를 직접 구현하여 인증 전에 접근을 시도한 URL로
	 * 					리다이렉트하는 기능을 구현한다.
	 * 
	 * 				# 화면 설명
	 * 				- 일반 게시판 등록 화면
	 * 				> 사용자가 정의한 로그인 페이지에서 회원 권한에 해당하는 계정으로 로그인 시, 성공했다면 성공 처리자인
	 * 					CustomLoginSuccess 클래스로 넘어가 넘겨받은 파라미터들 중 authentication 안에
	 * 					principal로 User 정보를 받아서 username과 password를 출력한다.
	 * 					(출력 정보는 로그인 성공 시 인증된 회원 정보이다.)
	 * 
	 * 		9. 로그아웃 처리
	 * 
	 * 			- 로그아웃을 위한 URI를 지정하고, 로그아웃 처리 후에 별도의 작업을 하기 위해서 사용자가 직접 구현한 처리자를
	 * 				등록할 수 있다.
	 * 
	 * 			# 환경 설정
	 * 
	 * 				- 스프링 시큐리티 설정(security-context.xml 설정)
	 * 				> <security:logout logout-url="/logout" invalidate-session="true" />
	 * 
	 * 				** logout 경로를 스프링에서 제공하는 /logout 경로로 설정한다.
	 * 					logout 처리 페이지에서도 action 경로를 /logout으로 설정한다.
	 */
	
}

* 우리가 시큐리티 적용 상태에서 특정 타겟 요청 시, 타겟은 곧 보호자원에 해당하는 권한이 걸려 있을 것이다. 예를 들어, 내가 가고자 하는 타겟이 ROLE_MEMBER에 해당하는 회원만 들어가는 권한으로 묶여 있다면, 이 타겟 진입시 해당 요청을 서블릿 필터 체인에 진입한다. 여기에는 무수히 많은 필터가 있는데, 그 중에 로그인이라는 인증 절차를 거칠 것. JSP에서 어떤 페이지가 나타났었는가? 로그인 화면을 보여 줌. 

* 그 로그인 페이지를 던져주는 녀석도 필터 체인임. 내가 보낸 요청을 필터 체인이 받음. 프록시도 필터 체인에 포함. 로그인을 담당하는 친구를 필터 기반으로 볼 것. 

이 사진은 스프링 시큐리티의 아키텍처 사진이고 스프링 시큐리티의 흐름은 아래와 같다.

-----------------------------------------------------------------------
1. Http Request 수신 

-> 사용자가 로그인 정보와 함께 인증 요청을 한다.
-----------------------------------------------------------------------

-----------------------------------------------------------------------
2. 유저 자격을 기반으로 인증토큰 생성 

-> AuthenticationFilter가 요청을 가로채고, 가로챈 정보를 통해 UsernamePasswordAuthenticationToken의 인증용 객체를 생성한다.
-----------------------------------------------------------------------

-----------------------------------------------------------------------
3. FIlter를 통해 AuthenticationToken을 AuthenticationManager로 위임

-> AuthenticationManager의 구현체인 ProviderManager에게 생성한 UsernamePasswordToken 객체를 전달한다.
-----------------------------------------------------------------------

-----------------------------------------------------------------------
4. AuthenticationProvider의 목록으로 인증을 시도

-> AutenticationManger는 등록된 AuthenticationProvider들을 조회하며 인증을 요구한다.
-----------------------------------------------------------------------

-----------------------------------------------------------------------
5. UserDetailsService의 요구

-> 실제 데이터베이스에서 사용자 인증정보를 가져오는 UserDetailsService에 사용자 정보를 넘겨준다.
-----------------------------------------------------------------------

-----------------------------------------------------------------------
6. UserDetails를 이용해 User객체에 대한 정보 탐색

-> 넘겨받은 사용자 정보를 통해 데이터베이스에서 찾아낸 사용자 정보인 UserDetails 객체를 만든다.
-----------------------------------------------------------------------

-----------------------------------------------------------------------
7. User 객체의 정보들을 UserDetails가 UserDetailsService(LoginService)로 전달

-> AuthenticaitonProvider들은 UserDetails를 넘겨받고 사용자 정보를 비교한다.
-----------------------------------------------------------------------

-----------------------------------------------------------------------
8. 인증 객체 or AuthenticationException

-> 인증이 완료가되면 권한 등의 사용자 정보를 담은 Authentication 객체를 반환한다.
-----------------------------------------------------------------------

-----------------------------------------------------------------------
9. 인증 끝

-> 다시 최초의 AuthenticationFilter에 Authentication 객체가 반환된다.
-----------------------------------------------------------------------

-----------------------------------------------------------------------
10. SecurityContext에 인증 객체를 설정

-> Authentication 객체를 Security Context에 저장한다.
-----------------------------------------------------------------------

최종적으로는 SecurityContextHolder는 세션 영역에 있는 SecurityContext에 Authentication 객체를 저장한다. 사용자 정보를 저장한다는 것은 스프링 시큐리티가 전통적인 세선-쿠키 기반의 인증 방식을 사용한다는 것을 의미한다.

[pom.xml]

		<!-- 스프링 시큐리티 라이브러리 의존 관계 정의 시작 -->
		<!-- 스프링 시큐리티를 웹에서 동작하도록 해줌 -->
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-web</artifactId>
			<version>5.0.7.RELEASE</version>
		</dependency>
		<!-- 스프링 시큐리티 설정을 도와줌 -->
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-config</artifactId>
			<version>5.0.7.RELEASE</version>
		</dependency>
		<!-- 스프링 시큐리티 일반 기능 -->
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-core</artifactId>
			<version>5.0.7.RELEASE</version>
		</dependency>
		<!-- 스프링 시큐리티 태그 라이브러리를 연결해 줌 -->
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-taglibs</artifactId>
			<version>5.0.7.RELEASE</version>
		</dependency>
		<!-- 스프링 시큐리티 라이브러리 의존 관계 정의 끝 -->


[web.xml]

<?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">

	<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
	<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>
	
	<!-- Creates the Spring Container shared by all Servlets and Filters -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<!-- Processes application requests -->
	<servlet>
		<servlet-name>appServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
		
	<servlet-mapping>
		<servlet-name>appServlet</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
	
	<!--  
		한글 처리를 위한 UTF-8 필터 등록
		JSP나 서블릿 처리할 때마다 넘겨받은 request를 setCharacterEncoding으로 UTF-8 설정했던 부분을 필터로 대체함
	-->
	<filter>
		<filter-name>encordingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
		<init-param>
			<param-name>forceEncoding</param-name>
			<param-value>true</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>encordingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
	
	<!-- 서블릿 필터 클래스를 서블릿 컨테이너에 등록함 -->
	<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>

</web-app>

<?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>
	
	<security:authentication-manager>
		
	</security:authentication-manager>

</beans>

package kr.or.ddit.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("/board")
public class BoardController {

	private static final Logger log = LoggerFactory.getLogger(BoardController.class);
	
	@RequestMapping(value = "/list", method = RequestMethod.GET)
	public String list() {
		log.info("list : access to all");
		return "board/list";
	}
	
	@RequestMapping(value = "/register", method = RequestMethod.GET)
	public String register() {
		log.info("register : access to member");
		return "board/register";
	}
	
}

package kr.or.ddit.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;


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

	private static final Logger log = LoggerFactory.getLogger(NoticeController.class);
	
	@RequestMapping(value = "/list", method = RequestMethod.GET)
	public String list() {
		log.info("list : access to all");
		return "notice/list";
	}
	
	@RequestMapping(value = "/register", method = RequestMethod.GET)
	public String register() {
		log.info("register : access to admin");
		return "notice/register";
	}
	
}

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>BOARD LIST</title>
	</head>
	<body>
		<h3>BOARD LIST : access to all</h3>
	</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>BOARD REGISTER</title>
	</head>
	<body>
		<h3>BOARD REGISTER : access to member</h3>
	</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>NOTICE LIST</title>
	</head>
	<body>
		<h3>NOTICE LIST : access to all</h3>
	</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>NOTICE REGISTER</title>
	</head>
	<body>
		<h3>NOTICE REGISTER : access to admin</h3>
	</body>
</html>

- http://localhost/board/list

- http://localhost/board/register

- http://localhost/notice/list

- http://localhost/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>
		<!-- 3. 접근 제한 설정 : URL 패턴으로 접근 제한을 설정한다. -->
		<security:intercept-url pattern="/board/list" access="permitAll" />
		<security:intercept-url pattern="/board/register" access="hasRole('ROLE_MEMBER')" />
		<security:intercept-url pattern="/notice/list" access="permitAll" />
		<security:intercept-url pattern="/notice/register" access="hasRole('ROLE_ADMIN')" />
		<!-- 3. 접근 제한 설정 끝 -->
		
		<!-- 폼 기반 인증 기능을 사용한다. -->
		<security:form-login />
	</security:http>
	
	<security:authentication-manager>
		
	</security:authentication-manager>

</beans>

- http://localhost/notice/list

- http://localhost/notice/register


- http://localhost/board/list

- http://localhost/board/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>
		<!-- 3. 접근 제한 설정 : URL 패턴으로 접근 제한을 설정한다. -->
		<security:intercept-url pattern="/board/list" access="permitAll" />
		<security:intercept-url pattern="/board/register" access="hasRole('ROLE_MEMBER')" />
		<security:intercept-url pattern="/notice/list" access="permitAll" />
		<security:intercept-url pattern="/notice/register" access="hasRole('ROLE_ADMIN')" />
		<!-- 3. 접근 제한 설정 끝 -->
		
		<!-- 폼 기반 인증 기능을 사용한다. -->
		<security:form-login />
	</security:http>
	
	<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_ADMIN" />
			</security:user-service>
		</security:authentication-provider>
	</security:authentication-manager>

</beans>

- http://localhost/board/register


- http://localhost/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>
		<!-- 3. 접근 제한 설정 : URL 패턴으로 접근 제한을 설정한다. -->
		<security:intercept-url pattern="/board/list" access="permitAll" />
		<security:intercept-url pattern="/board/register" access="hasRole('ROLE_MEMBER')" />
		<security:intercept-url pattern="/notice/list" access="permitAll" />
		<security:intercept-url pattern="/notice/register" access="hasRole('ROLE_ADMIN')" />
		<!-- 3. 접근 제한 설정 끝 -->
		
		<!-- 폼 기반 인증 기능을 사용한다. -->
		<security:form-login />
		
		<!-- 5. 접근 거부 처리자 : 접근 거부 처리자의 URI를 지정 -->
		<security:access-denied-handler error-page="/accessError" />
	</security:http>
	
	<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_ADMIN" />
			</security:user-service>
		</security:authentication-provider>
	</security:authentication-manager>

</beans>

package kr.or.ddit.security;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class CommonController {

	private static final Logger log = LoggerFactory.getLogger(CommonController.class);
	
	@RequestMapping(value = "/accessError")
	public String accessDenied(Authentication auth, Model model) {
		log.info("Access Denied : " + auth);
		model.addAttribute("msg", "Access Denied");
		return "accessError";
	}
	
}

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>ACCESS ERROR</title>
	</head>
	<body>
		<h3>Access Denied</h3>
		<!--  
			SPRING-SECURITY_403-EXCEPTION.message 는 'Access is denied' 문자열을 출력한다.
			security-context.xml에서 security:access-denied-handler 태그 자체로 설정했을 때 메세지가 출력
		-->
		<h2>${SPRING-SECURITY_403-EXCEPTION.message }</h2>
		<h2>${msg }</h2>
	</body>
</html>

- http://localhost/board/register

- http://localhost/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">

	<!-- 6. 사용자 정의 접근 거부 처리자 Bean 추가 -->
	<bean id="customAccessDenied" class="kr.or.ddit.security.CustomAccessDeniedHandler"></bean>

	<security:http>
		<!-- 3. 접근 제한 설정 : URL 패턴으로 접근 제한을 설정한다. -->
		<security:intercept-url pattern="/board/list" access="permitAll" />
		<security:intercept-url pattern="/board/register" access="hasRole('ROLE_MEMBER')" />
		<security:intercept-url pattern="/notice/list" access="permitAll" />
		<security:intercept-url pattern="/notice/register" access="hasRole('ROLE_ADMIN')" />
		<!-- 3. 접근 제한 설정 끝 -->
		
		<!-- 폼 기반 인증 기능을 사용한다. -->
		<security:form-login />
		
		<!-- 5. 접근 거부 처리자 : 접근 거부 처리자의 URI를 지정 -->
		<!-- <security:access-denied-handler error-page="/accessError" /> -->
		
		<!-- 6. 사용자 정의 접근 거부 처리자 추가 : customAccessDenied를 접근 거부 처리자로 등록 -->
		<security:access-denied-handler ref="customAccessDenied" />
	</security:http>
	
	<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_ADMIN" />
			</security:user-service>
		</security:authentication-provider>
	</security:authentication-manager>

</beans>

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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;

public class CustomAccessDeniedHandler implements AccessDeniedHandler {

	private static final Logger log = LoggerFactory.getLogger(CustomAccessDeniedHandler.class);
	
	@Override
	public void handle(HttpServletRequest request, HttpServletResponse response,
			AccessDeniedException accessDeniedException) throws IOException, ServletException {
		
		log.info("CustomAccessDeniedHandler handle() 실행...!");
		
		// CommonController에서 만들어둔 컨트롤러 메소드를 실행한다.
		response.sendRedirect("/accessError");
		
	}
	
}

- http://localhost/board/register

- http://localhost/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">

	<!-- 6. 사용자 정의 접근 거부 처리자 Bean 추가 -->
	<bean id="customAccessDenied" class="kr.or.ddit.security.CustomAccessDeniedHandler"></bean>

	<security:http>
		<!-- 3. 접근 제한 설정 : URL 패턴으로 접근 제한을 설정한다. -->
		<security:intercept-url pattern="/board/list" access="permitAll" />
		<security:intercept-url pattern="/board/register" access="hasRole('ROLE_MEMBER')" />
		<security:intercept-url pattern="/notice/list" access="permitAll" />
		<security:intercept-url pattern="/notice/register" access="hasRole('ROLE_ADMIN')" />
		<!-- 3. 접근 제한 설정 끝 -->
		
		<!-- 폼 기반 인증 기능을 사용한다. : 사용자 정의 로그인 페이지 추가(login-page) -->
		<security:form-login login-page="/login" />
		
		<!-- 5. 접근 거부 처리자 : 접근 거부 처리자의 URI를 지정 -->
		<!-- <security:access-denied-handler error-page="/accessError" /> -->
		
		<!-- 6. 사용자 정의 접근 거부 처리자 추가 : customAccessDenied를 접근 거부 처리자로 등록 -->
		<security:access-denied-handler ref="customAccessDenied" />
	</security:http>
	
	<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_ADMIN" />
			</security:user-service>
		</security:authentication-provider>
	</security:authentication-manager>

</beans>

package kr.or.ddit.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class LoginController {

	private static final Logger log = LoggerFactory.getLogger(LoginController.class);
	
	// 사용자가 정의한 로그인 페이지를 요청할 컨트롤러 메소드
	@RequestMapping(value = "/login", method = RequestMethod.GET)
	public String loginForm(String error, String logout, Model model) {
		log.info("error : " + error);
		log.info("logout : " + logout);
		
		if(error != null) {
			model.addAttribute("error", "Login Error!");
		}
		if(logout != null) {
			model.addAttribute("logout", "Logout!");
		}
		
		return "loginForm";
	}
	
}

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="sec" %>
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>LOGIN FORM</title>
	</head>
	<body>
		<h1>Login</h1>
		<h2>${error }</h2>			<!-- 에러 발생 시, 출력할 메세지 -->
		<h2>${logout }</h2>			<!-- 로그아웃 발생 시, 출력할 메세지 -->
		
		<!-- security를 적용 후 데이터를 전송 시 csrfInput으로 시큐리티 토큰을 전송해야 한다. -->
		<form action="/login" method="post">
			username : <input type="text" name="username" /><br />
			password : <input type="text" name="password" /><br />
			<button type="submit">로그인</button>
			<sec:csrfInput />
		</form>
	</body>
</html>

- http://localhost/board/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">

	<!-- 6. 사용자 정의 접근 거부 처리자 Bean 추가 -->
	<bean id="customAccessDenied" class="kr.or.ddit.security.CustomAccessDeniedHandler"></bean>
	<bean id="customLoginSuccess" class="kr.or.ddit.security.CustomLoginSuccessHandler"></bean>

	<security:http>
		<!-- 3. 접근 제한 설정 : URL 패턴으로 접근 제한을 설정한다. -->
		<security:intercept-url pattern="/board/list" access="permitAll" />
		<security:intercept-url pattern="/board/register" access="hasRole('ROLE_MEMBER')" />
		<security:intercept-url pattern="/notice/list" access="permitAll" />
		<security:intercept-url pattern="/notice/register" access="hasRole('ROLE_ADMIN')" />
		<!-- 3. 접근 제한 설정 끝 -->
		
		<!-- 
			폼 기반 인증 기능을 사용한다. : 사용자 정의 로그인 페이지 추가(login-page),
			로그인 성공 처리 추가 : 로그인 성공 후 처리를 담당하는 처리자로 지정(customLoginSuccess)
		-->
		<security:form-login login-page="/login" authentication-success-handler-ref="customLoginSuccess" />
		
		<!-- 5. 접근 거부 처리자 : 접근 거부 처리자의 URI를 지정 -->
		<!-- <security:access-denied-handler error-page="/accessError" /> -->
		
		<!-- 6. 사용자 정의 접근 거부 처리자 추가 : customAccessDenied를 접근 거부 처리자로 등록 -->
		<security:access-denied-handler ref="customAccessDenied" />
	</security:http>
	
	<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_ADMIN" />
			</security:user-service>
		</security:authentication-provider>
	</security:authentication-manager>

</beans>

package kr.or.ddit.security;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;

public class CustomLoginSuccessHandler implements AuthenticationSuccessHandler {

	private static final Logger log = LoggerFactory.getLogger(CustomLoginSuccessHandler.class);
	private RequestCache requestCache = new HttpSessionRequestCache();

	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) throws IOException, ServletException {

		log.info("onAuthenticationSuccess() 실행...!");
		
		User user = (User) authentication.getPrincipal();
		log.info("username : " + user.getUsername());
		log.info("password : " + user.getPassword());
		
		clearAuthenticationAttribute(request);
		
		SavedRequest savedRequest = requestCache.getRequest(request, response);
		String targetUrl = savedRequest.getRedirectUrl();
		
		log.info("Login Success targetUrl : " + targetUrl);
		response.sendRedirect(targetUrl);
		
	}

	private void clearAuthenticationAttribute(HttpServletRequest request) {
		// session 가 존재한다면 현재 session을 반환하고, 그렇지 않으면 null을 반환한다.
		HttpSession session = request.getSession(false);
		if(session == null) {
			return;
		}
		
		// SPRING_SECURITY_LAST_EXCEPTION 값을 세션에서 삭제
		session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
	}
	
}

- http://localhost/board/register

password는 PROTECTED로 감싸져 있기 때문에 보이지 않는 것이 정상이다.


[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">

	<!-- 6. 사용자 정의 접근 거부 처리자 Bean 추가 -->
	<bean id="customAccessDenied" class="kr.or.ddit.security.CustomAccessDeniedHandler"></bean>
	<bean id="customLoginSuccess" class="kr.or.ddit.security.CustomLoginSuccessHandler"></bean>

	<security:http>
		<!-- 3. 접근 제한 설정 : URL 패턴으로 접근 제한을 설정한다. -->
		<security:intercept-url pattern="/board/list" access="permitAll" />
		<security:intercept-url pattern="/board/register" access="hasRole('ROLE_MEMBER')" />
		<security:intercept-url pattern="/notice/list" access="permitAll" />
		<security:intercept-url pattern="/notice/register" access="hasRole('ROLE_ADMIN')" />
		<!-- 3. 접근 제한 설정 끝 -->
		
		<!-- 
			폼 기반 인증 기능을 사용한다. : 사용자 정의 로그인 페이지 추가(login-page),
			로그인 성공 처리 추가 : 로그인 성공 후 처리를 담당하는 처리자로 지정(customLoginSuccess)
		-->
		<security:form-login login-page="/login" authentication-success-handler-ref="customLoginSuccess" />
		
		<!-- 5. 접근 거부 처리자 : 접근 거부 처리자의 URI를 지정 -->
		<!-- <security:access-denied-handler error-page="/accessError" /> -->
		
		<!-- 6. 사용자 정의 접근 거부 처리자 추가 : customAccessDenied를 접근 거부 처리자로 등록 -->
		<security:access-denied-handler ref="customAccessDenied" />
		
		<!-- 9. 로그아웃 처리 : 로그아웃 처리를 위한 URI를 지정하고, 로그아웃한 후에 세션을 무효화한다. -->
		<security:logout logout-url="/logout" invalidate-session="true" />
	</security:http>
	
	<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_ADMIN" />
			</security:user-service>
		</security:authentication-provider>
	</security:authentication-manager>

</beans>
package kr.or.ddit.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class LoginController {

	private static final Logger log = LoggerFactory.getLogger(LoginController.class);
	
	// 사용자가 정의한 로그인 페이지를 요청할 컨트롤러 메소드
	@RequestMapping(value = "/login", method = RequestMethod.GET)
	public String loginForm(String error, String logout, Model model) {
		log.info("error : " + error);
		log.info("logout : " + logout);
		
		if(error != null) {
			model.addAttribute("error", "Login Error!");
		}
		if(logout != null) {
			model.addAttribute("logout", "Logout!");
		}
		
		return "loginForm";
	}
	
	@RequestMapping(value = "/logout", method = RequestMethod.GET)
	public String logoutForm() {
		return "logoutForm";
	}
	
}

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="sec" %>
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>LOGOUT FORM</title>
	</head>
	<body>
		<h2>Logout Form</h2>
		
		<form action="/logout" method="post">
			<input type="submit" value="로그아웃" />
			<sec:csrfInput />
		</form>
	</body>
</html>

- http://localhost/board/register

- http://localhost/logout

- http://localhost/notice/register


 

'대덕인재개발원_웹기반 애플리케이션' 카테고리의 다른 글

231212_SPRING 2 (15-2)  (0) 2023.12.12
231212_SPRING 2 (15-1)  (0) 2023.12.11
231208_SPRING 2 (13-2)  (0) 2023.12.08
231208_SPRING 2 (13-1)  (0) 2023.12.08
231207_SPRING 2 (12-2)  (1) 2023.12.07