관리 메뉴

거니의 velog

231212_SPRING 2 (15-1) 본문

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

231212_SPRING 2 (15-1)

Unlimited00 2023. 12. 11. 08:24
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으로 설정한다.
	 * 
	 * 		10. JDBC를 이용한 인증/인가 처리
	 * 
	 * 			- 지정한 형식으로 테이블을 생성하면 JDBC를 이용해서 인증/인가를 처리할 수 있다.
	 * 			- 생성할 테이블은 사용자를 관리하는 테이블(users)과 권한을 관리하는 테이블이다.
	 * 
	 * 			# 데이터베이스 테이블 준비
	 * 
	 * 				- users, authorities 테이블 준비
	 * 
	 * 			# 환경설정
	 * 
	 * 				- 의존 라이브러리 설정
	 * 					> 데이터베이스 관련 라이브러리를 추가한다.
	 * 					> 기존 데이터베이스 연결을 위한 라이브러리를 가져와 등록(pom.xml)
	 * 
	 * 			# 스프링 설정(root-context.xml 설정)
	 * 
	 * 				- 데이터 소스 설정(기존 설정)
	 * 
	 * 			# 스프링 시큐리티 설정(security-context.xml 설정)
	 * 
	 * 				- customPasswordEncoder 빈 등록 진행
	 * 					<security:authentication-manage> 태그 설정
	 * 
	 * 			# 비밀번호 암호화 처리기 클래스 정의
	 * 				- 비밀번호 암호화 처리기
	 * 					스프링 시큐리티 5부터는 기본적으로 PasswordEncoder를 지정해야 하는데, 제대로 하려면 생성된 사용자 테이블(users)
	 * 					에 비밀번호를 암호화하여 저장해야 한다. 테스트를 위해서 생성한 데이터는 암호화를 처리하지 않으므로 로그인 하면
	 * 					당연히 로그인 에러가 발생할 것이다. (암호화된 비밀번호가 날아가는게 아니라서)
	 * 					그래서 암호화를 하지 않는 PasswordNoOpPasswordEncoder를 직접 구현하여 지정하면 로그인 시 암호화를 고려하지
	 * 					않으므로 로그인이 정상적으로 이루어지는 걸 확인할 수 있다.
	 * 					** PasswordEncoder를 참조받아서 우리가 원하는 로직으로 변경해서 사용(암호화를 사용하지 않는 루트로)
	 * 
	 * 		11. 사용자 테이블 이용한 인증/인가 처리
	 * 
	 * 			- 스프링 시큐리티가 기본적으로 이용하는 테이블 구조를 그대로 생성해서 사용해도 되지만, 기존에 구축된 회원 테이블이 있다면
	 * 				약간의 작업으로 기존 테이블을 활용할 수 있다.
	 * 
	 * 			# 데이터베이스 테이블 준비
	 * 				- member, member_auth 테이블 준비
	 * 
	 * 			# 환경 설정
	 * 				- 스프링 시큐리티 설정(security-context.xml 설정)
	 * 					> bcryptPasswordEncoder 빈 등록 진행
	 * 					> <security:jdbc-user-service> 태그 설정
	 * 					> <security:password-encoder> 태그 설정
	 * 
	 * 			# 쿼리 정의
	 * 				- 인증할 때 필요한 쿼리
	 * 					> select user_id, user_pw, enabled from member where user_id = ?
	 *
	 *				- 권한을 확인할 때 필요한 쿼리
	 *					> select m.user_id, ma.auth from member m, member_auth ma where ma.user_no = m.user_no and m.user_id = ?
	 *
	 *			# BCryptPasswordEncoder 클래스를 이용하여 직접 encode된 비밀번호를 찾아 데이터베이스에 셋팅한다.
	 *				- 12번 UserDetails 정보를 등록하면서 같이 진행
	 *
	 *					- BCryptPasswordEncoder 클래스를 활용한 단방향 비밀번호 암호화
	 *						> encode() 메소드를 통해서 SHA-2 방식의 8바이트 Hash 암호를 매번 랜덤하게 생성한다.
	 *						> 똑같은 비밀번호를 입력하더라도 암호화되는 문자열은 매번 다른 문자열을 반환한다.
	 *
	 *						비밀번호를 입력하면 암호화된 비밀번호로 인코딩하는데, 암호화된 비밀번호와 DB 테이블에 있는 암호화된 비밀번호가 
	 *						일치한지를 파악 후 일치하면 로그인 성공으로 다음 스탭을 진행한다.
	 *						> BCryptPasswordEncoder 클래스의 encode() 메소드를 통해 만들어지는 암호화된 해쉬 다이제스트들은 입력한
	 *							비밀번호 문자에 해당하는 수십억개의 다이제스트들 중에서 일치하는 다이제스트가 존재할 경우 비밀번호의 일치로 보고
	 *							인증을 성공시켜준다.
	 *
	 *		12. UserDetailsService 재정의
	 *
	 *			- 스프링 시큐리티의 UserDetailsService를 구현하여 사용자 상세 정보를 얻어오는 메서드를 재정의한다.
	 *
	 *				# 환경 설정
	 *					- 의존 라이브러리 설정(pom.xml) 설정
	 *					> 데이터베이스 관련 라이브러리
	 *					- 스프링 시큐리티 설정(security-context.xml 설정)
	 *					> customUserDetailsService 빈 등록
	 *					> security:authentication-provider 태그 설정
	 *
	 *				# 클래스 재정의
	 *					- UserDetailsService 재정의
	 *						> security/CustomUserDetailsService 클래스 생성
	 *						> 기존 사용중인 read를 기반으로 한 readById 재정의
	 *						> CustomUserDetailsService 클래스 내 loadUserByUsername 메소드에서 인코딩 된 비밀번호 확인 후
	 *							데이터베이스 비밀번호 수정
	 *							> Member 테이블 내 특정 계정들 비밀번호를 암호화된 비밀번호로 수정한다.
	 *
	 *		13. 스프링 시큐리티 표현식
	 *
	 *			- 스프링 시큐리티를 이용하면 인증 및 권한 정보에 따라 화면을 동적으로 구성할 수 있고, 
	 *				로그인 한 사용자 정보를 보여줄 수도 있다.
	 *
	 *			# 공동 표현식
	 *
	 *				- hasRole([role])
	 *					> 해당 롤이 있으면 true
	 *				- hasAnyRole([role1, role2])
	 *					> 여러 롤들 중에서 하나라도 해당하는 롤이 있으면 true
	 *				- principal
	 *					> 인증된 사용자의 사용자 정보(UserDetails 인터페이스를 구현한 클래스의 객체)를 의미
	 *				- authentication
	 *					> 인증된 사용자의 인증 정보(Authentication 인터페이스를 구현한 클래스의 객체)fmf dmlal
	 *				- permitAll
	 *					> 모든 사용자에게 허용
	 *				- denyAll
	 *					> 모든 사용자에게 거부
	 *				- isAnonymous()
	 *					> 익명의 사용자의 경우(로그인을 하지 않은 경우도 해당)
	 *				- isAuthenticated()
	 *					> 인증된 사용자면 true를 반환
	 *				- isFullyAuthenticated()
	 *					> Remember-me로 인증된 것이 아닌 일반적인 방법으로 인증된 사용자인 경우 true
	 *
	 *			# 표현식 사용
	 *
	 *				- 표현식을 이용하여 동적 화면 구성
	 *					> home.jsp 수정
	 *						: 표현식을 이용한 내용 추가
	 *
	 *				- 로그인한 사용자 정보 보여주기
	 *					> views/board/register.jsp 수정
	 *					> views/notice/register.jsp 수정
	 *
	 *		14. 자동 로그인
	 *
	 *			- 로그인하면 특정 시간 동안 다시 로그인 할 필요가 없는 기능이다.
	 *			- 스프링 시큐리티는 메모리나 데이터베이스를 사용하여 처리한다.
	 *			- 기능을 구현하기 위해 <security:remember-me> 태그를 이용하여 시큐리티 설정 파일을 수정한다.
	 *
	 *			# 데이터베이스 테이블
	 *				- persistent_logins 테이블 준비
	 *
	 *			# 환경 설정
	 *				- 스프링 시큐리티 설정(security-context.xml 설정)
	 *					<security:remember-me data-source-ref="dataSource" token-validaty="604800" /> 태그 설정
	 *					<security:logout logout-url="/logout" invalidate-session="true" 
	 *						delete-cookies="remember-me, JSESSION_ID" /> 태그 설정
	 *
	 *			# 자동 로그인
	 *				- 로그인 상태 유지 체크박스 추가
	 *					> loginForm.jsp 수정 (자동 로그인 체크 박스 만들기)
	 *
	 *			# 자동 로그인 시, 만들어지는 쿠키 정보들
	 *				- JSESSIONID와 remember-me 쿠키가 만들어진다.
	 *				- JSESSIONID를 삭제 후, 다시 로그인을 진행하더라도 로그인 후 진행될 페이지가 정상적으로 나타나는 것을 확인할 수 있음
	 *					> 자동 로그인이 remember-me 쿠키에 의해서 데이터베이스에 저장되는 각 hash token 값에 의해 재설정됨을 확인
	 */
	
}

