Angular 使用HTTPS与 Golang 通信

一些声明:

  • 这是我的第一篇博文,以下主要以我的笔记为主,希望大家能看懂
  • 这事实上是个很简单的东西,只不过自己踏了各种错路弄了好几天,网上有没有一步到位的教程,这里就记录一下。

 

注意:

  1. 这里不是指浏览器使用SSL/TLS访问Angular,而是Angular使用SSL/TLS访问Golang
  2. 如果你的服务器没有域名,请寻找其他CA机构签发证书,Let‘s Encrypt无法签发IP证书

 

使用场景:

  • Angular将登录信息以HTTPS的方式POST到Golang后端服务器

 

我的环境:

  • CentOS 7.3
  • Angular和Golang运行在服务器上
  • 服务器和域名都交由Cloudflare CDN管理
  • Cloudflare 开启SSL:FULL

 

目录:

  1. 生成Let's Encrypt密钥
  2. 配置 Golang的TLS
  3. Angular进行http.post
  4. 后端运行结果图
  5. 一些坑的说明

 

正文:

  1. 生成Let's Encrypt密钥

  • 安装Certbot-auto

Certbot也可以,只不过certbot-auto会自动处理依赖,何乐而不为

//以下抄自官网
# wget https://dl.eff.org/certbot-auto
# sudo mv certbot-auto /usr/local/bin/certbot-auto
# sudo chown root /usr/local/bin/certbot-auto
# chmod 0755 /usr/local/bin/certbot-auto
# /usr/local/bin/certbot-auto --help
  • 生成密钥

这个教程很详细,我就不化简为繁了

简书教程:生成Let's Encrypt 通配符证书

此时你得到了需要的文件:

//作为server.crt使用的证书文件
/etc/letsencrypt/live/你的域名/fullchain.pem

//作为server.key使用的密钥文件
/etc/letsencrypt/live/你的域名/privkey.pem

 

  1. 配置 Golang的TLS

  • 下面以我的登陆代码为例
import {

    //HTTPS相关包
    "net/http"
    "crypto/tls"
    "encoding/json"

    //Postgresql数据库
    "database/sql"
	_ "github.com/lib/pq"

    //其他
    "fmt"
	"io/ioutil"
	"log"

}

var serverPem string = "/etc/letsencrypt/live/服务器域名/fullchain.pem"
var serverKey string = "/etc/letsencrypt/live/服务器域名/privkey.pem"
var tls_config *tls.Config //最重要的证书信息都存这了

type USER struct{  //用户信息
	Username string `json:"username"`
	Password string `json:"password"`
}

type FeedBack struct{  // 登录反馈
	Code int `json:"code"`
	Ok bool `json:"ok"`
}

const(  // 数据库配置
	host = "localhost"
	port = 5432
	user = "数据库用户名"
	sqlpassword = "数据库密码"
	dbname = "数据库名"
)

func main() {
    InitTLS()
    fmt.Print("开始服务.\n-----------------\n")
    //HTTPS.server 监听
	mux := http.NewServeMux()
	mux.HandleFunc("/api/login", loginHandle)
	mux.HandleFunc("/api/register", registerHandle) 
	//http.ListenAndServe(":2083", mux)
        //一个被抛弃的HTTP靓仔默默路过
    //http.ListenAndServeTLS(serverPem, serverKey)
        //这句HTTPS也不行,可能默认状态有些奇怪的参数在搞怪
        //当然你用Golang做客户端的话就没有这些问题了,主要是这里要用Angular
	srv := &http.Server{
        Addr:         ":服务器端口",
        Handler:      mux,
        TLSConfig:    tls_config,
        TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0),
    }
    log.Fatal(srv.ListenAndServeTLS(serverPem, serverKey))
        //一个HTTPS靓仔高调路过
}

func InitTLS() {
	fmt.Print("开启TLS认证.\n")
    //导入Server的cert和key
	cert, err := tls.LoadX509KeyPair(serverPem, serverKey)
	if err != nil {
		log.Println(err)
		return
    }
    //配置TLS
	tls_config = &tls.Config{
		Certificates: []tls.Certificate{cert}, //引入cert和key
        //ClientAuth:   tls.RequireAndVerifyClientCert, //要求验证客户端密钥
        //如果你用Angular进行连接,切勿启用该选项,详见下方论述
	}
}


/*  
    HTTPS相关内容上面已全部包含
    下面是一些其他无关紧要的东西
    可以忽略
*/

