1. 程式人生 > >秒懂HTTPS介面(實現篇)

秒懂HTTPS介面(實現篇)

文章目錄

HTTPS介面實現

在上文秒懂HTTPS介面(原理篇)中我們詳細介紹了HTTPS協議原理,下面我們來實踐使用Java實現一個簡單HTTPS介面示例

專案結構:

springbootdemo
├─config 配置資訊類
├─controller 控制器類
├─entity 實體類
├─enums 列舉類
├─exception 異常類
├─handler 捕獲類
├─repository 資料訪問類 
├─util 工具類
├─SpringbootdemoApplication 專案啟動類
├──resources 資原始檔目錄
│  ├─application.yml 全域性配置檔案
│  ├─banner.txt 專案啟動banner
│  ├─tomcat.keystore SSL證書
│  ├─logback.xml 日誌配置檔案

主要特點:

  • Restful風格
  • 統一異常處理
  • SQL預處理

技術選型:

  • 核心框架:Spring Boot 2.1
  • 持久層框架:JPA 2.0
  • 日誌管理:Logback
  • 資料庫:MySQL 5.7
  • 外掛:lombok

開發環境:

  • SUN JDK1.8
  • Maven 3.5.4

新建Spring Boot專案

這裡使用的IDE是IntelliJ IDEA 2018
在這裡插入圖片描述
引包,配置pom.xml

<dependencies>
		<dependency>
			<
groupId
>
org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-rest</artifactId> </dependency> <!--引入MySQL驅動包--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>

編寫Entity

編寫Person實體bean,用於ORM物件關係對映,對映資料庫表結構

/**
 * Person實體類
 */

@Entity
@Data
@Table(name = "person")
public class Person {
	//主鍵自增長
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Integer id;
	private String name;
	private Integer age;
}

建立一個介面PersonRepository,後續的控制器直接呼叫該介面繼承自JpaRepository的方法,來實現和資料庫互動

/**
 * 繼承JpaRepository,實現與資料庫互動(JPA支援自動生成一些基本CURD SQL語句)
 */
public interface PersonRepository extends JpaRepository<Person,Integer> {

}

統一異常處理

自定義異常

/**
 * 自定義異常類
 */

//RuntimeException繼承Exception,spring只對繼承RuntimeException的異常進行回滾
public class PersonException extends RuntimeException {
	private Integer code;

	public PersonException(ResultEnum resultEnum) {
		super(resultEnum.getMsg());
		this.code = code;
	}

	public Integer getCode() {
		return code;
	}

	public void setCode(Integer code) {
		this.code = code;
	}
}

捕獲異常類

@ControllerAdvice
@Slf4j
public class ExceptionHandle {

    /**
     * 捕獲異常類
     * @param e
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public Result handle(Exception e){
        if (e instanceof PersonException){
            PersonException personException = (PersonException) e;
            return ResultUtil.error(personException.getCode(),personException.getMessage());
        }
        log.error("【系統錯誤】",e);
        return ResultUtil.error(-1,"未知錯誤");
    }
}

封裝異常訊息列舉

/**
 * 封裝異常訊息列舉類
 */

public enum ResultEnum {
	UNKONW_ERROR(-1, "未知錯誤"),
	SUCCESS(0, "成功");

	private Integer code;
	private String msg;

	ResultEnum(Integer code, String msg) {
		this.code = code;
		this.msg = msg;
	}

	public Integer getCode() {
		return code;
	}

	public String getMsg() {
		return msg;
	}
}

封裝異常物件實體

/**
 * 封裝異常物件(Http請求返回的最外層物件)
 * @param <T>
 */

@Data
public class Result<T> {
    //錯誤碼
    private Integer code;
    //提示資訊
    private String msg;
    private T date;
}

異常工具類

/**
 * 異常工具類
 */
public class ResultUtil {
	public static Result sucess(Object obj) {
		Result result = new Result();
		result.setCode(0);
		result.setMsg("sucess");
		result.setDate(obj);
		return result;
	}

	public static Result sucess() {
		Result result = new Result();
		result.setCode(0);
		result.setMsg("sucess");
		return result;
	}

