80일 차 - 스프링 시큐리티, 암호화 및 CSRF (23.04.21)

yvonne·2023년 4월 21일
0

📂Spring

목록 보기
12/18
post-thumbnail

📝 암호화

📍 BCryptPasswordEncoder

스프링 시큐리티(Spring Seurity) 프레임워크에서 제공하는 클래스 중 하나로 비밀번호를 암호화하는 데 사용할 수 있는 메서드를 가진 클래스

  • BCrypt 해싱 함수(BCrypt hashing function)를 사용해서 비밀번호를 인코딩해주는 메서드와 사용자의 의해 제출된 비밀번호와 저장소에 저장되어 있는 비밀번호의 일치 여부를 확인해주는 메서드를 제공

  • PasswordEncoder 인터페이스를 구현한 클래스

  • 단순히 해시 하는 것 뿐만 아니라 Salt를 넣는 작업까지 하므로, 입력값이 같음에도 불구하고 매번 다른 encoded된 값을 return


  • 🔎 encode( ): 패스워드를 암호화해주는 메서드

    • 반환 타입은 String 타입
    • 똑같은 비밀번호를 해당 메서드를 통하여 인코딩하더라도 매번 다른 인코딩 된 문자열을 반환
  • 🔎 matchers( ): 제출된 인코딩 되지 않은 패스워드(일치 여부를 확인하고자 하는 패스워드)와 인코딩 된 패스워드의 일치 여부를 확인

    • 첫 번째 매개변수는 일치 여부를 확인하고자 하는 인코딩 되지 않은 패스워드를 입력하고, 두 번째 매개변수는 인코딩 된 패스워드를
    • 반환 타입은 boolean
  • 🔎 upgradeEncoding( ): 더 나은 보안을 위해서 인코딩 된 암호를 다시 한번 더 인코딩해야 하는 경우에 사용

    • 매개변수는 인코딩 필요 여부를 확인하고자 하는 인코딩 된 패스워드(String 타입)를 입력
    • 반환 타입은 인코딩이 필요한 경우 true를, 필요하지 않은 경우는 false를 입력

✔ SecurtiyConfig.java

package edu.global.ex.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import edu.global.ex.security.CustomUserDetailsService;
import edu.global.ex.vo.UserVO;

@Configuration // @Component + 의미(설정할수 있는 파일)
@EnableWebSecurity // 스프링 시큐리티 필터가 스프링 필터체인에 등록됨 = 스프링 시큐리티를 작동 시키는 파일 이라는걸 알려줌 - 스프링 한테.
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	private CustomUserDetailsService customUserDetailsService;

	@Bean // ioc 컨테이너에 객체 생성하라는 의미
	public PasswordEncoder bCryptPasswordEncoder() {
		return new BCryptPasswordEncoder();
	}
	
	@Bean 
	public UserVO userVO() {
		UserVO vo = new UserVO();
		vo.setPassword("메롱");
		vo.setUsername("메롱");
		return vo;
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		// 우선 CSRF설정을 해제한다.
		// 초기 개발시만 해주는게 좋다.
		http.csrf().disable();
		http.authorizeRequests().antMatchers("/user/**").hasAnyRole("USER") // /user/userHome 치고 들어오면 유저권한으로 로그인 창 띄움
				.antMatchers("/admin/**").hasAnyRole("ADMIN") // /admin/adminHome 치고 들어오면 관리자권한으로 로그인 창 띄움
				.antMatchers("/**").permitAll(); // 그 외로 치고 들어오면 권한 체크 없이 전부 허가

//		http.formLogin(); // 스프링 시큐리티에 있는 기본 로그인 폼을 사용하겠다.
		http.formLogin().loginPage("/login") // login할 때의 페이지 url 연결
				.usernameParameter("id") // login.jsp에서 name 바꿔주고 싶을 때 셋팅하는 부분
				.passwordParameter("pw").permitAll(); // 모든 유저가 로그인 화면을 볼 수 있게 한다.
	}

	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {

		auth.userDetailsService(customUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());

	}

}

✔ UserMapperTest.java

package edu.global.ex.mapper;

import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import edu.global.ex.vo.UserVO;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@SpringBootTest

class UserMapperTest {

   @Autowired
   private UserMapper userMapper;

   /*
    * @Test void testInsertUser() {
    * 
    * // @Insert("insert into users(username,password,enabled) values(#{username},#{password},#{enabled})"
    * ) // public int insertUser(UserVO userVO); //
    * // @Insert("insert into AUTHORITIES (username,AUTHORITY) values(#{username},'ROLE_USER')"
    * ) // public void insertAuthorities(UserVO UserVO);
    * 
    * UserVO user = new UserVO(); user.setUsername("kim2"); user.setPassword(new
    * BCryptPasswordEncoder().encode("kim2")); user.setEnabled(1);
    * 
    * userMapper.insertUser(user); userMapper.insertAuthorities(user); }
    * 
    * @Test void testInserAdmintUser() {
    * 
    * UserVO user = new UserVO(); user.setUsername("admin2"); user.setPassword(new
    * BCryptPasswordEncoder().encode("admin2")); user.setEnabled(1);
    * 
    * userMapper.insertUser(user); userMapper.insertAdminAuthorities(user);
    *  
    * }
    */
  