-----------------------------------------
-- 스프링 시큐리티
-----------------------------------------

create table users(
    username varchar2(50) not null,
    password varchar2(50) not null,
    enabled char(1) default '1' null,
    constraint pk_users primary key(username)
);

create table authorities(
    username varchar2(50) not null,
    authority varchar2(50) not null,
    constraint fk_authorities_users_username foreign key(username)
    references users(username)
);

insert into users values('user00', '1234', '1');
insert into users values('member00', '1234', '1');
insert into users values('admin00', '1234', '1');

insert into authorities values('user00', 'ROLE_USER');
insert into authorities values('member00', 'ROLE_MEMBER');
insert into authorities values('admin00', 'ROLE_MEMBER');
insert into authorities values('admin00', 'ROLE_ADMIN');

commit;


[pom.xml]

<!--  데이터베이스 의존관계 시작 -->
		<!-- XML로 쿼리를 작성하게 해주는 라이브러리 -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>3.5.10</version>
		</dependency>
		<!-- 스프링과 mybatis를 연동하게 해주는 라이브러리 -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis-spring</artifactId>
			<version>2.0.4</version>
		</dependency>
		<!-- 스프링에서 JDBC(Java Database Connectivity) -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>${org.springframework-version}</version>
		</dependency>
		<!--  
			dbcp : database connection pool > 커넥션 객체를 미리 만들어놓고 쓰고 반납
						미리 커넥션 객체를 여러 개 만들어 놓고 가용해야 할 때 커넥션을 들고 가서 사용하다가 다시 반납
						최근에는 hikaricp를 사용하는 경우도 있음
		-->
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-dbcp2</artifactId>
			<version>2.7.0</version>
		</dependency>
		<!-- 로깅을 위한 라이브러리, 쿼리를 console이나 파일 로드로 볼 수 있다. -->
		<dependency>
			<groupId>org.bgee.log4jdbc-log4j2</groupId>
			<artifactId>log4jdbc-log4j2-jdbc4</artifactId>
			<version>1.16</version>
		</dependency>
		<!-- 오라클 데이터베이스 라이브러리 -->
		<dependency>
			<groupId>com.oracle.database.jdbc</groupId>
			<artifactId>ojdbc8</artifactId>
			<version>21.1.0.0</version>
		</dependency>
		<!--  데이터베이스 의존관계 끝 -->

