비밀번호와 같이 민감한 정보들을 암호화 하고 싶거나,
권한 별로 동작에 제한을 걸고 싶은 경우 (특정 url 접근 불가 등)
spring security를 사용하면 된다.
인증 (Authentication)
'ㅇㅇ이가 내가 ㅇㅇ이야' 라고 했을 때
진짜인지 아닌가 확인
→ '그 사람이 본인인지 아닌지 확인하는 것이 인증' - identity
인가 (Authorization)
→ 권한
ㅇㅇ이가 맞는지 확인했음. 맞음. 그래서 들여보냄.
하지만 권한에 따라 할 수 있는 행동이 다름. 제한됨.
프로젝트 설정 - project facts
src/main/resources에 log4j.dtd 파일 추가
web.xml에 서블릿 설정 변경
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
log4j.xml에 시큐리티 추가
<logger name="org.springframework.security">
<level value="info" />
</logger>
<logger name="com.ddit.sec">
<level value="debug" />
</logger>
level value ="info"에서 debug 로 변경
HomeController에서
@Slf4j 어노테이션 추가
logger logger... 지우기
logger.info... 이런식으로 되어있는 거 log.info로 바꿈
시큐리티 패스워드 인코딩이 어떤식으로 돌아가는지 살펴보기
// 스프링 시큐리티에서 주로 사용하는 패스워드인코더
// 보안상 디코딩은 지원 안 함
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
log.debug("angle : {}", bCryptPasswordEncoder.encode("angel"));
log.debug("angma : {}", bCryptPasswordEncoder.encode("angma"));
String angel = bCryptPasswordEncoder.encode("angma");
log.debug("isSame : {}", bCryptPasswordEncoder.matches("angel", angel));
// 인코딩 된 값을 DB에 저장하면 됨
패스워드 인코딩 핵심 메소드 2개 !
encode 메소드 : 인코딩하는 메소드 (리턴 string 타입)
matches 메소드 : 사용자가 입력한 패스워드가 전에 인코딩 한 거랑 일치하는지 확인하는 메소드 (리턴 boolean 타입)
↳ 일반 문자열과 암호화 된 거랑 비교
DB에는 인코딩된 패스워드를 넣어주면 됨
spring 폴더에 security-context.xml
auto-config="true" 해주면
get으로 login에 오면 spring에서 만든 Login 페이지 보여줌
post으로 login에 오면 알아서 로그인 인증 처리 해줌
<?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-5.8.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 패스워드 인코더 빈에 등록 -->
<bean id="bCryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"></bean>
<!-- 웹 보안 -->
<security:http auto-config="true">
<!-- 인터셉터로 잡기 -->
<security:intercept-url pattern="/*" access="hasRole('ROLE_SOYEON')"/>
<!-- /* => 모든 url-->
<!-- access => 해당 권환을 가진 사람만 접근 가능하도록-->
</security:http>
<!-- 인증 매니저 : 여러가지 인증 시스템(프로바이더)을 관리하는 애 -->
<security:authentication-manager>
<!-- 인증 프로바이더 : 인증 방식, 인증 어떤 거로 할 건지 정해야 됨 -->
<security:authentication-provider>
<!-- 스프링에서 패스워드 인코더 무조건 사용하게 함 -->
<!-- 암호화 하는 건 'bCryptPasswordEncoder' 를 reference 해서 쓰라고 알려줌 -->
<security:password-encoder ref="bCryptPasswordEncoder"/>
<!-- 유저 직접 등록하겠다 -->
<!-- '아이디 패스워드 인증 방식' 이라고 부름 -->
<security:user-service>
<!-- 패스워드 자리에는 무조건 암호화된 게 들어가야 함 -->
<!-- ROLE 뒤에는 원하는 대로 쓰면 됨 -->
<security:user name="soyeon" password="$2a$10$jOjiCOPD2nai0vkLML1qteqkm7LGSrMCCzWwmtq/fUI2pO5h5CoiK" authorities="ROLE_SOYEON" />
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
</beans>
여기까진 설정 파일만 만들어 준 것.
이걸 읽어 갈 수 있게 해줘야 함.
(tomcat이 자동으로 읽어가는 게 web.xml)
web.xml 보면 root-context.xml을 자동으로 읽어가도록 설정 되어있음
그렇기 때문에 root-context.xml에서 security-content.xml을 import 하기
<?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 -->
<import resource="security-context.xml" />
</beans>
쿠키 지울 때 인터넷 사용 기록 삭제에서 지워도 되고
F12 - 애플리케이션 - 쿠키 삭제도 가능
(권장은 방문 기록에서 삭제하는 방식)
@GetMapping("/soyeon")
public String soyeon(Authentication auth) {
log.debug("auth : {}", auth);
log.debug("auth : {}", auth.getPrincipal());
log.debug("auth : {}", auth.getAuthorities());
// 시큐리티 포인트!
SecurityContext secCont = SecurityContextHolder.getContext();
log.debug("check : {}", secCont);
return "soyeon";
}
로그 출력 결과
DEBUG: com.ddit.sec.controller.SecController - auth : UsernamePasswordAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=soyeon, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_SOYEON]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=48AA2904628AFEEE0CC479ECA1640E59], Granted Authorities=[ROLE_SOYEON]]
DEBUG: com.ddit.sec.controller.SecController - auth : org.springframework.security.core.userdetails.User [Username=soyeon, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_SOYEON]]
DEBUG: com.ddit.sec.controller.SecController - auth : soyeon
DEBUG: com.ddit.sec.controller.SecController - auth : null
DEBUG: com.ddit.sec.controller.SecController - auth : [ROLE_SOYEON]
DEBUG: com.ddit.sec.controller.SecController - check : SecurityContextImpl [Authentication=UsernamePasswordAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=soyeon, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_SOYEON]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=6443E0CA22F78293D9B29F087A647612], Granted Authorities=[ROLE_SOYEON]]]
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!-- 시큐리티 태그라이브러리 추가 -->
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="sec" %>
<html>
<head>
<title>soyeon</title>
</head>
<body>
<h1>soyeon page</h1>
<form action="/logout" method="post">
<!-- 토큰 값을 가지고 있는 input type=hidden 생성됨 -->
<!-- <input type="hidden" name="_csrf" value="토큰값"> -->
<sec:csrfInput/>
<button>LOGOUT</button>
</form>
</body>
</html>
csrf
전혀 다른 cross site에서 내용을 조작하려고 하는 걸 'csrf'라고 함
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="sec" %>
<html>
<head>
<title>soyeon</title>
</head>
<body>
<h1>soyeon page</h1>
<form action="/logout" method="post">
<sec:csrfInput/>
<button>LOGOUT</button>
</form>
<input type="text" id="btn" value="BUTTON"><br>
<button onclick="fAjax()">AJAX</button>
<script>
// csrf 토큰을 get방식에는 안 보내도 됨! (나머진 보내야 함)
// 서버에서 발행된 헤더네임과 토큰갑사 저장
var header = '${_csrf.headerName}';
var token = '${_csrf.token}';
function fAjax() {
let xhr = new XMLHttpRequest();
xhr.open("post", "/logout", true);
// 바닐라 자바스크립트 사용시 AJAX send 전에 헤더값 세팅 필요
xhr.setRequestHeader(header, token);
xhr.onreadystatechange = function () {
if(xhr.readyState == 4 && xhr.status == 200) {
console.log(xhr.responseText);
}
}
xhr.send();
}
</script>
</body>
</html>