linux
检测端口是否打开:nc -zuv ip 端口
服务器监听端口:nc -l -u ip 端口(可以发送和接受信息)
客户端检测端口:nc -u ip 端口(可以发送和接受信息)
查看监听的tup端口:ss -ant
查看监听的udp端口:ss -anu
查看所有协议端口:ss -ano
windows
检测端口是否打开:nmap -sU IP -p 端口 -P
ConnectTimeout: 建立连接的超时时间,容易理解,TCP 三次握手的时间
SocketTimeout: 数据传输过程中数据包之间间隔的最大时间。
重点说下 SocketTimeout ,有的地方介绍为响应超时时间, 但是有没有想过这个响应超时时间,是指开始响应,还是整个响应完成呢? 如果 服务端 间隔的响应数据(比如 socketTimeout 为3s, 服务端每隔2s响应1个数据,总输出两次,整个耗时会是4s,会触发整个超时异常么?)
功能 | IDEA |
---|---|
多点编辑 | Alt + Shift + 鼠标点击 |
Ctrl+w 选中变量,Alt+j选中要选择的多个相同变量 | |
Alt + Enter (双引号内): Inject Inject Language ,方便 的 JSON 插入, 正则直接的校验 | |
Shift + F7 : 一行多个方法,让你选中进入某个方法(不必要每个方法再进一次了,可以方便的来跳过gettter,setter) | |
方法参数提示 | Ctrl +p |
Quick Text Search | Ctrl + Shift + F (Preview界面) |
定位文件/类 | Ctrl + Shift + N Ctrl + N |
类内部导航 | Ctrl + F12 / Alt + 7 |
窗口最大化 | Ctrl + Shift + F12 |
项目导航 | Alt + 1 (Alt + [1-9]好像都有对应的视图显示,可以尝试) |
代码生成get/set/toString等 | Alt + Insert |
代码补全 | Ctrl + J (psvm/sout/souf) |
补全 | Options->Keymap->copy->Main Menu->Code->Complete Code-> |
导入包 | Alt + Enter or Ctrl + Alt + O |
快速修复 | Alt + Enter |
显示类/方法说明 | Ctrl + Q |
调试单步进入 | F7 |
调试单步跳出 | F8 |
跳过 | F9 |
执行选中语句 | Alt + F8 |
变量重命名 | Shift + F6 |
查看实现 | Ctrl + Alt + B |
查看方法在哪里被使用 | Alt +F7 /Ctrl + B |
代码Back/Forward | Ctrl + Alt + Left/Right |
代码包裹/Surround With | Ctrl + Alt + T |
格式化代码 | Ctrl + Alt + L |
切换项目窗口 | Ctrl +Shift +[ |
—————————————- | ——————————————————————————– |
1 | cassadra |
技巧:cqlsh
提供的命令行是具有智能提示的,输入命令的部分字母,按Tab
建可以出发,再CREATE KEYSPACE
这条命令中可以深切的感受到方便。
1 | cqlsh 172.18.10.217 |
更多命令请参考 官方文档
CQL类型 | 对应Java类型 | 描述 |
---|---|---|
ascii | String | ascii字符串 |
bigint | long | 64位整数 |
blob | ByteBuffer/byte[] | 二进制数组 |
boolean | boolean | 布尔 |
counter | long | 计数器,支持原子性的增减,不支持直接赋值 |
decimal | BigDecimal | 高精度小数 |
double | double | 64位浮点数 |
float | float | 32位浮点数 |
inet | InetAddress | ipv4或ipv6协议的ip地址 |
int | int | 32位整数 |
list | List | 有序的列表 |
map | Map | 键值对 |
set | Set | 集合 |
text | String | utf-8编码的字符串 |
timestamp | Date | 日期 |
uuid | UUID | UUID类型 |
timeuuid | UUID | 时间相关的UUID |
varchar | string | text的别名 |
varint | BigInteger | 高精度整型 |
1 | git clone --depth=1 https://github.com/wg/wrk |
查看下版本以及命令帮助信息:
1 | wrk -v |
1 | wrk -t12 -c100 -d30s http://www.baidu.com |
30秒钟结束以后可以看到如下输出:
1 | Running 30s test @ http://www.baidu.com |
wrk 默认超时时间是1秒,可以使用T
参数设置,如设置超时为10s:
1 | wrk -t12 -c100 -T10s -d30s http://www.baidu.com |
输出响应时间分布:
1 | wrk -t12 -c100 -T10s -d30s --latency http://www.baidu.com |
HTTP请求通常不会这么简单,通常还有POST, Header, body 等,可以结合Lua脚本来测试。
建立一个 post.lua
文件:
1 | wrk.method = "POST" |
然后执行:
1 | wrk -t12 -c100 -d10s -T30s --script=post.lua --latency http://www.baidu.com |
查看 wrk.lua
的源码,https://github.com/wg/wrk/blob/master/src/wrk.lua
, 可以看出有以下属性:
1 | local wrk = { |
wrk 可以在lua脚本里添加下面的Hook函数,你可以想象成生命周期,每个生命周期做的事情都不一样, 但是生命周期是有时间顺序的。我们常用一般是 request 和 delay 周期. 另外还提供了以下几个hook函数:
setup
线程初始后支持会调用一次
init
每次请求发送之前被调用。可以接受 wrk 命令行的额外参数
delay
这个函数返回一个数值,在这次请求执行完以后延迟多长时间执行下一个请求,可以对应 thinking time 的场景
request
通过这个函数可以每次请求之前修改本次请求体和Header,我们可以在这里写一些要压力测试的逻辑。
response
每次请求返回以后被调用,可以根据响应内容做特殊处理,比如遇到特殊响应停止执行测试,或输出到控制台等等。
1 | function response(status, headers, body) |
done
在所有请求执行完以后调用, 一般用于自定义统计结果.
1 | done = function(summary, latency, requests) |
wrk 源码中给出的完整示例 ,源码 (这个目录下其他 脚本也可以参考)
1 | local counter = 1 |
以通过 lua 实现访问多个 url.
例如这个复杂的 lua 脚本, 随机读取 paths.txt 文件中的 url 列表, 然后访问:
1 | counter = 1 |
关于 cookie
有些时候我们需要模拟一些通过 cookie 传递数据的场景. wrk 并没有特殊支持, 可以通过 wrk.headers["Cookie"]="xxxxx"
实现.
下面是在网上找的一个取 Response的cookie作为后续请求的cookie :
1 | function getCookie(cookies, name) |
Supervisord 是用 Python 实现的一款非常实用的进程管理工具,supervisord 还要求管理的程序是非 daemon 程序,supervisord 会帮你把它转成 daemon 程序,因此如果用 supervisord 来管理 nginx 的话,必须在 nginx 的配置文件里添加一行设置 daemon off 让 nginx 以非 daemon 方式启动。
1 | apt-cache search python-setuptools |
修改为自己习惯的配置 /etc/supvervisord.conf
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24[unix_http_server] ; supervisord的unix socket服务配置
file=/usr/local/deploy/supervisord/supervisor.sock ; socket文件的保存目录
[inet_http_server] ; supervisord的tcp服务配置
port=*:9001 ; tcp端口
[supervisord] ; supervisord的主进程配置
logfile=/usr/local/deploy/supervisord/supervisord.log ; 主要的进程日志配置
logfile_maxbytes=50MB ; 最大日志体积,默认50MB
logfile_backups=10 ; 日志文件备份数目,默认10
loglevel=info ; 日志级别,默认info; 还有:debug,warn,trace
pidfile=/usr/local/deploy/supervisord/supervisord.pid ; supervisord的pidfile文件
nodaemon=false ; 是否以守护进程的方式启动
minfds=1024 ; 最小的有效文件描述符,默认1024
minprocs=200 ; 最小的有效进程描述符,默认200
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl]
serverurl=unix:///usr/local/deploy/supervisord/supervisor.sock ; use a unix:// URL for a unix socket
[include]
files = /usr/local/deploy/supervisord/conf/*.conf
vim /etc/supervisord.conf
1 | mkdir -p /usr/local/deploy/supervisord/conf/ |
1 | cd /usr/local/deploy/supervisord/conf/ |
内容:1
2
3
4
5
6[program:express-service]
command=node /home/vagrant/test-service/app.js
autostart=false
autorestart=false
stdout_logfile=/usr/local/deploy/log/express-service.out
stderr_logfile=/usr/local/deploy/log/express-service.err
如果报错,可以使用 supervisorctl tail express-service stdout
来查看具体报错。
另外配置文件可使用:%(directory)
的格式来引用变量。
http://127.0.0.1:9001/
可以查看 Tail -f
的日志,通过Nginx代理后则不行;Nginx需要增加以下配置,参考Supervisord inet_http_server behind nginx:1 | server { |
启动:supervisord
(默认会读/etc/supervisord.conf
)或者 指定主配置supervisord -c /etc/supervisord.conf
auth-service.conf
:1
2
3
4
5
6[program:auth-service]
command=java -D"app.id=gateway" -D"env=FAT" -javaagent:/usr/local/skywalking-agent/skywalking-agent.jar -jar /usr/local/deploy/auth-service-exec.jar
autostart=false
autorestart=false
stdout_logfile=/usr/local/deploy/log/auth-service.out
stderr_logfile=/usr/local/deploy/log/auth-service.err
shadowsocks.conf
:1
2
3
4
5[program:shadowsocks]
command=sslocal -c ~/shadowsocks.json
autostart=true
autorestart=true
user=nobody
http://www.supervisord.org/configuration.html#program-x-section-example1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30[program:cat]
command=/bin/cat
process_name=%(program_name)s
numprocs=1
directory=/tmp
umask=022
priority=999
autostart=true
autorestart=unexpected
startsecs=10
startretries=3
exitcodes=0,2
stopsignal=TERM
stopwaitsecs=10
stopasgroup=false
killasgroup=false
user=chrism
redirect_stderr=false
stdout_logfile=/a/path
stdout_logfile_maxbytes=1MB
stdout_logfile_backups=10
stdout_capture_maxbytes=1MB
stdout_events_enabled=false
stderr_logfile=/a/path
stderr_logfile_maxbytes=1MB
stderr_logfile_backups=10
stderr_capture_maxbytes=1MB
stderr_events_enabled=false
environment=A="1",B="2"
serverurl=AUTO
1 | ; Sample supervisor config file. |
1 | mkdir -p /usr/servers |
1 | wget https://openresty.org/download/openresty-1.13.6.2.tar.gz |
1 | cd LuaJIT-2.1-20180420/ |
1 | cd /usr/servers/openresty-1.13.6.2/bundle/ |
1 | cd /usr/servers/ngx_openresty-1.7.7.2/bundle |
1 | yum install openssl-devel pcre-devel zlib-devel -y |
/usr/servers/luajit
:luajit环境,luajit类似于java的jit,即即时编译,lua是一种解释语言,通过luajit可以即时编译lua代码到机器代码,得到很好的性能;
/usr/servers/lualib
:要使用的lua库,里边提供了一些默认的lua库,如redis,json库等,也可以把一些自己开发的或第三方的放在这;
/usr/servers/nginx
:安装的nginx;
/usr/servers/nginx/sbin/nginx
1 | vim /usr/servers/nginx/conf/nginx.conf |
1 | #lua模块路径,多个之间”;”分隔,其中”;;”表示默认搜索路径,默认到/usr/servers/nginx下找 |
1 | #lua.conf |
1 | include lua.conf; |
1 | /usr/servers/nginx/sbin/nginx -t |
1 | wget http://luajit.org/download/LuaJIT-2.0.5.tar.gz |
1 | wget https://github.com/simplresty/ngx_devel_kit/archive/v0.3.1rc1.tar.gz |
1 | wget http://nginx.org/download/nginx-1.15.2.tar.gz |
1 | echo "/usr/local/LuaJIT/lib" >> /etc/ld.so.conf |
当客户端ip为192.168.56.1
时,让其访问新发布的upstream测试实例;其他ip访问时还是老的upstream实例。相当于前者为新发布功能,后者为稳定的生产功能。
这里使用 memcache
来存储哪些ip要访问新的,哪些ip访问稳定的老的。
1 | yum install memcached |
准备两个服务新上线服务
与 稳定生产服务
,这里使用两个tomcat来演示(如果你熟悉node 也可以使用node快速实现两个后台服务)
1 | #测试页面 |
配置Nginx nginx.conf
1 | | location / { |
编写 /opt/app/lua/dep.lua
1 | local clientIP = ngx.req.get_headers()["X-Real-IP"] |
启动Nginx,访问测试
在本机使用 curl http://127.0.0.1/test.jsp
查看效果,返回结果无 Test server 字样。
在192.168.56.1
访问 http://192.168.56.101
(此ip为Nginx 服务所在ip) 查看效果,返回结果有 Test server 字样。
以上只是简单的针对某个 ip
做的灰度,完整的灰度,还需要企业后台灵活的管理memcache
。
另外要实现更灵活的灰度,比如根据 Cookie, header 头信息,也可以个根据这种原理来实现。
对于建链接的3次握手 主要是初始化Sequence Number
的初始值。通信的双方要互相知道对方初始化的Sequence Number
(缩写ISN
: Initial Sequence Number
),所以叫SYN
,全称:Synchronize Sequence Number。这个同步序号作为此TCP连接后面数据通信的序号,以保证应用层能处理乱序、重复、丢失的问题。
对于4次挥手 其实你仔细看是2次,因为TCP是全双工的,所以双方都会发出FIN
、ACK
一次请求和应答。只不过有一方是被动的,所以看上去就像是4次挥手。如果两边同时发出断开的请求,那就会进入到CLOSING
的状态,然后到达TIME_WAIT
状态。下图是双方同时段连接的示意图:
因为双方同时发起了FIN
,都认为自己是FIN发起的主动方
,各自都进入FIN_WAIT_1
状态,都在等待对方被动
发出FIN
请求,同时双方都收到对方的FIN
请求,于是各自应答对方ACK
,对方进入CLOSING
状态,这个CLOSING
都是在等待前面提到等待对方发出被动的FIN请求
,都在互相等待,那就像死锁
了,这里的解决方案就是进入CLOSING
状态时同时开启TIME_WAIT
等待指定的时机后会自动进入CLOSED
状态。
下面我们用电话通话
的例子来比拟下这里的建立连接3次握手,断开链接4次挥手。
A: 你听到我说话么? – 1. 发往
B
的一个握手请求。发出问题让对方回答,目的是为了验证自己说的话对方能听到,即验证自己的话筒
->对方的听筒
这条链路。
B: 我听到你说话,你能听到我说话吗? –2. 发往
A
的一个握手请求。回答对方的问题,让对方知道他的验证通过。自己再发出问题,目的是为了验证自己说的话对方能听到,也就是:B的话筒
->A的听筒
这条链路是通的。
A: 我也能听到你说话 – 3. 发往
B
的一个握手请求。回答B
的问题,让B
知道他说的话自己也能听到。
A: 我的话说完了,要挂电话了,你说完了么? (发起断开
A -> B
的请求)
B: 好吧(应答下对方的挂掉意思本方已经收到, 这里意味着
A -> B
不会再发起新的请求,只有应答)
….
B: 我也说完了,我也要挂电话了 (发起断开
B -> A
链路的请求)
A: 好的 (应答对方,意味
B -> A
的链路也断开了,整个连接完整断开)
这里要解释的一点是,上面B
的连在一起的两句话,为什么不能像建立连接时候的两句话再一次请求里一次说掉呢? 因为bye bye
前可能还有话正在说,而hello
是对话的开始,之前没有别的话。
关于建连接SYN超时 试想一下,如果server
接到了client
的SYN
请求,然后应答了ACK-SYN
后client
掉线,server
端没有收到client
回来的ACK
,那么连接处于一个中间的状态,既没成功,也没失败。于是server
端如果在一定时间内没有收到ACK
,TCP
回重发ACK-SYN
。在Linux
下,默认重试次数是5次,5次重试的时间间隔分别是:1s, 2s, 4s, 8s, 16s, 32s
,所以总共需要1s+2s+4s+8s+16s+32s = 2^6 -1 = 63s
,TCP
才会断开这个连接
关于SYN Flood攻击 一些恶意的人就为此制造了SYN Flood
攻击——给服务器发了一个SYN
后就下线,于是服务器默认等63s才会断开连接,这样攻击者重复这个过程就可以把服务器SYN连接队列
耗尽,让正常的连接请求不能处理。于是,Linux
下给了一个叫tcp_syncookies
的参数来应对这个事——当SYN连接队列
满了以后,TCP
会通过源地址端口、目标地址端口和时间戳打造出一个特别的Sequence Number
发回去(又叫cookie
),如果是攻击者则不会有响应,如果是正常连接,则会把这个SYN cookie
发回来,然后服务端可以通过cookie
来建连接(即使你不在SYN队列中
),请注意请先千万别用tcp_syncookies
来处理正常的大负载连接的情况。因为syncookies
是妥协版的TCP
协议,并不严谨。对于正常的请求有三个参数可供调整,第一个是:tcp_synack_retries
可以减少重试次数;第二个是:tcp_max_syn_backlog
,可以增大SYN
连接数;第三个是:tcp_abort_on_overflow
处理不过来就直接拒绝连接。
关于ISN
的初始化 ISN
是不能用hard code
的,不然会有问题——如果连接建立好始终使用1
来作ISN
,client
发了30
个segment
过去,但是网络断了,于是client
重连,又用1
做ISN
,但是之前连接的那些包到了,于是就被当成了新连接的包,此时,client
的Sequence Number
可能是3
,而server
端认为client
的这个号是30了。全乱了。RFC793中说,ISN
会和一个假的时钟绑在一起,这个时钟会在每4微秒
对ISN
做加一操作,知道超过2^32
,又从0
开始。这样,一个ISN
的周期大约是4.55个小时
。因为,我们假设我们的TCP segment
在网络上的存货时间不会超过Maximun Segment Lifetime
(MSL),所以,只要MSL
的值小于4.55小时
,那么我们就不会重用到ISN
关于MSL
和TIME_WAIT
在TCP
的状态图中,从TIME_WAIT
状态到CLOSED
状态,有一个超时设置是2*MSL
(RFC793定义MSL
为分钟
,Linux
设置为30s
),为什么要有TIME_WAIT
,不直接转成CLOSED
状体呢?主要有两个原因:1) TIME_WAIT
确保有足够的时机让对方收到ACK
,如果被动关闭的那方没有收到ACK
,就会触发被动端重发FIN
,一来一去正好2个MSL
2) 有足够的时间让这个连接不会跟后面的连接混在一起(你要知道,有些路由器会缓存IP数据包
,如果连接被重用了,那么这些延迟收到的包就有可能会跟新连接混在一起)
关于TIME_WAIT
数量太多 从上面的描述我们可以知道,TIME_WAIT
是一个很重要的状态,但是如果在大并发的短链接下,TIME_WAIT
就会太多,这也会消耗很多系统资源。只要搜一下,就会发现多数方式会教你设置两个参数,一个tcp_tw_reuse
,另一叫tcp_tw_recycle
,这两个参数默认是关闭的后者recycle
比前者更激进,reuse
要温柔一些。另外,如果使用tcp_tw_reuse
,必须设置tcp_timestamps=1
,否则无效。这里,你一定要注意,打开这两个参数会有比较大的坑——可能会让TCP
连接出一些诡异的问题
tcp_tw_reuse
官方文档上说tcp_tw_reuse
加上tcp_timestamps
可以保证协议角度上的安全,但是你需要tcp_timestamps
在两端都被打开tcp_tw_recycle
如果tcp_tw_recycle
打开的话,会假设对端开启了tcp_timestamps
,然后回去比较时间戳,如果时间戳变大,就可以重用。但是,如果对端是一个NAT
网络的话(如:一个公司只用一个IP出公网)或是对端的IP
被另一台重用了,这个事就复杂了。建链接的SYN
可能被直接丢掉(你可以看到connnection time out
的错误)tcp_max_tw_buckets
这个是控制并发TIME_WAIT
的数量,默认值是180000
,如果超限,那么系统会把多的destory掉,然后在日志里打上警告(如:time wait bucket table overflow
),官网文档说这个参数用来对抗DDOS
攻击的。也说的180000
默认值并不小。这个还要根据实际的情况考虑。TIME_WAIT
意味是你主动断开连接的,如果对方断连接,那么这个破问题就是对方的了。另外如果你的服务器是HTTP
服务器,那么设置一个HTTP的KeepAlive
相当重要(浏览器会重用一个TCP
连接处理多个HTTP
请求),让客户端去断链接。
参考自:TCP 的那些事儿
OAuth在我印象中已经啃过几次了。大多数时候,好记性还是不如烂笔头,对自己的记忆力太过自信了,当时理解了,觉的妙,但是现在只记得一个妙字了,至于其它的已经忘的差不多了…
所以最好趁自己刚理解的那一刻,赶紧的尽量用自己组织的语言记录下来。
首先,想到OAuth 2.0
,脑海中先弹出三方交互的概念,三方即如下:
在来挨个解释下,资源提供者,好比新浪微博提供了发微博、获取微博列表,百度网盘提供了存储文件、访问文件列表的服务,其中新浪微博、百度网盘都是资源的提供者。用户就是资源拥有者,具体的某条微博、网盘里的某个照片都是具体用户的,没有得到用户允许,资源提供者不可供其他应用使用。获取资源者 就是一些非资源提供者的应用想要访问这些资源,比如一款冲印照片的应用需要访问你网盘里的照片。
资源提供者
、获取资源者
、用户(资源拥有者)
,为了引用方便,我们先将分别简称其为:B1
、B2
、C
。站在B1
的角度来说,它定义了OAuth服务,交互细节都有它来设计,整个流程都需要依赖它,其一般是拥有众多用户的高可用服务。对于B2
来说,它“觊觎”B1
庞大的用户群以及用户资源(为用户省心,避免再注册于在上传资源),它的最终目的是通过Access Token
拿到想要的资源。对于C
来说,它其实既是B1
的用户,也是B2
的用户,C
在整个过程中只需要选中同意还是不同意。
搞清楚了这些概念后,如果你要做应用,最好确定下你是要做提供者的应用 还是获取者的应用,前者相当于是要在基于现有服务,整理好资源提供一整套OAuth 2.0
的Server,后者作为一个OAuth 2.0
的Client相对较为简单。
有了以上的概念我们就能从宏观上来理解为什么会有OAht2.0的存在。如果要从软件上来实现它,基于以上三者,我们还需要细分一下,如下:
OAuth 2.0
的运行流程如下:(referrence from RFC-6749)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17+--------+ +---------------+
| |--(A)- Authorization Request ->| Resource |
| | | Owner |
| |<-(B)-- Authorization Grant ---|
| | +---------------+
| |
| | +---------------+
| |--(C)-- Authorization Grant -->| Authorization |
| Client | | Server |
| |<-(D)----- Access Token -------| |
| | +---------------+
| |
| | +---------------+
| |--(E)----- Access Token ------>| Resource
| | | Server |
| |<-(F)--- Protected Resource ---| |
+--------+ +---------------+
步骤如下:1
2
3
4
5
6(A) 用户打开客户端后,客户端要求用户授权
(B) 用户同意给予客户端授权
(C) 客户端使用上一步获得的授权,向认证服务器申请令牌。
(D) 认证服务器对客户端进行认证后,确认无误,同意发放令牌
(E) 客户端使用令牌,向资源服务器申请获取资源
(F) 资源服务器确认令牌无误,同意向客户端开放资源
简单的来分析下,上面的流程是以请求为载体流转的,最终拿到Access token需要经过几个步骤,但是拿到的Access Token在失效前一直可以重复使用,也就是后面对资源的请求都是请求-应答这种方式。得到Access Token前花费两次“请求-响应”,一次是询问用户是否授权,另外一次是根据授权码请求得到Access Token。这一点与我们平时的普通web应用不同,平时web的认证只需要一次登录“请求-响应”,一般请求携带着用户名、密码,响应头里设置客户端sessionid至cookie。为什么前者需要两次,而后者只需要一次呢?因为后者服务端只需要验证用户名密码是否正确,而前者则需要转个弯,三方应用先向用户申请,申请同意过后再向资源提供的应用申请。
前面讲到三方应用要向用户申请授权,这个过程要怎么做才合适呢?很显然这个授权的界面不能由第三来做,不然就没一点意义了,这个授权的界面都是资源提供方来做的,一般在界面还会提示一些文字让用户确认该界面是资源官方提供的,比如新浪微博:
这个提醒大多数普通用户是忽略的,因为现在也很少有黑客用这种方式用来盗取微博的密码了,不过这种方式在仿银行的网站上存在很多。
客户端必须得到用户的授权(authorization grant),才能获取令牌(Access Token),OAuth2.0定义了四种授权方式。
授权码模式(authorization code)是功能最完整、流程最严密的授权模式。它的特点是通过客户端的后台服务器,与“服务提供商”的认证服务器进行互动。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B)
+----|-----+ Client Identifier +---------------+
| -+----(A)-- & Redirection URI ---->| |
| User- | | Authorization |
| Agent -+----(B)-- User authenticates --->| Server |
| | | |
| -+----(C)-- Authorization Code ---<| |
+-|----|---+ +---------------+
| | ^ v
(A) (C) | |
| | | |
^ v | |
+---------+ | |
| |>---(D)-- Authorization Code ---------' |
| Client | & Redirection URI |
| | |
| |<---(E)----- Access Token -------------------'
+---------+ (w/ Optional Refresh Token)
执行步骤:1
2
3
4
51. 用户访问客户端,后者将其导向认证服务器 ----------------- 比如打开:https://passport.csdn.net/ ,点击“微博登录”,其链接导向的是:https://api.weibo.com/oauth2/authorize?client_id=2601122390&response_type=code&redirect_uri=https%3A%2F%2Fpassport.csdn.net%2Faccount%2Flogin%3Foauth_provider%3DSinaWeiboProvider
2. 用户选择是否给客户端授权
3. 假设用户选择了授权,认证服务器将用户导向至客户端事先指定的“重定向URI”(redirect URI),同时在URI后会附上一个授权码-----------------比如前面的新浪:点击授权,请求api.weibo/oauth2/autorize,响应“302”(重定向),响应头中的Location=https://passport.csdn.net/account/login?oauth_provider=SinaWeiboProvider&code=fbfd1c75c3309bb653a7c0816f919f49 ,即重定向地址。
4. 客户端根据“附带了授权码code的重定向URI”请求,即重定向的实现,客户端的后台服务器上向认证服务器申请令牌-token,这个请求-响应对于客户端与用户是不可见的
5. 认证服务器对授权码和重定向URI确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)
看下上面的一些请求细节。
第一步,客户端导向认证服务器的URI的参数:
第二步,授权界面:
第三步,授权与否:
第四步,带授权码的URI重定向跳转,客户端的后台服务向认证服务器申请token
第五步,认证服务器检查token请求,响应参数如下:
关于这三种模式,这里只会简单的记录下,如果需要详细了解,可以点文章末尾的链接去看阮一峰的文章。
从以上的实现机制,可以看出最终是为了得到token
。其实这个token
与平时web会话客户端的那个 sessionid
的本质意义是一样的,都是用户的身份标识。
为了sessionid
的安全我们一般会做两点,后台设置的response cookie是httpOnly
的,也就是js不可读取保证客户端安全,另外就是使用https
保证传输通道的安全。按照这个思路我们可以类比下这个token
该怎么在安全方面有所保障。
安全问题,需要好好权衡,“三道安全门”你自己进门都会很困难,“一道安全门”足已,重要的是你要保护好自己的钥匙,插在门上不拔,再多安全门也没用。
细节总难被记住,单个的web项目的身份认证只需要一个登录请求即可,而OAuth2.0涉及的是三方,会有两次请求,记忆的转弯点在这里,所以我的总结就是请求授权码,请求token。