[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"
	xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
	
	<!-- Root Context: defines shared resources visible to all other web components -->
		
	<!-- dataSource : 데이터베이스와 관련된 정보를 설정한다. -->
	<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
		<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
		<property name="url" value="jdbc:oracle:thin:@localhost:1521:xe" />
		<property name="username" value="spring2" />
		<property name="password" value="java" />
	</bean>
	
	<!--  
		데이터베이스와 연결을 맺고 끊어질 때까지의 라이프 사이클을 관리해주는 SqlSessionTemplate 객체를 생성한다.
		1) dataSource
		2) Mapper xml 위치 지정
		3) 마이바티스 설정 위치 지정
	-->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="mapperLocations" value="classpath:/sqlmap/**/*_SQL.xml" />
		<property name="configLocation" value="/WEB-INF/mybatisAlias/mybatisAlias.xml" />
	</bean>
	
	<!--  
		데이터베이스에 쿼리를 실행시키는 객체
		이 객체를 통해서 query를 실행한다.
	-->
	<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
		<constructor-arg name="sqlSessionFactory" index="0" ref="sqlSessionFactory" />
	</bean>
	
	<!--  
		Mapper 인터페이스 설정
		개발자가 직접 DAO를 설정하지 않아도 자동으로 Mapper 인터페이스를 활용하는 객체를 생성하게 된다.
	-->
	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
		<property name="basePackage" value="kr.or.ddit.mapper" />
	</bean>
		