// 初始化数据库
func InitDB(){
	psqlInfo := fmt.Sprintf("host=%s port=%d user=%s "+
		"password=%s dbname=%s sslmode=disable",
		host, port, user, sqlpassword, dbname)
	db,err := sql.Open("postgres", psqlInfo)  // 打开数据库连接
	if err != nil{
		panic(err)
	}
	defer db.Close()
	err = db.Ping()
	if err != nil{
		panic(err)
	}
	fmt.Print("数据库连接成功.\n")
	sqlStatement := `CREATE TABLE IF NOT EXISTS "userinfo"(
		username VARCHAR(25) not null primary key,
		password VARCHAR(25)
	);`
	_, err = db.Exec(sqlStatement)
	if err != nil{
		panic(err)
	}
	fmt.Print("数据库初始化成功.\n")
}

// 验证数据库是否存在用户,在控制台输出结果,没有做进一步处理
func loginHandle(res http.ResponseWriter, req *http.Request){ // 登录处理函数
	fmt.Print("##开始登陆...")
	res.Header().Set("Access-Control-Allow-Origin", "*") // 设置跨域:允许访问所有域
	res.Header().Set("Access-Control-Allow-Headers", "Content-Type") //header的类型
	res.Header().Set("content-type", "application/x-www-form-urlencoded")
	//application/json会请求两次,第一次为空,引发一次报错后再登陆成功,这里就不用了
	body, err := ioutil.ReadAll(req.Body)
	if err != nil{
		fmt.Print(err)
		return
	}
	var userInfo USER
	err = json.Unmarshal(body, &userInfo)  // 解析json数据
	if err != nil{
		fmt.Print(err)
		return
	}
	var fb FeedBack
	ok, err := checkLogin(userInfo.Username, userInfo.Password)
	if err != nil {
		fmt.Print(err.Error())
		return
	}
	if ok {
		fb.Code = 200
		fb.Ok = true
		fbData,err := json.Marshal(fb)
		fmt.Print("用户" + userInfo.Username + "登陆成功...")
		//fmt.Print(string(fbData))
		if err != nil {
			fmt.Print("反馈数据解析失败...")
			return
		}
		fmt.Fprintln(res, string(fbData))
	} else {
		fb.Code = 200
		fb.Ok = false
		fbData,err := json.Marshal(fb)
		fmt.Print(string(fbData))
		if err != nil {
			fmt.Print("反馈数据解析失败...")
			return
		}
		fmt.Fprintln(res, string(fbData))
	}
	defer req.Body.Close()
	fmt.Print("\n")
}

//将用户信息存入Postgresql数据库
func registerHandle(res http.ResponseWriter, req *http.Request) {
    fmt.Print("##开始注册...")
	res.Header().Set("Access-Control-Allow-Origin", "*")
	res.Header().Set("Access-Control-Allow-Headers", "Content-Type")
	res.Header().Set("content-type", "application/x-www-form-urlencoded")
	body, err := ioutil.ReadAll(req.Body)
	defer req.Body.Close()
	if err!= nil{
		fmt.Print("获取请求参数失败...", err.Error())
		return
	}
	var userInfo USER
	err = json.Unmarshal(body, &userInfo)
	if err != nil {
		fmt.Print("解析数据失败...", err.Error())
		return
	}
	exist, err := checkDatabase(userInfo.Username)
	var fb FeedBack
	if exist {
		fmt.Print("用户" + userInfo.Username + "已存在...")
		fb.Code = 200
		fb.Ok = false
	} else {
		ok, err := checkRegister(userInfo.Username, userInfo.Password)
		if err != nil {
			fmt.Print("注册失败...", err.Error())
			fb.Code = 200
			fb.Ok = false
		}
		if ok {
			fb.Code = 200
			fb.Ok = true
		} else {
			fb.Code = 200
			fb.Ok = false
		}
	}
	fbData,err := json.Marshal(fb)
	if err != nil {
		fmt.Print("解析反馈信息错误...", err.Error())
		return
	}
	fmt.Fprintln(res, string(fbData))
	fmt.Print("\n")
}

func checkDatabase(username string)(bool, error){
	fmt.Print("检查数据库状态...")
	psqlInfo := fmt.Sprintf("host=%s port=%d user=%s "+
		"password=%s dbname=%s sslmode=disable",
		host, port, user, sqlpassword, dbname)
	db,err := sql.Open("postgres", psqlInfo)
	if err != nil{
		fmt.Print("连接数据库错误...")
		return false, errors.New("连接数据库出错...")
	}
	
	sqlStat := "SELECT from userInfo WHERE username = $1"
	
	stmt, err := db.Prepare(sqlStat)
	
	if err != nil {
		fmt.Print("准备语句失败...", err.Error())
		return false, errors.New("准备语句失败...")
	}
	
	rows ,err := stmt.Query(username)
	
	if rows.Next(){
		return true, nil
	}
	
	return false, nil
}