	public static Result error(Integer code, String message) {
		Result result = new Result();
		result.setCode(code);
		result.setMsg(message);
		return result;
	}
}

建立RESTful API

風格設計

請求型別 請求路徑 功能
Get /person 獲取人員列表
Post /person 建立一個人員

建立Controller

/**
 * 控制器,處理Http/https請求(RESTful API)
 */

@RestController
public class PersonController {
	@Autowired
	PersonRepository personRepository;

	/**
	 * 查詢所有人員列表(Get方式)
	 * @return
	 */

	@GetMapping(value = "/person")
	private List<Person> personlist() {
		return personRepository.findAll();
	}

	/**
	 * 新增人員 (Post方式)
	 * @param person
	 * @return
	 */
	@PostMapping(value = "/person")
	public Result personAdd(HttpServletRequest request,@RequestBody Person person) {
		return ResultUtil.sucess(personRepository.save(person));
	}
}

使用SSL-HTTPS

Spring Boot中使用HTTPS步驟:

  1. 要有一個SSL證書,證書怎麼獲取呢?買(通過證書授權機構購買)或者自己生成(通過keytool生成)
  2. 啟用HTTPS
  3. 將HTTP重定向到HTTPS(可選)

獲取SSL證書

有兩種方式可以獲取到SSL證書:

  • 自己通過keytool生成;
  • 通過證書授權機構購買;

這裡作為演示,採用keytool生成,實際專案中大部分採用的都是購買的方式。
那麼怎麼使用keytool生成呢?
Keytool是Java提供的證書生成工具,如果配置了JAVA_HOME的,直接就可以在控制檯進行生成了,這裡演示使用的是Mac的終端視窗

192:~ apple$ keytool -genkey -alias tomcat -keyalg RSA -keystore tomcat.keystore
輸入金鑰庫口令:  
再次輸入新口令: 
您的名字與姓氏是什麼?
  [Unknown]:  zuozewei
您的組織單位名稱是什麼?
  [Unknown]:  7DGroup
您的組織名稱是什麼?
  [Unknown]:  7D
您所在的城市或區域名稱是什麼?
  [Unknown]:  Beijing
您所在的省/市/自治區名稱是什麼?
  [Unknown]:  Beijing
該單位的雙字母國家/地區程式碼是什麼?
  [Unknown]:  CN
CN=zuozewei, OU=7DGroup, O=7D, L=Beijing, ST=Beijing, C=CN是否正確?
  []:  y

輸入 <tomcat> 的金鑰口令
	(如果和金鑰庫口令相同, 按回車):  
再次輸入新口令: 

檢視生成的SSL證書資訊

apple$ keytool -list -keystore tomcat.keystore 
輸入金鑰庫口令:  
金鑰庫型別: jks
金鑰庫提供方: SUN

您的金鑰庫包含 1 個條目

tomcat, 2018-11-29, PrivateKeyEntry, 
證書指紋 (SHA1): 2B:C5:FB:77:2C:5E:DC:5B:C5:E9:9F:06:27:7F:2E:A4:E4:9E:DF:8C

這裡解釋下命令的各個引數的含義:
-genkey :生成key;
-alias :key的別名;
-dname:指定證書擁有者資訊
-storetype :金鑰庫的型別為JCEKS。常用的有JKS(預設),JCEKS(推薦),PKCS12,BKS,UBER。每個金鑰庫只可以是其中一種型別。
-keyalg :DSA或RSA演算法(當使用-genkeypair引數),DES或DESede或AES演算法(當使用-genseckey引數);
-keysize :金鑰的長度為512至1024之間(64的倍數)
-keystore :證書庫的名稱
-validity : 指定建立的證書有效期多少天

dname的值詳解:
CN(Common Name名字與姓氏)
OU(Organization Unit組織單位名稱)
O(Organization組織名稱)
L(Locality城市或區域名稱)
ST(State州或省份名稱)
C(Country國家名稱)

這時候在當前目錄下就會看到一個檔案tomcat.keystore,到這裡SSL證書就有了。

啟用HTTPS

預設情況下Spring Boot內嵌的Tomcat伺服器會在8080埠啟動HTTP服務,Spring Boot允許在全域性配置檔案中配置HTTP或HTTPS,但是不可同時配置,如果兩個都啟動,至少有一個要以程式設計的方式配置,Spring Boot官方文件建議在application配置檔案中配置HTTPS,因為HTTPS比HTTP更復雜一些

application.yml中配置HTTPS,配置資訊如下:

server:
  port: 443
  servlet:
    context-path: /springboot
  ssl:
    key-store: classpath:tomcat.keystore
    key-store-type: jks
    key-alias: tomcat
    key-store-password: zuozewei
    key-store-provider: SUN
    key-password: zuozewei
    enabled: true

注意:請將在上一步生成的證書放到src/main/resources目錄下。

將HTTP請求重定向到HTTPS

由於不能同時在application.l中同時配置兩個connector,所以要以程式設計的方式配置HTTP Connector,然後重定向到HTTPS Connector

編寫TomcatHttp配置類

@Configuration
public class TomcatHttpConfig {