</beans>

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	
	<!--  
		[마이바티스] 스프링에서 "_"를 사용한 컬럼명 사용 시(BOOK 테이블의 BOOK_ID와 같은 컬럼)
		카멜케이스로 읽어주는 역할(bookId와 같이)
		
		ex) 테이블 컬럼명이 member_id인 경우 jsp 화면 단에서 이 값을 사용시 memberId로 사용할 수 있다.
	-->
	<settings>
		<setting name="mapUnderscoreToCamelCase" value="true"/>
	</settings>
	
	<typeAliases>
		<!-- <typeAlias type="kr.or.ddit.vo.Board" alias="board"/> -->
	</typeAliases>
	
</configuration>

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="Blank">
	
</mapper>

[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>
	<bean id="customPasswordEncoder" class="kr.or.ddit.security.CustomNoOpPasswordEncoder"></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:jdbc-user-service data-source-ref="dataSource"/>
			<security:password-encoder ref="customPasswordEncoder" />
			
		</security:authentication-provider>
	</security:authentication-manager>

</beans>

package kr.or.ddit.security;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.crypto.password.PasswordEncoder;

public class CustomNoOpPasswordEncoder implements PasswordEncoder {

	private static final Logger log = LoggerFactory.getLogger(CustomNoOpPasswordEncoder.class);
	
	@Override
	public String encode(CharSequence rawPassword) {
		log.info("before encode : " + rawPassword);
		return rawPassword.toString();
	}

	@Override
	public boolean matches(CharSequence rawPassword, String encodedPassword) {
		log.info("matches : " + rawPassword + " :::: " + encodedPassword);
		return rawPassword.toString().equals(encodedPassword);
	}
	
}

- http://localhost/notice/register

<?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>
	<bean id="customPasswordEncoder" class="kr.or.ddit.security.CustomNoOpPasswordEncoder"></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:jdbc-user-service data-source-ref="dataSource"/>
			<security:password-encoder ref="customPasswordEncoder" />
			
		</security:authentication-provider>
	</security:authentication-manager>

</beans>

'user00', 'ROLE_USER' 이므로 접근 불가


'member00', 'ROLE_MEMBER' 이므로 접근 가능

 


[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>
	<!-- <bean id="customPasswordEncoder" class="kr.or.ddit.security.CustomNoOpPasswordEncoder"></bean> -->
	<bean id="bcryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"></bean>
	<bean id="customUserDetailsService" class="kr.or.ddit.security.CustomUserDetailsService"></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:jdbc-user-service data-source-ref="dataSource"/>
			<security:password-encoder ref="customPasswordEncoder" /> -->
			<!-- 사용자 정의 테이블을 이용한 인증/인가 처리 끝 -->
			
			<security:jdbc-user-service data-source-ref="dataSource" 
			users-by-username-query="select user_id, user_pw, enabled from member where user_id = ?" 
			authorities-by-username-query="select m.user_id, ma.auth from member m, member_auth ma where ma.user_no = m.user_no and m.user_id = ?" />
			<security:password-encoder ref="bcryptPasswordEncoder" />
			
		</security:authentication-provider>
	</security:authentication-manager>

</beans>

package kr.or.ddit.security;

import javax.inject.Inject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import kr.or.ddit.mapper.IMemberMapper;
import kr.or.ddit.vo.CrudMember;
import kr.or.ddit.vo.CustomUser;

public class CustomUserDetailsService implements UserDetailsService {
	
	private static final Logger log = LoggerFactory.getLogger(CustomUserDetailsService.class);
	
	@Inject
	private BCryptPasswordEncoder bpe;
	
	@Inject
	private IMemberMapper memberMapper;

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		log.info("loadUserByUsername() 실행...!");
		
		String password = "1234";
		
		log.info("#### 암호화된 비밀번호 : " + bpe.encode(password));
		log.info("#### 암호화된 비밀번호 : " + bpe.encode(password));
		log.info("#### 암호화된 비밀번호 : " + bpe.encode(password));
		log.info("#### 암호화된 비밀번호 : " + bpe.encode(password));
		log.info("#### 암호화된 비밀번호 : " + bpe.encode(password));
		log.info("#### 암호화된 비밀번호 : " + bpe.encode(password));
		log.info("#### 암호화된 비밀번호 : " + bpe.encode(password));
		
		log.info("Load User By Username : " + username);
		
		// UserDetailService를 등록하는 과정에서 우리가 할 목표는 User 객체의 정보와
		// 인증되어 실제로 사용될 내 id에 해당하는 회원정보를 CrudMember에 담고 그 녀석을 UserDetails 정보 안에서
		// 가용할 수 있도록 만든다.
		CrudMember member;
		try {
			member = memberMapper.readByUserId(username);
			log.info("queried by member mapper : " + member);
			return member == null ? null : new CustomUser(member);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		return null;
	}

}

package kr.or.ddit.vo;

import java.util.Date;
import java.util.List;

public class CrudMember {

	private int userNo;
	private String userId;
	private String userPw;
	private String userName;
	private Date regDate;
	private Date updDate;
	private List<CrudMemberAuth> authList;
	
	public int getUserNo() {
		return userNo;
	}
	public void setUserNo(int userNo) {
		this.userNo = userNo;
	}
	public String getUserId() {
		return userId;
	}
	public void setUserId(String userId) {
		this.userId = userId;
	}
	public String getUserPw() {
		return userPw;
	}
	public void setUserPw(String userPw) {
		this.userPw = userPw;
	}
	public String getUserName() {
		return userName;
	}
	public void setUserName(String userName) {
		this.userName = userName;
	}
	public Date getRegDate() {
		return regDate;
	}
	public void setRegDate(Date regDate) {
		this.regDate = regDate;
	}
	public Date getUpdDate() {
		return updDate;
	}
	public void setUpdDate(Date updDate) {
		this.updDate = updDate;
	}
	public List<CrudMemberAuth> getAuthList() {
		return authList;
	}
	public void setAuthList(List<CrudMemberAuth> authList) {
		this.authList = authList;
	}
	
}
package kr.or.ddit.vo;

public class CrudMemberAuth {

	private int userNo;
	private String auth;
	
	public int getUserNo() {
		return userNo;
	}
	public void setUserNo(int userNo) {
		this.userNo = userNo;
	}
	public String getAuth() {
		return auth;
	}
	public void setAuth(String auth) {
		this.auth = auth;
	}
	
}

package kr.or.ddit.mapper;

import kr.or.ddit.vo.CrudMember;

public interface IMemberMapper {

	public CrudMember readByUserId(String username);

}

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="kr.or.ddit.mapper.IMemberMapper">
	
	<resultMap type="crudMember" id="memberMap">
		<id property="userNo" column="user_no" />
		<result property="userNo" column="user_no" />
		<result property="userId" column="user_id" />
		<result property="userPw" column="user_pw" />
		<result property="userName" column="user_name" />
		<result property="regDate" column="reg_date" />
		<result property="updDate" column="upd_date" />
		<collection property="authList" resultMap="authMap" />
	</resultMap>
	
	<resultMap type="crudMemberAuth" id="authMap">
		<result property="userNo" column="user_no" />
		<result property="auth" column="auth" />
	</resultMap>
	
	<select id="readByUserId" parameterType="string" resultMap="memberMap">
		select 
			m.user_no, user_id, user_pw, user_name, reg_date, upd_date, a.auth 
		from member m left outer join member_auth a on(m.user_no = a.user_no) 
		where user_id = #{userId}
	</select>
	
</mapper>

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	
	<!--  
		[마이바티스] 스프링에서 "_"를 사용한 컬럼명 사용 시(BOOK 테이블의 BOOK_ID와 같은 컬럼)
		카멜케이스로 읽어주는 역할(bookId와 같이)
		
		ex) 테이블 컬럼명이 member_id인 경우 jsp 화면 단에서 이 값을 사용시 memberId로 사용할 수 있다.
	-->
	<settings>
		<setting name="mapUnderscoreToCamelCase" value="true"/>
	</settings>
	
	<typeAliases>
		<typeAlias type="kr.or.ddit.vo.CrudMember" alias="crudMember"/>
		<typeAlias type="kr.or.ddit.vo.CrudMemberAuth" alias="crudMemberAuth"/>
	</typeAliases>
	
</configuration>

package kr.or.ddit.vo;

import java.util.Collection;
import java.util.stream.Collectors;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;

public class CustomUser extends User {

	private CrudMember member;
	
	public CustomUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
		super(username, password, authorities);
	}
	
	public CustomUser(CrudMember member) {
		// Java 스트림을 사용한 경우(람다 표현식)
		// - 자바 버전 8부터 추가된 기능
		// map : 컬렉션(List, Map, Set 등), 배열 등의 설정되어 있는 각 타입의 값들을 하나씩 참조하여 람다식으로 반복 처리할 수 있게 해준다.
		// collect : Stream()을 돌려 발생되는 데이터를 가공 처리 하고 원하는 형태의 자료형으로 변환을 돕는다.
		// 회원 정보 안에 들어 있는 역할명 들을 컬렉션 형태의 스트림으로 만들어서 보내준다.
		// *** 람다 표현식은 복잡한 메서드 라인을 간단한 표현식으로 출력할 수 있다는 장점이 있는 대신 디버깅을 할 수 없다는 점이 단점이다.
		super(member.getUserId(), member.getUserPw(), member.getAuthList().stream().map(auth -> new SimpleGrantedAuthority(auth.getAuth())).collect(Collectors.toList()));
		this.member = member;
	}

	public CrudMember getMember() {
		return member;
	}

	public void setMember(CrudMember member) {
		this.member = member;
	}
	
}