func checkLogin(username string, password string) (bool, error){
	fmt.Print("检查登录信息...")
	psqlInfo := fmt.Sprintf("host=%s port=%d user=%s "+
		"password=%s dbname=%s sslmode=disable",
		host, port, user, sqlpassword, dbname)
	db,err := sql.Open("postgres", psqlInfo)
	if err != nil{
		fmt.Print("连接数据库错误...")
		return false, errors.New("连接数据库出错...")
	}
	sqlStatement := "SELECT * FROM userinfo WHERE username = $1 AND password = $2;"
	stmt, err := db.Prepare(sqlStatement)
	if err != nil {
		print(err.Error())
		return false, errors.New("准备数据失败...")
	}
	rows, err := stmt.Query(username, password)
	defer rows.Close()
	var user USER
	for rows.Next(){
		rows.Scan(user.Username, user.Password)
		return true, nil
		fmt.Print("用户" + username + "登陆成功...")
	}
	fmt.Print("用户" + username + "登陆失败...")
	return false, nil  // 没有查询到数据
}

func checkRegister(username string , password string)(bool, error){
	fmt.Print("正在检查注册信息...")
	psqlInfo := fmt.Sprintf("host=%s port=%d user=%s "+
		"password=%s dbname=%s sslmode=disable",
		host, port, user, sqlpassword, dbname)
	db,err := sql.Open("postgres", psqlInfo)
	if err != nil{
		fmt.Print("连接数据库错误...")
		return false, errors.New("连接数据库出错...")
	}
	sqlStatement := "INSERT INTO userinfo(username, password) VALUES ($1, $2);"
	stmt, err := db.Prepare(sqlStatement)
	if err != nil{
		fmt.Print("准备语句失败...", err.Error())
		return false, errors.New("准备语句失败...")
	}
	_, err = stmt.Exec(username, password)
	defer stmt.Close()
	if err != nil{
		fmt.Print("插入数据失败...", err.Error())
		return false, errors.New("插入数据失败...")
	}
	fmt.Print("用户" + username + "注册成功...")
	return true, nil
}

 

  1. Angular进行http.post

  • 引入HttpClient、HttpHeaders
  • http.post()
//login.service.ts

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})

export class LoginService {
  constructor(private http: HttpClient) { //以http为名使用HttpClient
    console.log("登录服务初始化")
    }

  login(username, password){
    const headers = new HttpHeaders({'Content-Type': 'application/x-www-form-urlencoded'});
    let data = { username: username, password: password};
    return this.http.post("https://服务器域名:端口/api/login",JSON.stringify(data), {headers: headers});
  }

  register(username, password) {
    const headers = new HttpHeaders({'Content-Type': 'application/x-www-form-urlencoded'});
    let data = {username: username,password: password};
    return this.http.post("https://服务器域名:端口/api/register", JSON.stringify(data), {headers: headers});
  }
}
  • 组件里调用登陆服务
//一个帅气的组件.component.ts

...

import { LoginService } from '../../services/login.service';

...

export class NavMenuComponent implements OnInit {
    constructor(public loginSvc:LoginService) { } //引入登录服务
    /* 登陆控制 */
    public input_username:string;
    public input_password:string;
    login() {
        console.log("登录", this.input_username, this.input_password);
        this.loginSvc.login(this.input_username, this.input_password).subscribe(
            fb=>{ console.log(fb); }, // 反馈信息
            err =>{ console.log(err); } // 错误信息
        )
    }
    register(){
        this.loginSvc.register(this.input_username, this.input_password).subscribe(
            fb=>{ console.log(fb); },
            err =>{ console.log(err) }
        )
    }
}
  1. 后端运行结果图

//实验前已注册过root passwd

  1. 注册 root passwd
  2. 登陆 root wrong
  3. 登陆 root passwd
  4. 注册 admin admin
  5. 登陆 admin admin

 

  1. 一些坑的说明

  • 不要使用自签发证书

​​​​​​​我用了自签发证书后,客户端一定会Certificates Unknown,即使将CA导入到系统证书库也一样,这坑了我最久

Wireshark抓包情况:
    1.Client Hello
    2.Server Hello
    3.Client Alert: Certificates Unknown

解决方案:
    1.对于没有“客户端证书认证”的Angular,勿必关闭服务器的“客户端证书认证”(如Golang的TLS_config)
    2.请使用信任机构签发的密钥和证书(如Let's encrypt证书)

  • 其他的坑

其他一些偏得和本文都没有关系了的坑就不写了

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值