	/**
	 * 配置內建的Servlet容器工廠為Tomcat
	 * @return
	 */
	@Bean
	public ServletWebServerFactory servletContainer() {
		TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
			@Override
			protected void postProcessContext(Context context) {
				SecurityConstraint securityConstraint = new SecurityConstraint();
				securityConstraint.setUserConstraint("CONFIDENTIAL");
				SecurityCollection collection = new SecurityCollection();
				collection.addPattern("/*");
				securityConstraint.addCollection(collection);
				context.addConstraint(securityConstraint);
			}
		};
		//新增配置資訊,主要是Http的配置資訊
		tomcat.addAdditionalTomcatConnectors(redirectConnector());
		return tomcat;
	}

	/**
	 * 配置一個Http連線資訊
	 * @return
	 */
	private Connector redirectConnector() {
		Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
		connector.setScheme("http");
		connector.setPort(8088);
		connector.setSecure(false);
		connector.setRedirectPort(443);
		return connector;
	}
}

自定義啟動標誌

只需要在src/main/resources路徑下新建一個banner.txt檔案,banner.txt中填寫好需要列印的字串內容即可。
一般情況下,我們會藉助第三方工具幫忙轉化內容,如:
網站:http://www.network-science.de/ascii/ 將文字轉化成字串,
網站:http://www.degraeve.com/img2txt.php 可以將圖片轉化成字串。

配置日誌配置檔案

只需要在src/main/resources路徑下新建一個logback.xml配置檔案

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <property name="FILE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{80} - %msg%n"/>
    <property name="LOG_PATH" value="${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}"/>
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/${LOG_FILE}</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/${LOG_FILE}.%d{yyyy-MM-dd}</fileNamePattern>
        </rollingPolicy>
        <encoder charset="UTF-8">
            <pattern>${FILE_LOG_PATTERN}</pattern>
        </encoder>
    </appender>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${FILE_LOG_PATTERN}</pattern>
        </encoder>
    </appender>
    <appender name="CRAWLER_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/event.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/event.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>%msg%n</pattern>
        </encoder>
    </appender>
    <logger name="com.business.intelligence.util.CrawlerLogger" level="INFO" additivity="false">
        <appender-ref ref="CRAWLER_LOG"/>
    </logger>
    <root level="INFO">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="FILE"/>
    </root>
</configuration>

配置資料庫配置

手動先建立db_person資料庫

spring:
  profiles:
    active: a
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    #手動建立db_person資料庫
    url: jdbc:mysql://39.105.21.2:3306/db_person?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: zuozewei
    password: zuozewei
jpa:
  hibernate:
    ddl-auto: update
  show-sql: true

啟動並測試

啟動專案
在這裡插入圖片描述
通過瀏覽器輸入:http://127.0.0.1:8088/springboot/person
在這裡插入圖片描述
我們可以看到瀏覽器自動重定向到 https://127.0.0.1/springboot/person

點選瀏覽器上方的證書,我們可以看到使用的SSL證書資訊
在這裡插入圖片描述

完整的專案結構

在這裡插入圖片描述

秒懂HTTPS介面系列原始碼:
https://github.com/zuozewei/Springboot-https-demo

相關係列:
秒懂HTTPS介面(原理篇)
秒懂HTTPS介面(介面測試篇)
秒懂HTTPS介面(JMeter壓測篇)