   @Autowired
   private PasswordEncoder passwordEncoder;
  
   @Autowired
   private UserVO userVO;
  
   @Test
   void testPassWordEncoder() {
     
      String plainPW = "1234";
      String encodePW= passwordEncoder.encode(plainPW);
     
      System.out.println(plainPW + " : " + encodePW);
      System.out.println(passwordEncoder.matches(plainPW, encodePW));
      System.out.println(userVO);
     
      //assertNotEquals(plainPW, encodePW); // 두 변수가 다를 때 성공
     // assertEquals(plainPW, encodePW); // 두 변수가 같을 때 성공 (String 비교)
     
      assertTrue(new BCryptPasswordEncoder().matches(plainPW, encodePW)); // return 값이 true,false인지 boolean타입으로 비교 
  
   }

}
  • 결과




📝 CSRF (Cross Site Request Forgery) - 사이트간 요청 위조

  • 사용자가 자신의 의지와는 무관하게 공격자가 의도한 행위(수정, 삭제, 등록 등)를 특정 웹사이트에 요청하게 하는 공격을 말한다.

  • 사용자가 의도하지 않았지만 자신도 모르게 서버를 공격하게 되는 경우로, 공격자가 만든 악성 페이지를 통해 사용자는 자신도 모르게 공격을 수행

  • 사용자가 로그인되어있는 상태여야한다는 조건


📍 방어방법

  1. CAPCHA 사용

  2. CSRF 토큰 사용 (스프링 시큐리티)

✔ SecurityConfig.java

package edu.global.ex.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import edu.global.ex.security.CustomUserDetailsService;
import edu.global.ex.vo.UserVO;

@Configuration // @Component + 의미(설정할수 있는 파일)
@EnableWebSecurity // 스프링 시큐리티 필터가 스프링 필터체인에 등록됨 = 스프링 시큐리티를 작동 시키는 파일 이라는걸 알려줌 - 스프링 한테.
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	private CustomUserDetailsService customUserDetailsService;

	@Bean // ioc 컨테이너에 객체 생성하라는 의미
	public PasswordEncoder bCryptPasswordEncoder() {
		return new BCryptPasswordEncoder();
	}
	
	@Bean 
	public UserVO userVO() {
		UserVO vo = new UserVO();
		vo.setPassword("메롱");
		vo.setUsername("메롱");
		return vo;
	}

	@Override 
	protected void configure(HttpSecurity http) throws Exception {

		//http.csrf().disable(); // csrf 설정
		http.authorizeRequests().antMatchers("/user/**").hasAnyRole("USER") // /user/userHome 치고 들어오면 유저권한으로 로그인 창 띄움
				.antMatchers("/admin/**").hasAnyRole("ADMIN") // /admin/adminHome 치고 들어오면 관리자권한으로 로그인 창 띄움
				.antMatchers("/**").permitAll(); // 그 외로 치고 들어오면 권한 체크 없이 전부 허가

//		http.formLogin(); // 스프링 시큐리티에 있는 기본 로그인 폼을 사용하겠다.
		http.formLogin().loginPage("/login") // login할 때의 페이지 url 연결
				.usernameParameter("id") // login.jsp에서 name 바꿔주고 싶을 때 셋팅하는 부분
				.passwordParameter("pw").permitAll(); // 모든 유저가 로그인 화면을 볼 수 있게 한다.
	}

	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {

auth.userDetailsService(customUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());

//      auth.inMemoryAuthentication()
//           .withUser("user").password("{noop}user").roles("USER").and()
//           .withUser("admin").password("{noop}admin").roles("ADMIN");

	}

}

✔ login.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>로그인 페이지</title>
</head>

<body onload="document.f.id.focus();">

<h3>아이디와 비밀번호를 입력해주세요.</h3>

<c:url value="/login" var="loginUrl" />
<p>${loginUrl}</p>
<form:form name="f" action="${loginUrl}" method="POST"> 
<%-- tag library 에서 csrf 처리 기능이 있는 form 태그를 사용하기 위해 form:form으로 사용 --%>
    <c:if test="${param.error != null}">
        <p>아이디와 비밀번호가 잘못되었습니다.</p>
    </c:if>
    <c:if test="${param.logout != null}">
        <p>로그아웃 하였습니다.</p>
    </c:if> 
    <p>
        <label for="username">아이디</label>
        <input type="text" id="id" name="id" />
    </p> 
    <p>
        <label for="password">비밀번호</label>
        <input type="password" id="password" name="pw"/>
    </p>
 <%-- <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />  --%> 
 <%--서버 쪽에 생성한 번호를 클라이언트에게 보내는 코드 --%> 
    <button type="submit" class="btn">로그인</button>
</form:form>

</body>
</html>
profile
개발 연습장

0개의 댓글