[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>
	<!-- <bean id="customPasswordEncoder" class="kr.or.ddit.security.CustomNoOpPasswordEncoder"></bean> -->
	<bean id="bcryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"></bean>
	<bean id="customUserDetailsService" class="kr.or.ddit.security.CustomUserDetailsService"></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 user-service-ref="customUserDetailsService">
			<!-- <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:jdbc-user-service data-source-ref="dataSource"/>
			<security:password-encoder ref="customPasswordEncoder" /> -->
			<!-- 사용자 정의 테이블을 이용한 인증/인가 처리 끝 -->
			
			<!-- 
				UserDetailsService를 설정하면서 데이터베이스 연동 후 사용자가 정의한 테이블로 mapper를 통한 데이터 바인딩으로 
				인증/인가 진행 시 주석
			-->
			<!-- <security:jdbc-user-service data-source-ref="dataSource" 
			users-by-username-query="select user_id, user_pw, enabled from member where user_id = ?" 
			authorities-by-username-query="select m.user_id, ma.auth from member m, member_auth ma where ma.user_no = m.user_no and m.user_id = ?" />
			<security:password-encoder ref="bcryptPasswordEncoder" /> -->
			
			<security:password-encoder ref="bcryptPasswordEncoder" />
			
		</security:authentication-provider>
	</security:authentication-manager>

</beans>

- 디버그 모드로 이행

- http://localhost/board/register


- http://localhost/logout

- http://localhost/board/register


[home.jsp]

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page session="false" %>
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="sec" %>
<html>
	<head>
		<title>Home</title>
	</head>
	<body>
		<h1>
			Hello world!  
		</h1>
		
		<P>  The time on the server is ${serverTime}. </P>
		
		<!--  
			로그인을 하지 않은 경우
			isAnonymous() : 익명의 사용자의 경우(로그인을 하지 않은 경우도 해당)
		-->
		<sec:authorize access="isAnonymous()">
			<a href="/login">로그인</a>
		</sec:authorize>
		
		<!--  
			인증된 사용자인 경우
			isAuthenticated() : 인증된 사용자면 true
		-->
		<sec:authorize access="isAuthenticated()">
			<a href="/logout">로그아웃</a>
		</sec:authorize>
		
		<div>
			<a href="/board/list">Board</a>
		</div>
		<div>
			<a href="/notice/list">Notice</a>
		</div>
	</body>
</html>

<%@ 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>BOARD REGISTER</title>
	</head>
	<body>
		<h3>BOARD REGISTER : access to member</h3>
		
		<sec:authentication property="principal.member.userName" var="name" />
		<sec:authentication property="principal.member.userId" var="id" />
		<sec:authentication property="principal.member.userPw" var="pw" />
		
		<p>
			사용자명 : ${name }<br />
			아이디 : ${id }<br />
			비밀번호 : ${pw }<br />
		</p>
		
		<p>principal : <sec:authentication property="principal" /></p>
		<p>principal.member : <sec:authentication property="principal.member" /></p>
		
		<p>
			<sec:authorize access="hasRole('ROLE_MEMBER')">
				${name }님의 역할명은 회원입니다!
			</sec:authorize>
			<sec:authorize access="hasRole('ROLE_ADMIN')">
				${name }님의 역할명은 관리자입니다!
			</sec:authorize>
		</p>
	</body>
</html>

<%@ 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>NOTICE REGISTER</title>
	</head>
	<body>
		<h3>NOTICE REGISTER : access to admin</h3>
		
		<sec:authentication property="principal.member.userName" var="name" />
		<sec:authentication property="principal.member.userId" var="id" />
		<sec:authentication property="principal.member.userPw" var="pw" />
		
		<p>
			사용자명 : ${name }<br />
			아이디 : ${id }<br />
			비밀번호 : ${pw }<br />
		</p>
		
		<p>principal : <sec:authentication property="principal" /></p>
		<p>principal.member : <sec:authentication property="principal.member" /></p>
		
		<p>
			<sec:authorize access="hasRole('ROLE_MEMBER')">
				${name }님의 역할명은 회원입니다!
			</sec:authorize>
			<sec:authorize access="hasRole('ROLE_ADMIN')">
				${name }님의 역할명은 관리자입니다!
			</sec:authorize>
		</p>
	</body>
</html>

<%@ 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>
		
		<div>
			<a href="/board/register">Register</a>
		</div>
		
		<div>
			<a href="/">Home</a>
		</div>
	</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>
		
		<div>
			<a href="/notice/register">Register</a>
		</div>
		
		<div>
			<a href="/">Home</a>
		</div>
	</body>
</html>

- http://localhost/

- http://localhost/board/register

- http://localhost/notice/register


create table persistent_logins(
    username varchar2(64) not null,
    series varchar2(64) not null,
    token varchar2(64) not null,
    last_used date not null,
    constraint pk_persistent_logins primary key(series)
);

[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>
	<!-- <bean id="customPasswordEncoder" class="kr.or.ddit.security.CustomNoOpPasswordEncoder"></bean> -->
	<bean id="bcryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"></bean>
	<bean id="customUserDetailsService" class="kr.or.ddit.security.CustomUserDetailsService"></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" />
		
		<!--  
			14. 자동 로그인 적용
			- 데이터 소스를 지정하고 테이블을 이용해서 기존 로그인 정보를 기록
			- 쿠키의 유효 시간을 지정한다. (604800 : 7일)
		-->
		<security:remember-me data-source-ref="dataSource" token-validity-seconds="604800" />
		
		<!-- 9. 로그아웃 처리 : 로그아웃 처리를 위한 URI를 지정하고, 로그아웃한 후에 세션을 무효화한다. -->
		<security:logout logout-url="/logout" invalidate-session="true" 
			delete-cookies="remember-me, JSESSION_ID" />
	</security:http>
	
	<security:authentication-manager>
		<security:authentication-provider user-service-ref="customUserDetailsService">
			<!-- <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:jdbc-user-service data-source-ref="dataSource"/>
			<security:password-encoder ref="customPasswordEncoder" /> -->
			<!-- 사용자 정의 테이블을 이용한 인증/인가 처리 끝 -->
			
			<!-- 
				UserDetailsService를 설정하면서 데이터베이스 연동 후 사용자가 정의한 테이블로 mapper를 통한 데이터 바인딩으로 
				인증/인가 진행 시 주석
			-->
			<!-- <security:jdbc-user-service data-source-ref="dataSource" 
			users-by-username-query="select user_id, user_pw, enabled from member where user_id = ?" 
			authorities-by-username-query="select m.user_id, ma.auth from member m, member_auth ma where ma.user_no = m.user_no and m.user_id = ?" />
			<security:password-encoder ref="bcryptPasswordEncoder" /> -->
			
			<security:password-encoder ref="bcryptPasswordEncoder" />
			
		</security:authentication-provider>
	</security:authentication-manager>

</beans>

<%@ 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 />
			<input type="checkbox" name="remember-me" /> Remember Me <br />
			<button type="submit">로그인</button>
			<sec:csrfInput />
		</form>
	</body>
</html>

- http://localhost/

제이세션 삭제 후에 f5 하면 그대로 남아 있음. 자동 로그인 성공!
로그아웃 하면 새로운 제이세션 아이디가 만들어지면서 초기화됨.


 

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

231213_SPRING 2 (16-1)  (0) 2023.12.12
231212_SPRING 2 (15-2)  (0) 2023.12.12
231211_SPRING 2 (14-1)  (0) 2023.12.11
231208_SPRING 2 (13-2)  (0) 2023.12.08
231208_SPRING 2 (13-1)  (0) 2023.12.08