2023. 11. 9. 03:09ㆍ학부 강의/웹프로그래밍 (Spring)
0. 출처
아직 배우고 있는 중이라 부정확한 정보가 포함되어 있을 수 있습니다!
주의하세요!
올인원 스프링 프레임워크 참고.
1. 데이터베이스
데이터베이스로는 RDBMS인 MariaDB를 사용한다.
- MariaDB
: 오픈 소스 RDBMS
: 오라클이 MySQL을 인수해면서 무료 및 오픈 소스 버전에 대한 미래가 불투명해졌고, 이러한 우려에서 MariaDB가 탄생했다.
: MySQL과 동일한 소스 코드를 기반으로 하며, GPL v2 라이선스를 따른다.
또한 데이터베이스를 쉽고 직관적으로 관리하기 위해서 HeidiSQL을 추가적으로 사용한다.
가. MariaDB 설치
10.11.5 버전으로 설치한다.
- root 유저의 비밀번호는
1234
로 설정했다. (자유롭게 선택하세요) - 3306 포트를 선택했다.
나. HeidiSQL 설치
다. 테이블 만들기
CREATE DATABASE db_library;
USE db_library;
CREATE TABLE tbl_admin_member(
a_m_no INT AUTO_INCREMENT,
a_m_approval INT NOT NULL DEFAULT 0,
a_m_id VARCHAR(20) NOT NULL,
a_m_pw VARCHAR(100) NOT NULL,
a_m_name VARCHAR(20) NOT NULL,
a_m_gender CHAR(1) NOT NULL,
a_m_part VARCHAR(20) NOT NULL,
a_m_position VARCHAR(20) NOT NULL,
a_m_mail VARCHAR(50) NOT NULL,
a_m_phone VARCHAR(20) NOT NULL,
a_m_reg_date DATETIME,
a_m_mod_date DATETIME,
PRIMARY KEY(a_m_no)
);
라. JDBC
- JDBC
: Java Database Connectivity
: 자바에서 데이터베이스에 접속할 수 있도록 하는 자바 API 세트다.
: JDBC는 데이터베이스에서 자료를 쿼리하거나 업데이트하는 방법을 제공한다.
: DBMS가 자바 프로그램의 요청을 이해할 수 있도록 프로토콜을 변환한다.
JDBC는 다양한 데이터베이스와 호환되며, 데이터베이스 업체들은 자신들의 데이터베이스에 맞는 JDBC 드라이버를 제공한다.
데이터베이스와 독립적인 코드를 작성할 수 있게 해준다.
개발자는 다양한 데이터베이스를 같은 방식으로 다룰 수 있고, 이는 코드의 이식성이 증가 및 데이터베이스 교체의 복잡성이 감소로 이어진다.
JDBC를 사용하면 다음과 같은 과정을 거치게 된다.
JDBC 드라이버 로드 → 데이터베이스 연결 → SQL query 작성 → PreparedStatement 생성 → 쿼리 실행
스프링에서는 JdbcTemplate
클래스를 제공하기 때문에 JDBC 드라이버 로드, 데이터베이스 연결과 같은 반복적인 작업보다는 SQL query 작성 및 실행에 집중할 수 있다.
<!-- pom.xml 수정 -->
...
<!-- JDBC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- mariaDB -->
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>3.1.2</version>
</dependency>
</dependencies>
pom.xml
에 mariaDB와 JDBC를 추가한다.
<!-- jdbc-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:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.3.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<!-- <context:property-placeholder location="/WEB-INF/spring/property/real.info.properties" /> -->
<!-- MariaDB connection 객체 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.mariadb.jdbc.Driver"/>
<property name="url" value="jdbc:mariadb://127.0.0.1:3306/db_library" />
<property name="username" value="root"/>
<property name="password" value="1234"/>
</bean>
<!-- JdbcTemplate 객체 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- TransactionManager 객체 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
</beans:beans>
jdbc-context.xml
생성한다.
- MariaDB connection 객체
: MariaDB 드라이버 로드
:127.0.0.1:3306/db_library
에 위치한 MariaDB 데이터베이스에 연결하도록 설정.
:username
은root
,password
는1234
다. - JdbcTemplate 객체
: 앞서 정의한dataSource
빈을 주입받아 데이터베이스 연결을 위해 사용한다. - TransactionManager 객체
: 트랜잭션을 관리.
<!-- web.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/jdbc-context.xml
/WEB-INF/spring/security-context.xml
</param-value>
</context-param>
jdbc-context.xml
의 존재와 위치를 web.xml
에 추가한다.
프로젝트가 서버에서 실행될 때 jdbc-context.xml
을 읽고 JdbcTemplate
객체를 생성한다.
@Component
public class AdminMemberDao {
@Autowired
JdbcTemplate jdbcTemplate;
public boolean isAdminMember(String a_m_id) {
System.out.println("[AdminMemberDao] isAdminMember()");
String sql = "SELECT COUNT(*) FROM tbl_admin_member "
+ "WHERE a_m_id = ?";
int result = jdbcTemplate.queryForObject(sql, Integer.class, a_m_id);
if(result > 0)
return true;
else
return false;
}
...
@Autowired JdbcTemplate jdbcTemplate;
: DAO에서JdbcTemplate
를 사용하기 위해서 의존 객체 자동 주입을 한다.public boolean isAdminMember(String a_m_id) { … }
: 입력한 아이디가 중복인지 검사한다.String sql = "SELECT COUNT(*) FROM tbl_admin_member " + "WHERE a_m_id = ?";
: SQL query로 “tbl_admin_member
테이블에서a_m_id
가?(= 외부에서 입력된 값)
과 같은 row의 수”를 질의한다.int result = jdbcTemplate.queryForObject(sql, Integer.class, a_m_id);
: 결과가Integer
타입의 단일 객체인sql
을 실행한다. 이때a_m_id
를sql
의 인자로 전달한다.
isAdminMember()
로 중복 검사를 하고 통과하면 관리자 정보를 데이터베이스에 저장하기 위해서 insertAdminAccount()
를 구현한다.
@Component
public class AdminMemberDao {
...
public int insertAdminAccount(AdminMemberVo adminMemberVo) {
System.out.println("[AdminMemberDao] insertAdminAccount()");
List<String> args = new ArrayList<String>();
String sql = "INSERT INTO tbl_admin_member(";
if(adminMemberVo.getA_m_id().equals("super admin")) {
sql += "a_m_approval, ";
args.add("1");
}
sql += "a_m_id, ";
args.add(adminMemberVo.getA_m_id());
sql += "a_m_pw, ";
args.add(adminMemberVo.getA_m_pw());
sql += "a_m_name, ";
args.add(adminMemberVo.getA_m_name());
sql += "a_m_gender, ";
args.add(adminMemberVo.getA_m_gender());
sql += "a_m_part, ";
args.add(adminMemberVo.getA_m_part());
sql += "a_m_position, ";
args.add(adminMemberVo.getA_m_position());
sql += "a_m_mail, ";
args.add(adminMemberVo.getA_m_mail());
sql += "a_m_phone, ";
args.add(adminMemberVo.getA_m_phone());
sql += "a_m_reg_date, a_m_mod_date) ";
if(adminMemberVo.getA_m_id().equals("super admin"))
sql += "VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())";
else
sql += "VALUES(?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())";
int result = -1;
try {
result = jdbcTemplate.update(sql, args.toArray());
}catch(Exception e) {
e.printStackTrace();
}
return result;
}
...
String sql = "INSERT INTO tbl_admin_member(";
: INSERT문 문법 →INSERT INTO 테이블_또는_뷰_이름 (컬럼1, 컬럼2, ...) *values* (값1, 값2, ...)
result = jdbcTemplate.update(sql, args.toArray());
:sql
실행. 인자로ArrayList<String> args
를array
로 변환해서 전달한다.
최고 관리자 super admin
만 a_m_approval
가 1
로 설정한다.
보충
아래는 JdbcTemplate
클래스의 몇 가지 핵심 메서드를 표 형식으로 정리한 것입니다:
메서드 유형 | 메서드 이름 | 설명 |
쿼리 | query(String sql, RowMapper rowMapper) | 주어진 SQL을 실행하고 RowMapper를 사용하여 결과를 객체로 매핑 |
쿼리 | queryForObject(String sql, Class requiredType) | 결과가 단일 객체인 SQL 쿼리를 실행 |
업데이트 | update(String sql) | 데이터베이스에서 INSERT, UPDATE, DELETE 작업을 수행하는 SQL을 실행 |
배치 업데이트 | batchUpdate(String... sql) | 배치로 여러 SQL 업데이트를 실행 |
설정 | setDataSource(DataSource dataSource) | JdbcTemplate에 사용할 DataSource를 설정 |
설정 | setQueryTimeout(int queryTimeout) | 쿼리 실행의 최대 시간을 초 단위로 설정 |
트랜잭션 | execute(String sql) | SQL 문을 실행하며 복잡한 트랜잭션과 저장 프로시저 호출에 사용될 수 있음 |
유틸리티 | queryForList(String sql) | 쿼리 결과를 Map의 리스트로 반환 |
유틸리티 | queryForMap(String sql) | 단일 행 결과를 Map으로 반환 |
- JdbcTemplate class reference
출처 : https://ko.wikipedia.org/wiki/JDBC
출처 : ChatGPT 4.0
2. 암호화
보안상 데이터베이스에 비밀번호를 평문으로 보관하는 것은 굉장히 위험하다.
이에 비밀번호를 암호화해서 보관하겠다.
스프링에는 데이터를 암호화하는 spring-security-core라는 모듈을 제공한다.
- spring-security
: Spring Security is a framework that provides authentication, authorization, and protection against common attacks.
: 자바 기반 응용 프로그램을 위한 인증, 인가 및 보안 기능을 제공한다.
데이터를 암호화하기 위해서 사용해 보겠다.
출처 : https://docs.spring.io/spring-security/reference/index.html
가. 설정
<!-- pom.xml -->
<dependencies>
...
<!-- Spring security -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>${org.springframework-version}</version>
</dependency>
</dependencies>
pom.xml
에 spring-security-core
를 추가한다.
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<bean id="bCryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
</beans>
WEB-INF/spring/security-context.xml
을 생성한다.
bcrypt 해시 알고리즘을 사용해서 암호화하는 것을 알 수 있다.
bcrypt는 단방향 암호화를 위해 만들어진 해시 함수로 복호화는 불가능하다.
사용자의 비밀번호는 암호화된 상태로만 저장되고, 로그인 시 사용자가 입력한 비밀번호는 다시 암호화하여 저장된 해시와 비교한다.
비교결과 둘이 일치한다면 비밀번호가 올바른 것으로 간주한다.
(참고로 보다 철저한 보안을 위해 salt도 사용되는 것으로 파악된다.)
<!-- web.xml -->
...
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring/root-context.xml
/WEB-INF/spring/jdbc-context.xml
/WEB-INF/spring/security-context.xml
</param-value>
</context-param>
프로젝트가 서버에서 실행될 때 security-context.xml
을 인식하도록 web.xml
에 추가한다.
나. 암호화 구현
@Component
public class AdminMemberDao {
@Autowired
PasswordEncoder passwordEncoder; // 추가
public int insertAdminAccount(AdminMemberVo adminMemberVo) {
...
sql += "a_m_pw, ";
args.add(passwordEncoder.encode(adminMemberVo.getA_m_pw())); // 추가
...
@Autowired PasswordEncoder passwordEncoder;
: 암호화에 사용할PasswordEncoder
를 자동으로 주입한다.args.add(passwordEncoder.encode(adminMemberVo.getA_m_pw()));
:adminMemberVo.getA_m_pw()
를 암호화해서 저장한다.
http://localhost:8090/library/admin/
접속 → 회원가입 누르기.
최고 관리자 계정을 생성한다.
회원가입에 성공했다.
실제로 암호화된 비밀번호가 데이터베이스에 저장된 것을 확인할 수 있다.
다. 복호화? matches!
앞서 bcrypt 해쉬 알고리즘은 단방향 해시 알고리즘이라서 복호호가 불가능하다고 설명했다.
그러면 복호화하지 않고 어떻게 입력한 비밀번호 정보가 올바른 비밀번호인지 알 수 있을까?
if(!passwordEncoder.matches(adminMemberVo.getA_m_pw(), adminMemberVos.get(0).getA_m_pw()))
짧게 설명하면 다음과 같이 boolean PasswordEncorder.matches(CharSequence rawPassword, String encodedPassword);
를 활용해서 비교한다.
입력된 비밀번호를 암호화해서 데이터베이스에 저장된 해시값과 비교한다.
같다면 올바른 비밀번호를 입력한 것으로 간주한다.
'학부 강의 > 웹프로그래밍 (Spring)' 카테고리의 다른 글
[Spring] Mail 보내기 (2) | 2023.12.21 |
---|---|
[Spring] 관리자 로그인 (2) | 2023.12.20 |
[Spring] 전자 도서관 서비스 1 (0) | 2023.11.08 |
[Spring] Service, DAO 그리고 VO (0) | 2023.11.07 |
[Spring] 클라이언트의 요청이 처리되는 과정 (0) | 2023.11.02 |