스프링 시큐리티(Spring Seurity) 프레임워크에서 제공하는 클래스 중 하나로 비밀번호를 암호화하는 데 사용할 수 있는 메서드를 가진 클래스
BCrypt 해싱 함수(BCrypt hashing function)를 사용해서 비밀번호를 인코딩해주는 메서드와 사용자의 의해 제출된 비밀번호와 저장소에 저장되어 있는 비밀번호의 일치 여부를 확인해주는 메서드를 제공
PasswordEncoder 인터페이스를 구현한 클래스
단순히 해시 하는 것 뿐만 아니라 Salt를 넣는 작업까지 하므로, 입력값이 같음에도 불구하고 매번 다른 encoded된 값을 return
🔎 encode( ): 패스워드를 암호화해주는 메서드
🔎 matchers( ): 제출된 인코딩 되지 않은 패스워드(일치 여부를 확인하고자 하는 패스워드)와 인코딩 된 패스워드의 일치 여부를 확인
🔎 upgradeEncoding( ): 더 나은 보안을 위해서 인코딩 된 암호를 다시 한번 더 인코딩해야 하는 경우에 사용
✔ 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타입으로 비교 } }
- 결과
사용자가 자신의 의지와는 무관하게 공격자가 의도한 행위(수정, 삭제, 등록 등)를 특정 웹사이트에 요청하게 하는 공격을 말한다.
사용자가 의도하지 않았지만 자신도 모르게 서버를 공격하게 되는 경우로, 공격자가 만든 악성 페이지를 통해 사용자는 자신도 모르게 공격을 수행
사용자가 로그인되어있는 상태여야한다는 조건
CAPCHA 사용
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>