// 스프링 시큐리티, Spring Security
- 기존 Servlet/JSP > 인증(증명, 인증 티켓) + 허가(권한) > 세션 기반 인증
- 동작 방식 > 서블릿 필터와 스프링 인터셉터를 사용하여 보안을 처리
- 일괄 설정(pom.xml, ojdbc6, web.xml, root-context.xml, https://log4jdbc.log4j2.properties, log4j(log level))
- 스프링 시큐리티 설정
~ pom.xml : 의존성 4개 추가
<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>
~ /webapp/WEB-INF/spring/ > security-context.xml
- root-context.xml, servlet-context/xml 처럼 스프링을 설정하는 파일(Context)
- 기존 컨텍스트와 분리해서 설정할 수 있다. > 단독 설정
- Spring Bean Configuration File
- xsi:schemaLocation에서 5.0 버전 지우기
<?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
- 스프링 시큐리티가 스프링 MVC에서 사용 > 필터를 사용해서 스프링 시큐리티가 스프링 동작에 관여할 수 있도록 설정(필터 등록)
1. security-context.xml 인식 > web.xml 추가
2. security-context.xml 기본 구문 설정
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" 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_2_5.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>
<filter>
<filter-name>encodingFilter</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>
</filter>
<filter-mapping>
<filter-name>encodingFilter</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>
// 인증, Authentication & 허가(권한부여, Authorization)
~ AuthenticationManager
- 인증 매니저
- 가장 중심 + 가장 중요한 역할
- 인증을 담당한다.
~ AuthenticationProvider
- 인증 제공자
- 실제로 인증 작업을 진행한다.
~ UserDetailsService
- 사용자 정보 + 사용자 권한 관리
// 테스트를 위한 URI 설계
- 통제가 필요한 URI를 설계 + 스프링 시큐리티를 적용
- /index.do > 로그인(O), 로그인(X) > 모든 사용자 접근 가능
- /member.do > 로그인(O) > 회원만 접근 가능
- /admin.do > 로그인(O) > 회원 중 관리자 권한이 있는 사용자만 접근 가능
- 파일 생성
~ "com.test.controller" > Test.Controller.java
package com.test.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import lombok.extern.log4j.Log4j;
@Controller
@Log4j
public class TestController {
@GetMapping("/index.do")
public String index() {
log.info("TestController > 모든 사용자 페이지");
return "index";
}
@GetMapping("/member.do")
public String member() {
log.info("TestController > 회원 전용 페이지");
return "member";
}
@GetMapping("/admin.do")
public String admin() {
log.info("TestController > 관리자 전용 페이지");
return "admin";
}
@GetMapping("/template.do")
public String template() {
return "template";
}
}
~ views > index.jsp
> member.jsp
> admin.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix = "c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="https://me2.do/5BvBFJ57">
<style>
</style>
</head>
<body>
<%@ include file="/WEB-INF/views/inc/header.jsp" %>
<h2>
<span class="material-symbols-outlined">favorite</span>
Index Page
</h2>
<div>모든 사용자가 접근 가능합니다.</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>
</script>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix = "c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="https://me2.do/5BvBFJ57">
<style>
</style>
</head>
<body>
<%@ include file="/WEB-INF/views/inc/header.jsp" %>
<h2>
<span class="material-symbols-outlined">heart_plus</span>
Member Page
</h2>
<div>회원만 접근 가능합니다.</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>
</script>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix = "c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="https://me2.do/5BvBFJ57">
<style>
</style>
</head>
<body>
<%@ include file="/WEB-INF/views/inc/header.jsp" %>
<h2>
<span class="material-symbols-outlined">heart_check</span>
Admin Page
</h2>
<div>관리자만 접근 가능합니다.</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>
</script>
</body>
</html>
~ views > "inc" > header.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix = "c" uri="http://java.sun.com/jsp/jstl/core" %>
<header>
<h1>Spring Security</h1>
<ul>
<li><a href="/security/index.do">Index</a></li>
<li><a href="/security/member.do">Member</a></li>
<li><a href="/security/admin.do">Admin</a></li>
<li class="divider"></li>
<li><a href="/security/customlogin.do">Login</a></li>
<li><a href="/security/customlogout.do">Logout</a></li>
</ul>
</header>
// 로그인, 로그아웃 > 인증
- 단순 로그인 처리
- 자동 생성 로그인 페이지 사용
- 계정 정보 > 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">
<security:http>
<!-- 특정 URI + 액세스 설정 -->
<!--
pattern: URI 패턴
access: 권한 > 표현식(권장★★★) or 권한명
인증 > Role(자격 - 관리자,매니저,팀장,담당자)
> Authority(권한 - 글쓰기,열람,승인)
-->
<security:intercept-url pattern="/index.do" access="permitAll"/>
<security:intercept-url pattern="/member.do" access="hasRole('ROLE_MEMBER')"/>
<security:intercept-url pattern="/admin.do" access="hasRole('ROLE_ADMIN')"/>
</security:http>
<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<security:user name="hong" password="{noop}1111" authorities="ROLE_MEMBER"/>
<security:user name="admin" password="{noop}1111" authorities="ROLE_ADMIN, ROLE_MEMBER"/>
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
</beans>
- There is no PasswordEncoder mapped for the id "null"
- 스프링 4버전까지는 암호 유무 선택
- 스프링 5버전부터는 반드시 구현 > PasswordEncoder 사용
- 관리자 사용자 설정
1. 비회원(X)
2. 회원('ROLE_MEMBER')
3. 관리자('ROLE_MEMBER', 'ROLE_ADMIN')
// 접근 권한 메시지 처리
- 로그인을 한 상태에서 접근 불가능한 URI를 접근하면 에러가 발생 > 403
1. AccessDeniedHandler 직접 구현
~ "com.test.auth" > CustomAccessDeniedHandler.java
package com.test.auth;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import lombok.extern.log4j.Log4j;
@Log4j
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
// 403 발생 > request, response 조작
log.error("Access Denied Handler");
log.error("Redirect..");
response.sendRedirect("/security/accesserror.do");
}
}
~ 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">
<!-- 403 처리 담당자 -->
<bean id="customAccessDenied" class="com.test.auth.CustomAccessDeniedHandler"></bean>
<security:http>
<!-- 특정 URI + 액세스 설정 -->
<!--
pattern: URI 패턴
access: 권한 > 표현식(권장★★★) or 권한명
인증 > Role(자격 - 관리자,매니저,팀장,담당자)
> Authority(권한 - 글쓰기,열람,승인)
-->
<security:intercept-url pattern="/index.do" access="permitAll"/>
<security:intercept-url pattern="/member.do" access="hasRole('ROLE_MEMBER')"/>
<security:intercept-url pattern="/admin.do" access="hasRole('ROLE_ADMIN')"/>
<!-- ref에 bean id 넣기 -->
<security:access-denied-handler ref="customAccessDenied"/>
</security:http>
<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<security:user name="hong" password="{noop}1111" authorities="ROLE_MEMBER"/>
<security:user name="admin" password="{noop}1111" authorities="ROLE_ADMIN, ROLE_MEMBER"/>
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
</beans>
2. 단순 URI 지정 > forward
- 에러 페이지 작성
~ "com.test.controller" > AuthController.java
package com.test.controller;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import lombok.extern.log4j.Log4j;
@Controller
@Log4j
public class AuthController {
@GetMapping("/accesserror.do")
public String accesserror(Authentication auth, Model model) {
log.info("Access Denied: " + auth);
model.addAttribute("msg", "Access Denied: " + auth);
return "accesserror";
}
}
~ views > accesserror.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix = "c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="https://me2.do/5BvBFJ57">
<style>
</style>
</head>
<body>
<%@ include file="/WEB-INF/views/inc/header.jsp" %>
<h2>Access Denied Page</h2>
<div>${SPRING_SECURITY_403_EXCEPTION.getMessage()}</div>
<div>${msg}</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>
</script>
</body>
</html>
~ 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 + 액세스 설정 -->
<!--
pattern: URI 패턴
access: 권한 > 표현식(권장★★★) or 권한명
인증 > Role(자격 - 관리자,매니저,팀장,담당자)
> Authority(권한 - 글쓰기,열람,승인)
-->
<security:intercept-url pattern="/index.do" access="permitAll"/>
<security:intercept-url pattern="/member.do" access="hasRole('ROLE_MEMBER')"/>
<security:intercept-url pattern="/admin.do" access="hasRole('ROLE_ADMIN')"/>
<security:access-denied-handler error-page="/accesserror.do"/>
</security:http>
<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<security:user name="hong" password="{noop}1111" authorities="ROLE_MEMBER"/>
<security:user name="admin" password="{noop}1111" authorities="ROLE_ADMIN, ROLE_MEMBER"/>
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
</beans>
// 로그인 페이지
- 직접 로그인 페이지 구현 > URI 지정
~ AuthController.java> 로그인 페이지 추가
package com.test.controller;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import lombok.extern.log4j.Log4j;
@Controller
@Log4j
public class AuthController {
@GetMapping("/customlogin.do")
public String customlogin(String error, String logout, Model model) {
log.error("error: " + error) ;
log.info("logout: " + logout);
model.addAttribute("error", error);
model.addAttribute("logout", logout);
return "customlogin";
}
}
~ views > customlogin.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix = "c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="https://me2.do/5BvBFJ57">
<style>
</style>
</head>
<body>
<%@ include file="/WEB-INF/views/inc/header.jsp" %>
<h2>Custom Login Page</h2>
<div class="message">${error}</div>
<div class="message">${logout}</div>
<form method="POST" action="/security/login">
<table>
<tr>
<th>아이디</th>
<td><input type="text" name="username" required></td>
</tr>
<tr>
<th>암호</th>
<td><input type="password" name="password" required></td>
</tr>
</table>
<div>
<button class="in">로그인</button>
</div>
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}">
</form>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>
</script>
</body>
</html>
- <form>의 action이 "/login"이다. + POST
- <input name="username">, <input name="password"> 의 name이 예약어이다.★★★★★
※ 스프링 시큐리티에서 username의 의미★★★
- 일반 시스템(userid) == 스프링 시큐리티(username)
- '홍길동' != username
- 'hong' == username
- <input type="hidden" name="_csrf" value="1219d8e6-7f5c-4391-89b0-9ca958ef675e">
※ CSRF, Cross-site request forgery
- CSRT 공격 > 토큰
- <security:csrf disabled="true" />
- "/login" + GET 요청 > /customlogin.do 호출
// 로그인 후속 동작
- 로그인 성공 후에 특정한 동작을 하고 싶을 때
- AuthenticationSuccessHandler 인터페이스를 구현
~ "com.test.auth" > CustomLoginSuccessHandler.java
package com.test.auth;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import lombok.extern.log4j.Log4j;
@Log4j
public class CustomLoginSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,Authentication authentication) throws IOException, ServletException {
log.info("Login Success");
//권한별 조치
// - 일반회원 > "/member.do"
// - 관리자 > "/admin.do"
List<String> roleNames = new ArrayList<String>();
authentication.getAuthorities().forEach(authority -> {
roleNames.add(authority.getAuthority());
});
log.info("Role");
if(roleNames.contains("ROLE_ADMIN")) {
response.sendRedirect("/security/admin.do");
return;
}
if(roleNames.contains("ROLE_MEMBER")) {
response.sendRedirect("/security/member.do");
return;
}
response.sendRedirect("/security/index.do");
}
}
~ 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">
<!-- 403 처리 담당자 -->
<bean id="customAccessDenied" class="com.test.auth.CustomAccessDeniedHandler"></bean>
<!-- 로그인 성공 처리 담당자 -->
<bean id="customLoginSuccess" class="com.test.auth.CustomLoginSuccessHandler"></bean>
<security:http>
<!-- 특정 URI + 액세스 설정 -->
<!--
pattern: URI 패턴
access: 권한 > 표현식(권장★★★) or 권한명
인증 > Role(자격 - 관리자,매니저,팀장,담당자)
> Authority(권한 - 글쓰기,열람,승인)
-->
<security:intercept-url pattern="/index.do" access="permitAll"/>
<security:intercept-url pattern="/member.do" access="hasRole('ROLE_MEMBER')"/>
<security:intercept-url pattern="/admin.do" access="hasRole('ROLE_ADMIN')"/>
<security:form-login login-page="/customlogin.do"
authentication-success-handler-ref="customLoginSuccess"/>
<!-- ref에 bean id 넣기 -->
<security:access-denied-handler ref="customAccessDenied"/>
</security:http>
<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<security:user name="hong" password="{noop}1111" authorities="ROLE_MEMBER"/>
<security:user name="admin" password="{noop}1111" authorities="ROLE_ADMIN, ROLE_MEMBER"/>
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
</beans>
// 로그아웃
- 로그인처럼 URI 지정, 핸들러 등록
~ AuthController.java > 메소드 추가
package com.test.controller;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import lombok.extern.log4j.Log4j;
@Controller
@Log4j
public class AuthController {
@GetMapping("/customlogout.do")
public String customlogout() {
log.info("custom logout");
return "customlogout";
}
}
~ views > customlogout.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix = "c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="https://me2.do/5BvBFJ57">
<style>
</style>
</head>
<body>
<%@ include file="/WEB-INF/views/inc/header.jsp" %>
<h2>Custom Logout Page</h2>
<form method="POST" action="/security/customlogout.do">
<div>
<button class="out">로그아웃</button>
</div>
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}">
</form>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>
</script>
</body>
</html>
// 계정 정보
- XML > <security:user name="hong" password="{noop}1111" authorities="ROLE_MEMBER">
- InMemoryUserDetailsManager를 사용한 방식
- 데이터베이스를 활용한 계정 정보
1.미리 정해진 구조의 스키마 사용 > 편해짐 > 제약 심함
~ SecurityTest > script.sql
create table users (
username varchar2(50) not null primary key,
password varchar2(50) not null,
enabled char(1) default '1'
);
create table authorities (
username varchar2(50) not null,
authority varchar2(50) not null,
constraint fk_authorities_users foreign key(username) references users(username)
);
create unique index ix_auth_username on authorities (username, authority);
insert into users (username, password) values('hong', '1111');
insert into users (username, password) values('test', '1111');
insert into users (username, password) values('admin', '1111');
insert into authorities (username, authority) values('hong', 'ROLE_MEMBER');
insert into authorities (username, authority) values('test', 'ROLE_MEMBER');
insert into authorities (username, authority) values('admin', 'ROLE_MEMBER');
insert into authorities (username, authority) values('admin', 'ROLE_ADMIN');
~ "com.test.auth" > CustomNoOpPasswordEncoder.java
package com.test.auth;
import org.springframework.security.crypto.password.PasswordEncoder;
import lombok.extern.log4j.Log4j;
@Log4j
public class CustomNoOpPasswordEncoder implements PasswordEncoder {
@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);
}
}
~ 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">
<!-- 403 처리 담당자 -->
<bean id="customAccessDenied" class="com.test.auth.CustomAccessDeniedHandler"></bean>
<!-- 로그인 성공 처리 담당자 -->
<bean id="customLoginSuccess" class="com.test.auth.CustomLoginSuccessHandler"></bean>
<!-- 암호화 객체 -->
<!-- <bean id="customPasswordEncoder" class="com.test.auth.CustomNoOpPasswordEncoder"></bean> -->
<security:http>
<!-- 특정 URI + 액세스 설정 -->
<!--
pattern: URI 패턴
access: 권한 > 표현식(권장★★★) or 권한명
인증 > Role(자격 - 관리자,매니저,팀장,담당자)
> Authority(권한 - 글쓰기,열람,승인)
-->
<security:intercept-url pattern="/index.do" access="permitAll"/>
<security:intercept-url pattern="/member.do" access="hasRole('ROLE_MEMBER')"/>
<security:intercept-url pattern="/admin.do" access="hasRole('ROLE_ADMIN')"/>
<security:form-login login-page="/customlogin.do"
authentication-success-handler-ref="customLoginSuccess"/>
<!-- <security:access-denied-handler error-page="/accesserror.do"/> -->
<!-- ref에 bean id 넣기 -->
<security:access-denied-handler ref="customAccessDenied"/>
<!-- 로그아웃 -->
<security:logout logout-url="/customlogout.do" invalidate-session="true" logout-success-url="/index.do"/>
</security:http>
<security:authentication-manager>
<security:authentication-provider>
<security:jdbc-user-service data-source-ref="dataSource"/>
<security:password-encoder ref="customPasswordEncoder"/>
</security:authentication-provider>
</security:authentication-manager>
</beans>
2. 사용자가 직접 만든 구조의 스키마 사용 > 불편함 > 제약 약함
- 스프링 시큐리티에서 제공하는 기본 스키마 말고, 사용자가 직접 설계한 스키마로 적용
~ SecurityTest > script.sql
create table tbl_member (
userid varchar2(50) not null primary key,
userpw varchar2(100) not null,
username varchar2(100) not null,
regdate date default sysdate,
updatedate date default sysdate,
enabled char(1) default '1'
);
create table tbl_member_auth (
userid varchar2(50) not null,
auth varchar2(50) not null,
constraint fk_member_auth foreign key(userid) references tbl_member(userid)
);
select * from tbl_member;
select * from tbl_member_auth;
※ 스프링 시큐리티
- BCryptPasswordEncoder 클래스를 사용
- bcrypt(Blowfish > 암호화 해시 함수)
- 암호화 가능, 복호화 불가능
~ src/test/java > com.test.security > MemberTest.java (인증을 하는 데 사용)
package com.test.security;
import java.sql.Connection;
import java.sql.PreparedStatement;
import javax.sql.DataSource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"file:src/main/webapp/WEB-INF/spring/root-context.xml", "file:src/main/webapp/WEB-INF/spring/security-context.xml"})
public class MemberTest {
@Autowired
private DataSource ds;
@Autowired
private PasswordEncoder encoder;
@Test
public void testInsertMember() {
String sql = "insert into tbl_member(userid, userpw, username) values (?, ?, ?)";
try {
Connection conn = ds.getConnection();
PreparedStatement stat = conn.prepareStatement(sql);
stat.setString(1, "dog");
stat.setString(2, encoder.encode("1111"));
stat.setString(3, "강아지");
stat.executeUpdate();
stat.setString(1, "cat");
stat.setString(2, encoder.encode("1111"));
stat.setString(3, "고양이");
stat.executeUpdate();
stat.setString(1, "lion");
stat.setString(2, encoder.encode("1111"));
stat.setString(3, "사자");
stat.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}
}
}
~ src/test/java > com.test.security > MemberTest2.java (권한을 확인하는 데 사용)
package com.test.security;
import java.sql.Connection;
import java.sql.PreparedStatement;
import javax.sql.DataSource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"file:src/main/webapp/WEB-INF/spring/root-context.xml", "file:src/main/webapp/WEB-INF/spring/security-context.xml"})
public class MemberTest2 {
@Autowired
private DataSource ds;
@Autowired
private PasswordEncoder encoder;
@Test
public void testInsertMember() {
String sql = "insert into tbl_member_auth(userid, auth) values (?, ?)";
try {
Connection conn = ds.getConnection();
PreparedStatement stat = conn.prepareStatement(sql);
stat.setString(1, "dog");
stat.setString(2, "ROLE_MEMBER");
stat.executeUpdate();
stat.setString(1, "cat");
stat.setString(2, "ROLE_MEMBER");
stat.executeUpdate();
stat.setString(1, "lion");
stat.setString(2, "ROLE_MEMBER");
stat.executeUpdate();
stat.setString(1, "lion");
stat.setString(2, "ROLE_ADMIN");
stat.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}
}
}
~ 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">
<!-- 403 처리 담당자 -->
<bean id="customAccessDenied" class="com.test.auth.CustomAccessDeniedHandler"></bean>
<!-- 로그인 성공 처리 담당자 -->
<bean id="customLoginSuccess" class="com.test.auth.CustomLoginSuccessHandler"></bean>
<!-- 암호화 객체(사용자 정의) -->
<bean id="bcryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"></bean>
<security:http>
<!-- 특정 URI + 액세스 설정 -->
<!--
pattern: URI 패턴
access: 권한 > 표현식(권장★★★) or 권한명
인증 > Role(자격 - 관리자,매니저,팀장,담당자)
> Authority(권한 - 글쓰기,열람,승인)
-->
<security:intercept-url pattern="/index.do" access="permitAll"/>
<security:intercept-url pattern="/member.do" access="hasRole('ROLE_MEMBER')"/>
<security:intercept-url pattern="/admin.do" access="hasRole('ROLE_ADMIN')"/>
<security:form-login login-page="/customlogin.do"
authentication-success-handler-ref="customLoginSuccess"/>
<!-- ref에 bean id 넣기 -->
<security:access-denied-handler ref="customAccessDenied"/>
<!-- 로그아웃 -->
<security:logout logout-url="/customlogout.do" invalidate-session="true" logout-success-url="/index.do"/>
</security:http>
<security:authentication-manager>
<security:authentication-provider>
<security:jdbc-user-service data-source-ref="dataSource" users-by-username-query="select userid username, userpw password, enabled from tbl_member where userid = ?" authorities-by-username-query="select userid username, auth authority from tbl_member_auth where userid = ?" />
<security:password-encoder ref="bcryptPasswordEncoder"/>
</security:authentication-provider>
</security:authentication-manager>
</beans>
'서버 > Spring' 카테고리의 다른 글
[Spring Security] 권한 체크 (0) | 2023.06.22 |
---|---|
[Spring Security] 회원가입, 자동 로그인 (0) | 2023.06.22 |
[스프링(Spring)] 파일 업로드 / 다운로드 (0) | 2023.06.20 |
[스프링(Spring)] MyBatis (1) | 2023.06.16 |
[스프링(Spring)] MVC 에러 처리 (0) | 2023.06.16 |