[Spring] 스프링 시큐리티 설정 및 이해하기 🔐

leesoyeon·2023년 10월 3일
0

Spring

목록 보기
3/3

'스프링 시큐리티' 언제 사용하나 ? 🤔

비밀번호와 같이 민감한 정보들을 암호화 하고 싶거나,
권한 별로 동작에 제한을 걸고 싶은 경우 (특정 url 접근 불가 등)
spring security를 사용하면 된다.


인증과 인가

  • 인증 (Authentication)
    'ㅇㅇ이가 내가 ㅇㅇ이야' 라고 했을 때
    진짜인지 아닌가 확인
    → '그 사람이 본인인지 아닌지 확인하는 것이 인증' - identity

  • 인가 (Authorization)
    → 권한
    ㅇㅇ이가 맞는지 확인했음. 맞음. 그래서 들여보냄.
    하지만 권한에 따라 할 수 있는 행동이 다름. 제한됨.


시큐리티 로깅 설정

프로젝트 설정 - project facts

  • Dynamic Web Module 4.0
    Runtime에 톰캣 체크
  • Java 1.8

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에는 인코딩된 패스워드를 넣어주면 됨


auto-config

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 하기

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

	<import resource="security-context.xml" />
	
</beans>

💡 tip

쿠키 지울 때 인터넷 사용 기록 삭제에서 지워도 되고
F12 - 애플리케이션 - 쿠키 삭제도 가능
(권장은 방문 기록에서 삭제하는 방식)


java - Authentication

@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]]]

jsp

<%@ 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'라고 함


ajax로 사용해보기

<%@ 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>

0개의 댓글