HTTP는 인터넷에서 웹 서버와 사용자 브라우저간 문서를 전송하기 위한 통신 규약이다. HTTP는 정보를 텍스트로 주고받기 때문에 누군가가 네트워크 이를 가로챈다면 정보를 확인할 수 있어서 보안에 취약하다 HTTPS는 이를 암호화하여 보안상의 문제를 해결해주는 프로토콜이다
HTTPS는 새로운 프로토콜이 아닌 HTTP와 통신을 하는 소켓 부분을 SSL이나 TSL로 대체하는 것 즉 사용자는 SSL과 통신하여 암호화된 정보를 주고받는다.
HTTPS는 공개키 암호화 방식을 사용한다. 공개키 암호화 방식에는 비밀키와 공개키가 존재하는데 비밀키는 서버 쪽에 가지고 있으며 공개되어서는 안 되고, 공개키는 누구에게나 공개되어도 괜찮은 키이다. 먼저 클라이언트는 공개키를 통해 텍스트를 암호화하고 이는 비밀키로만 복호화할 수 있다 따라서 다른 사람이 정보를 가로채더라도 비밀키만 안전하다면 괜찮은 방식이다.
2. SpringBoot에 HTTPS 적용하기
#1) 사전준비
스프링 부트 2.2.5 RELEASE, Dependency로 spring Web사용
Maven 사용 (Gradle사용 가능)
JAVA 1.8, 내장 톰캣 9 사용
#2) JAVA를 이용한 인증서 만들기
Intellij 터미널에서 다음과 같이 keystore.p12라는 키를 만든다 입력해야 할 이름, 도시 등 정보는 임의로 입력한다. 인증받은 키가 아니고 로컬에서 확인하는 용도 이므로 자유롭게 입력한다. 만약 인증된 키를 사용할 경우 값을 지불하고 이용해야 한다.
/WEB-INF/spring/appServlet 경로에 security-context.xml을 작성한다. 먼저 name은 text, password는 test1234로 ROLE_USER를 만들고 /secured의 경로로 접근할경우 ROLE_USER인 사람만 접근할 수 있게 설정한다
다음과 같은 spring MVC 아키텍처에서 우리는 controller와 view부분만 만들면 되며 나머지 부분은 스프링에서 제공한다. 사용자의 request가 들어올 경우 dispatcher servlet가 요청을 가로채고 handler Mapping에 전달한 후 컨트롤러로 요청이 전달하고 우리가 만든 컨트롤러에 의해 view 네임을 선택하면 view Resolver에 의해 view네임을 가진 jsp를 선택하게 된다.
2. 스프링 MVC 실습하기
사전준비
다음과 같이 pom.xml에 필요한 라이브러리를 추가한다. 게터, 세터를 용이하게 만들어주는 lombok mysql과 연동하기 위한 dbcp, mysql-connector, spring-jdbc를 추가한다. 다음과 같은 deoendency를 추가한다
다음과 같이 Spring Legacy Project의 Spring MVC Project를 클릭하고 helloSpringMVC라는 프로젝트를 생성한다
#2) home.jsp 수정
다음과 같이 기본으로 있는 home.jsp를 수정한다. 클릭할 시 createoffer로, offers로 요청을 보내는 a태그가 2개 있다. 이때 ${pageContext.request.contextPath}는 서버에서 설정된 contextPath를 의미한다.
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html><head><title>Home</title></head><body><p><ahref="${pageContext.request.contextPath}/offers">show current offers</a><p><ahref="${pageContext.request.contextPath}/createoffer">Add a new offer</a></body></html>
jsp코드는 이후에 수정될 예정이니 controlle가 jsp로 제대로 요청을 전달하는지 확인만 할 목적으로 기본 jsp2개를 생성한다
#5) 실행하기
다음과 같이 제대로 매핑이 된 것을 확인할 수 있다.
#6) dao, model, service 생성하기
다음 그림과 같은 구조를 생성하기 위해 dao, model, service를 생성한다. 사용자의 요청이 들어오면 Dispatcher Servlet에서 이를 컨트롤러에 전달해주고 컨트롤러는 이에 맞는 서비스에게 전달하고 서비스는 DAO에게 DAO는 DB에 접근하고 모델을 통해 View로 사용자에게 response 하는 구조이다
OfferDAO
처음 프로젝트 생성할 때 패키 지명을 kr.ac.hansung으로 지정했기 때문에 DAO의 패키지명을 kr.ac.hansung.dao로 패키지를 만들고 내부에 다음과 같은 OfferDAO라는 클래스를 생성한다. @Repository 에노테이션을 통해 DAO클래스임을 명시하고 @Autowired를 통해 DB에 접근할 jdbctemplet을 의존성 주입시킨다.
package kr.ac.hansung.dao;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import kr.ac.hansung.model.Offer;
@RepositorypublicclassOfferDAO{
private JdbcTemplate jdbcTemplateObject;
@AutowiredpublicvoidsetDataSource(DataSource dataSource){
this.jdbcTemplateObject = new JdbcTemplate(dataSource);
}
publicintgetRowCount(){
String sqlStatement = "select count(*) from offers";
return jdbcTemplateObject.queryForObject(sqlStatement, Integer.class);
}
public Offer getOffer(String name){
String sqlStatement = "select * from offers where name=?";
return jdbcTemplateObject.queryForObject(sqlStatement, new Object[]{name}, new RowMapper<Offer>() {
@Overridepublic Offer mapRow(ResultSet rs, int rowNum)throws SQLException {
Offer offer = new Offer();
offer.setId(rs.getInt("id"));
offer.setName(rs.getString("name"));
offer.setEmail(rs.getString("email"));
offer.setText(rs.getString("text"));
return offer;
}
});
}
public List<Offer> getOffers(){
String sqlStatement = "select * from offers";
return jdbcTemplateObject.query(sqlStatement, new RowMapper<Offer>() {
@Overridepublic Offer mapRow(ResultSet rs, int rowNum)throws SQLException {
Offer offer = new Offer();
offer.setId(rs.getInt("id"));
offer.setName(rs.getString("name"));
offer.setEmail(rs.getString("email"));
offer.setText(rs.getString("text"));
return offer;
}
});
}
publicbooleaninsert(Offer offer){
String sqlStatement = "insert into offers (name, email, text) values (?,?,?)";
String name = offer.getName();
String email = offer.getEmail();
String text = offer.getText();
return (jdbcTemplateObject.update(sqlStatement, new Object[]{name, email, text}) == 1);
}
publicbooleanupdate(Offer offer){
String sqlStatement = "update offers set name=?, email=?, text=? where id=?";
String name = offer.getName();
String email = offer.getEmail();
String text = offer.getText();
int id = offer.getId();
return (jdbcTemplateObject.update(sqlStatement, new Object[]{name, email, text, id}) == 1);
}
}
Offer
kr.ac.hansung.model 패키지를 생성하고 다음과 같은 Offer클래스를 생성한다. lombok의 게터 세터 에노테이션을 통하여 게터 세터를 만든다
기존의 HomeController가 작동할 수 있었던 이유는 web.xml을 보면 알 수 있다. 먼저 에노테이션이 활성화되어있고 ViewResolver를 사용하여 jsp의 이름을 받을 경우 name.jsp로 변경하여 연결해주는 것을 확인할 수 있다. 또한 base-package가 정해져 있어 패키지의 에노테이션을 확인하는것을 알 수 있다. 이처럼 service와 dao의 bean을 생성하고 autowired해주기위해 service-context.xml 와 dao-context.xml을 생성한다.
먼저 src/main/webapp/WEB-INF/spring/appServlet에 다음과 같은 service-context.xml을 생성한다. kr.ac.hansung.service패키지를 스캔하도록 되어있으며 에노테이션을 사용한다고 적혀있다.
다음으로는 service-context.xml과 같은 위치인 src/main/webapp/WEB-INF/spring/appServlet에 다음과 같은 dao-context.xml을 생성한다. dao-context에는 dbcp를 활용하여 DB에 접근하게 할 dataSource Bean이 정의되어있다. 또한 placeholder로 dbcp에서 참고할 properties파일의 위치가 정의되어있다.
src/main/webapp/WEB-INF에 props라는 폴더를 생성하고 다음과 같은 jdbc.properties 파일을 생성한다. 이는 dao-context.xml에서 dbcp를 이용하여 db에 접근할 때 사용하는 파일으로서 다음과 같다.
jdbc.username = 아이디를 입력
jdbc.password = 비밀번호를 입력
jdbc.driverClassName = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/csemall
#9) web.xml 수정하기
이렇게 dao와 service를 생성하였어도 실행할 경우 에러가 발생한다. 이는 스프링에서 dao-context.xml와 service-context.xml을 바라보고 있지 않기 때문이다. 따라서 다음과 같이 web.xml을 수정한다. 기존에는 root-context.xml만 바라보고 있었지만 생성한 service-context.xml과 dao-context.xml을 추가해줌으로써 스프링이 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 http://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/appServlet/service-context.xml
/WEB-INF/spring/appServlet/dao-context.xml
</param-value>
</context-param>
<!-- Creates the Spring Container shared by all Servlets and Filters -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Processesapplicationrequests -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
#10) offers.jsp수정
DB에 존재하는 정보를 받아오는 jsp를 구성하기 위해 다음과 같이 offers.jsp를 수정한다.
<%@ page language="java" contentType="text/html; charset=US-ASCII"
pageEncoding="US-ASCII"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE htmlPUBLIC"-//W3C//DTD HTML 4.01 Transitional//EN""http://www.w3.org/TR/html4/loose.dtd"><html><head><metahttp-equiv="Content-Type"content="text/html; charset=US-ASCII"><title>Insert title here</title></head><body><c:forEachvar="offer"items="${offers}"><p><c:outvalue="${offer}"></c:out></p></c:forEach></body></html>
1. 사용자는 /으로 접속하게 되면 HomeController에 의해 home.jsp로 접속하게 된다.
2. 사용자가 offers로 요청을 보내게 되면 디스패쳐 서블릿에 의해 OffersController로 요청을 전달하고, /offers로 매핑된 showOffers함수를 호출한다. showOffers는 autowired 된 offerService의 getCurrent() 함수를 호출한다.
3. offerService의 getCurrent함수는 autowried 된 offerDAO의 getOffers를 호출한다
4. offerDAO의 getOffers는 DB에 저장된 객체를 리스트로 저장하여 서비스로 반환하고 서비스는 다시 컨트롤러로 반환한다.
5. 컨트롤러에서는 모델을 이용하여 view에 있는 jsp로 결과값과 함께 반환하고, 사용자는 마지막으로 반환된 offers.jsp를 확인하게 된다.
지난 #8 강의에 이어 MYSQL과 연동하여 Update, delete, insert 하는 방법을 설명하도록 한다.
1. INSERT
먼저 지난번 query와 달리 update메소드를 사용하며 다음과 같이 사용한다 update메서드 내부에는 insert sql문, new Object로 name, email, text를? 에 넣어주는 방식으로 사용하며 이때 반환하는 값이 1이면 true를 리턴한다.
publicbooleaninsert(Offer offer){
String sqlStatement = "insert into offers (name, email, text) values (?,?,?)";
String name = offer.getName();
String email = offer.getEmail();
String text = offer.getText();
return (jdbcTemplateObject.update(sqlStatement, new Object[]{name, email, text}) == 1);
}
2. UPDATE
insert와 같이 update메소드를 사용하며 사용방법은 다음과 같다.
publicbooleanupdate(Offer offer){
String sqlStatement = "update offers set name=?, email=?, text=? where id=?";
String name = offer.getName();
String email = offer.getEmail();
String text = offer.getText();
int id = offer.getId();
return (jdbcTemplateObject.update(sqlStatement, new Object[]{name, email, text, id}) == 1);
}
3. DELETE
마찬가지로 update 메소드를 사용하며 사용방법은 다음과 같다.
publicbooleandelete(int id){
String sqlStatement = "delete from offers where id = ?";
return (jdbcTemplateObject.update(sqlStatement, new Object[]{id}) == 1);
}
DB에 접근하기 위해 다음 그림과 같이 DAO는 JDBC Template에 이는 JDBC Driver를 통해 Database에 접근하고 DataSource를 이용하여 DB에 실질적으로 커넥션 한다.
2. Data Source
데이터 베이스에 접근하기 위해 DataSource를 사용한다 DataSource의 종류는 다양하지만 DBCP라이브러리의 BasicDataSource를 통상 많이 사용하므로 실습에서도 이를 사용할 예정이다.
3. DAO란?
Data Access Object의 약자로서 서비스는 DB에 대해 아무것도 모르고, 실질적으로 DB에 접근하는 오브젝트를 의미한다. 쉽게 생각하여 DB의 CRUD를 실질적으로 수행하는 코드가 담긴 부분이다.
4. JDBC란?
JDBC란 DB와의 커넥션, SQL 사용, Close 커넥션 등 잡다한 일들을 해주는 프레임 워크이다. 기본 JAVA JDBC는 커넥션 등 try catch의 반복으로 반복적인 코드를 짜야하지만 Spring JDBC를 사용하면 SQL만 코드를 작성하고 나머지는 Spring 프레임워크에서 담당한다.
5. 실습하기
#1) 사전 준비
Mysql 5.7.28 설치
Mysql내부에는 csemall라는 DB, offers라는 테이블을 만들고 다음과 같은 구조로 이루어져 있다.
id(INT)
name
email
text
1
Alice
Alice@google.com
alice
2
Bob
Bob@google.com
bob
3
Jun
Jun@google.com
jun
#2) Simple Spring Maven 프로젝트 실행
다음과 같은 helloDB프로젝트를 생성한다.
#3) pom.xml 수정
다음과 같은 dependency를 추가하고 저장을 누르면 maven에 라이브러리가 추가된 것을 확인할 수 있다.
props라는 패키지를 생성하고 그 밑에 jdbc.properties라는 파일을 생성한다. 파일 내부는 다음과 같다 이는 DB의 정보를 담고 있으며, 생성할 bean에 하드 코딩하는 것보다 좋은 방법이다.
jdbc.username = root
jdbc.password = 사용자 비밀번호를 입력
jdbc.driverClassName = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/csemall
#5) beans.xml 생성
다음과 같이 beans.xml을 생성한다 context:annotation은 DAO클래스에서 사용할 @Autowired를 활성화한다는 의미이며 context:component는 @Component 에노 테이션이 있는 어떤 패키지를 scan 할 건지 명시하는 부분이다. bean으로 생성한 dataSource는 #4에서 생성한 jdbc.properties를 사용하여 실제 DB와 커넥션을 하는 부분이다 dbcp2의 BasicDataSource클래스를 사용하고 있고 이 클래스는 DataSource인터페이스를 따르는 클래스이다. 마지막으로 context:property-placeholder는 앞서 사용할 jdbc.properties가 어떤 위치에 있는지 명시하는 부분이다.
src/main/java아래의 csemall이라는 패키지를 만들고 다음과 같은 Class를 생성한다. @Component 에노테이션은 이 클래스는 id가 offerDAO라는 bean을 하나 생성하겠다는 의미이다. 또한 JdbcTemplate를 사용하여 bean에 생성한 dataSource를 주입(@Autowired)시켜 DB와 실질적인 커넥션을 해주는 부분이 담겨있다. getRowCount() 부분은 다음과 같은 sql을 query 하고 결과는 Integer로 받는다는 의미이다.
package csemall;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
@Component("offerDAO")publicclassOfferDAO{
private JdbcTemplate jdbcTemplateObject; //얘는 new를 사용해됨 이후 DataSource는 DI를 통해 주입 시킬예정 @AutowiredpublicvoidsetDataSource(DataSource dataSource){ //DataSource는 인터페이스인데 우리가 앞서 bean에 선언한 datasource가 이 인터페이스를 따르고 DI한다this.jdbcTemplateObject = new JdbcTemplate(dataSource);
}
publicintgetRowCount(){
String sqlStatement = "select count(*) from offers";
return jdbcTemplateObject.queryForObject(sqlStatement, Integer.class);
}
}
#6) Offer.java 클래스 생성
Lombok을 이용해 @Getter, @Setter를 생성하였고 DB에 저장된 id, name, email, text를 받는다. 이는 추후에 사용될 클래스이다.
만약 다음과 같이 Bean으로 만들 클래스 내부에 로깅, 트랜잭션, 실제 코드인 비즈니스 로직이 함께 있다고 생각해보자. 로깅, 트랜잭션, 비즈니스 로직 순서대로 작동하겠지만 이는 좋은 코드라고 할 수 없다. 따라서 로깅, 트랜잭션과 비즈니스 로직을 분리하기 위해 AOP가 존재한다
Bank 클래스
AOP는 다음 그림처럼 선처리, 후처리(런타임시 짜집기함)를 가능하게 지원하기 때문에 메인 Bean을 만드는 클래스를 따로 두고 로깅, 트랜잭션을 선처리 후처리로 다음과 같은 구조를 만드는 것이다.
다음 그림처럼 a.f()가 실직적인 타겟이되는 joinpoint가 되고 그림 앞에 보라색, 주황색이 선처리 후처리를 담당하는 어드바이스가 된다. 어드바이스는 joinpoint는 Pointcut이 가리키게 된다
다음 그림을 보면 조금더 이해하기 쉽다
AOP 구조 설명
2. AOP실습
#1) spring-aspect를 이용한 실습
1. pom.xml dependency추가
다음과 같은 디펜던시를 추가하면, Maven Dependencies에 spring-aspects라이브러리가 추가된 것을 확인할 수 있다.
publicclassLogger{
publicvoidaboutToSound(){
System.out.println("Before: about to sound");
}
}
3. animal.xml Namespaces 수정 및 aop추가
다음과 같이 Namespaces에 aop를 추가한다
aop 추가
aop추가를 완료하였다면 다음과 같은 코드를 추가한다 <aop:aspect ref="Logger">를 통해 Logger클래스를 Aspect로 이용하겠다는 의미이며, <aop:pointcut expression="execution(void kr.ac.hansung.helloDI.*.sound())" id="selectSound" />를 통해 helloDI패키지의 sound() 함수를 joinpoint로 지정하겠다는 의미이다. 이때 *은 와일드카드로서 어떤 클래스든 상관없다는 의미를 가진다. <aop:before method="aboutToSound" pointcut-ref="selectSound" />는 before어드바이스를 추가하겠다는 의미로 aboutToSound라는 메서드를 이용하며 ref로는 위에 선언한 pointcut의 id를 가진다. 따라서 pointcut은 jointpoint를 가르키고 advice는 그 pointcut을 가리킨다
앞 강의에서 DI란 무엇인지와 생성자를 이용하여 의존성 주입을 하는 방법을 실습해 보았다면 이번 강의에서는 Annotation을 이용하여 의존성 주입을 해볼 예정이다.
먼저 에노테이션이란 기존 XML을 이용하여 의존성 주입을 하는 것이 아닌 @Required 같은 태그를 이용하여 의존성 주입을 하는 것을 의미한다. 의존성 주입 에노테이션으로는 다음과 같은 3가지가 있다
#1 @Required
XML의 Bean의 property에 해당하는 부분 중 필수적인 부분을 명시할 때 다음과 같이 사용한다. 쉽게 말해 XML에 bean중 property가 꼭 있어야 하니 없으면 에러가 발생한다고 생각하면 좋다 즉 @required를 사용할때 xml에 property에 name에 해당하는 부분이 반드시 있어야 에러가 발생하지 않는다.
이전 강의에서 의존성 주입을 할 때 bean에 property를 사용하여 세터를 통한 주입을 했었다. 하지만 Class의 기존 세터 부분을 지우고 @Autowired 에노테이션을 추가하면 자동으로 의존성 주입이 이루어진다. 이는 다음 코드처럼 Boy클래스는 property를 사용하여 세터를 통해 bean을 생성했고 college라는 bean은 propery를 통해 boy라는 bean을 의존성 주입 했지만 College클래스에 @Autowired 에노테이션을 추가하고 세터부분을 지우고, 마찬가지로 bean의 property부분을 지우면 자동으로 의존성 주입이 이루어지는것을 확인할 수 있다.
만약 Autowired를 할때 다음과 같이 Boy 클래스의 bean이 두 개 있으면 어떤 걸 자동으로 Autowired를 할지 몰라 오류가 생긴다 따라서 이럴 때 @Qualifier를 사용한다. 다음과 같이 bean에는 qualifer의 value를 지정하고 Class에는 @Qualifier의 에노테이션과 함께 value를 설정하면 어떤 bean을 주입할지 정하게 된다
다음과 같이 코드를 수정하고 Run 하게 되면 다음과 같은 에러가 발생하는 것을 확인할 수 있습니다. 앞서 공부한 것처럼 2개의 bean 중 어떤 bean을 주입할지 모르기 때문에 다음과 같은 error가 발생하는 것입니다. 따라서 2가지 방법을 통해 해결해보도록 하겠습니다.
error
#3) @Qualifier추가 및 animal.xml수정
다음과 같이 Qualifier에노테이션을 사용하여 다음과 같이 어떤 빈을 주입할지 선택합니다.