认证鉴权
认证鉴权
HTTP协议是无状态的和Connection: keep-alive的区别: 无状态是指协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。从另一方面讲,打开一个服务器上的网页和你之前打开这个服务器上的网页之间没有任何联系。 HTTP是一个无状态的面向连接的协议,无状态不代表HTTP不能保持TCP连接,更不能代表HTTP使用的是UDP协议(无连接)。 从HTTP/1.1起,默认都开启了Keep-Alive,保持连接特性,简单地说,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接。 Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。
http协议本身是无状态的,所以服务端不能知道用户的状态,需要借助些技术手段来实现。
basic认证
basic认证是基于http协议。
- 访问受保护的资源,没有登录
- 服务端返回401,并响应头设置WWW-Authenticate:Basic realm=”localhost
- 浏览器弹出用户名密码框,输入后再次访问资源通过,后续的访问都会请求头带上用户名和密码,格式为Authorization:Basic base64(name:password)
http.HandleFunc("/user", func(writer http.ResponseWriter, request *http.Request) {
authHeader := request.Header.Get("Authorization")
fmt.Println(authHeader) //Basic emhhbmdzYW46MTIz
if authHeader == "" {
//请求没有头信息,返回401
writer.Header().Add("WWW-Authenticate", "Basic realm=”localhost")
writer.WriteHeader(401)
} else {
//请求有头信息,base64解码,并校验用户名密码
authHeader = authHeader[6:]
bytes, _ := base64.StdEncoding.DecodeString(authHeader)
fmt.Println(string(bytes)) //zhangsan:123
nameAndPwd := strings.Split(string(bytes), ":")
if nameAndPwd[0] == "zhangsan" && nameAndPwd[1] == "123" {
writer.Write([]byte("login success"))
writer.WriteHeader(200)
} else {
writer.Header().Add("WWW-Authenticate", "Basic realm=”localhost")
writer.WriteHeader(401)
}
}
})
这种basic认证是不安全的,因为都知道是base64编码,相当于铭文。 另外,登录后每次都带上密码,要想注销很麻烦,一般采用约定用户名密码表明要注销,比如:logout:logout cookie session cookie是http协议中的,在请求头和响应头中,但是session并不是http协议中的一部分。
如果不设置Expires的属性那么Cookie的存活时间就是在关闭浏览器的时候。默认cookies失效时间是直到关闭浏览器,cookies失效,也可以指定cookies时间。
服务端通过Set-Cookie头来指导浏览器保存cookie,指导过期时间、域名
cookie保存在浏览器是不安全的,通过嵌入脚本就能拿到cookie,比如xss(跨网站脚本),可以通过设置cookie的httponly属性 + https来避免。
session保存在服务端,但是容易被劫持,也就是通过监听网络拿到sessionid,有效的手段就是https;另外如果session生成规则比较简单,也是可以暴力尝试的。
jwt
jwt全称是json web token,它是保存在客户端的。传统的session是保存在服务端,每次拿到sessionid后还要查询数据库等来获取用户信息并校验,服务端压力比较大。jwt通过把用户信息通过一定的格式并加密后返回给客户端,每次请求传给服务端(一般放在header中),服务端进行解密校验即可自动用户的登录态和数据,避免了查询数据库。 jwt有三部分:base64url(header) +"." +base64url(payload) +"."+ 加密(base64url(header) +"." +base64url(payload) +盐)
header:
{
"alg": "HS256",
"typ": "JWT"
}
它指定了加密方法
Claims 或者叫payload,指定了用户的信息及jwt的其他信息,比如userId、过期时间等
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
golang中有jwt-go等框架,方便我们生成校验jwt 生成jwt
type Person struct {
Name string
Age int
}
type MyCustomClaims struct {
Person Person
jwt.StandardClaims
}
func jwtTest() string {
mySigningKey := []byte("AllYourBase")
// Create the Claims
claims := MyCustomClaims{
Person: Person{
Name: "zhang san",
Age: 12,
},
StandardClaims: jwt.StandardClaims{
NotBefore: int64(time.Now().Unix()),
ExpiresAt: time.Now().Unix() + 3,
Issuer: "test",
Id: "12345",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
ss, err := token.SignedString(mySigningKey)
fmt.Printf("%v %v\n", ss, err)
return ss
}
校验jwt
func jwtCheck(str string) {
token, err := jwt.Parse(str, func(token *jwt.Token) (interface{}, error) {
return []byte("AllYourBase"), nil
})
if token.Valid {
fmt.Println("You look nice today")
} else if ve, ok := err.(*jwt.ValidationError); ok {
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
fmt.Println("That's not even a token")
} else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 {
// Token is either expired or not active yet
fmt.Println("Timing is everything")
} else {
fmt.Println("Couldn't handle this token:", err)
}
} else {
fmt.Println("Couldn't handle this token:", err)
}
c, ok := token.Claims.(jwt.MapClaims)
fmt.Println(c["Person"], ok)
}