整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

Scrapy 爬虫模拟登陆的3种策略

Scrapy 爬虫模拟登陆的3种策略

crapy 爬虫模拟登陆的3种策略

1 Scrapy 爬虫模拟登陆策略

前面学习了爬虫的很多知识,都是分析 HTML、json 数据,有很多的网站为了反爬虫,除了需要高可用代理 IP 地址池外,还需要登录,登录的时候不仅仅需要输入账户名和密码,而且有可能验证码,下面就介绍 Scrapy 爬虫模拟登陆的几种策略。

1.1 策略一:直接POST请求登录

前面介绍的爬虫 scrapy 的基本请求流程是 start_request 方法遍历 start_urls 列表,然后 make_requests_from_url方法,里面执行 Request 方法,请求 start_urls 里面的地址,使用的是 GET 方法,由于直接使用用户名和密码可以登录,使用 POST 方法进行登录。

例子:人人网登录

登录地址:http://www.renren.com/PLogin.do

案例步骤:

第一步:创建项目。

在 dos下切换到目录

D:\scrapy_project

新建一个新的爬虫项目:scrapy startproject renren

第二步:创建爬虫。

在 dos下切换到目录。

D:\scrapy_project\renren\renren\spiders

用命令 scrapy genspider renren1 " renren.com" 创建爬虫。

第三步: 通过浏览器登录人人网,使用 fiddler 抓包抓取登录 post 请求的 data。

第四步:编写爬虫文件。

import scrapy

# 登录只需要提供 post 数据就可以登录的,就可以用这种方法,

# 下面示例:post 数据是账户密码

class Renren1Spider(scrapy.Spider):

name="renren1"

allowed_domains=["renren.com"]

def start_requests(self):

url='http://www.renren.com/PLogin.do'

# FormRequest 是 Scrapy 发送 POST 请求的方法

yield scrapy.FormRequest(

url=url,

formdata={"email" : "13554799060", "password" : "xny123"},

callback=self.parse_page)

# 回调方法,对返回的 response 进行处理(把response.body保存到 xiao.html 中)

def parse_page(self, response):

with open("xiao.html", "wb") as filename:

filename.write(response.body)

第五步:修改 settings 文件。

设置爬虫请求的默认头信息。

第六步:运行爬虫。

在 dos下切换到目录

D:\scrapy_project\renren\renren 下

通过命令运行爬虫 :scrapy crawl renren1

第七步:查看结果。

xiao.html 中显示的内容正是登录自己人人网之后的主页内容,说明登录成功。

1.2 策略二:标准的模拟登陆

标准的模拟登录方法:

1、首先发送登录页面的 get 请求,获取到页面里的登录必须的参数。

2、登录必须的参数和账户密码一起 post 到服务器,登录成功。

23.2.1 Cookie原理

HTTP 是无状态的面向连接的协议, 为了保持连接状态, 标准的模拟登陆案例引入了 Cookie 机制。

Cookie 是 http 消息头中的一种属性,包括:

.Cookie 名字(Name)Cookie 的值(Value)

.Cookie 的过期时间(Expires/Max-Age)

.Cookie 作用路径(Path)

.Cookie 所在域名(Domain),使用 Cookie 进行安全连接(Secure)。

前两个参数是 Cookie 应用的必要条件,另外,还包括 Cookie 大小( Size,不同浏览器对Cookie 个数及大小限制是有差异的 )。

23.2.2 模拟登陆

爬取的网站:github (https://github.com/login)

案例步骤:

第一步:爬取前分析。

打开 fiddler,接着我们打开 github 的登陆页面(https://github.com/login ),输入用户名、密码( 输入错误的密码 ),提交查看 fiddler 获取的信息,结果入下:

输入用户名和错误密码获取的 fiddler 结果:

我们用 google 浏览器看源码也可以看到 form 提交时会加入 authenticity_token 参数一起,如下图:

第二步:创建项目。

在 dos下切换到目录

D:\scrapy_project

新建一个新的爬虫项目:scrapy startproject github

第三步:创建爬虫。

在 dos下切换到目录。

D:\scrapy_project\github\github\spiders

用命令 scrapy genspider gh "github.com" 创建爬虫。

第四步: 开始前的准备工作。

(一)、在 scrapy.cfg 同级目录下创建 pycharm 调试脚本 run.py,内容如下:

# -*- coding: utf-8 -*-

from scrapy import cmdline

cmdline.execute("scrapy crawl github".split())

(二)修改 settings 中的 ROBOTSTXT_OBEY=True 参数为 False,因为默认为 True,就是要遵守 robots.txt 的规则, robots.txt 是遵循 Robot协议 的一个文件,它保存在网站的服务器中,它的作用是,告诉搜索引擎爬虫,本网站哪些目录下的网页不希望你进行爬取收录。在 Scrapy 启动后,会在第一时间访问网站的 robots.txt 文件,然后决定该网站的爬取范围。查看 robots.txt 可以直接网址后接 robots.txt 即可。

例如百度:https://www.baidu.com/robots.txt

修改 settings 文件。

(三)模拟登陆时,必须保证 settings.py 里的 COOKIES_ENABLED (Cookies中间件) 处于开启状态。

COOKIES_ENABLED=True

第五步:编写爬虫文件-获取 authenticity_token。

首先要打开登陆页面,获取 authenticity_token,代码如下:

import scrapy

class GithubSpider(scrapy.Spider):

name='gh'

allowed_domains=['github.com']

def start_requests(self):

urls=['https://github.com/login']

for url in urls:

# 重写 start_requests 方法,通过 meta 传入特殊 key cookiejar,爬取 url 作为参数传给回调函数

yield scrapy.Request(url, meta={'cookiejar': 1}, callback=self.github_login)

def github_login(self, response):

# 首先获取authenticity_token,这里可以借助scrapy shell ”url“来获取页面

# 然后从源码中获取到authenticity_token的值

authenticity_token=response.xpath("//input[@name='authenticity_token']/@value").extract_first()

# 利用 scrapy 内置 logger 打印 info 信息

self.logger.info('authenticity_token='+ authenticity_token)

pass

运行结果:

通过运行的结果,可以看到我们已经获取了 authenticity_token 的值,这一步重点要说明meta、cookiejar 和 logger。

【meta】:字典格式的元数据,可以传递给下一个函数 meta。

【cookiejar】:是 meta 的一个特殊的key,通过 cookiejar 参数可以支持多个会话对某网站进行爬取,可以对 cookie 做标记,1,2,3,4......这样 scrapy 就维持了多个会话;

【logger】:scrapy 为每个 spider 实例内置的日志记录器。

为了能使用同一个状态持续的爬取网站, 就需要保存cookie, 使用cookie保存状态, Scrapy 提供了 cookie 处理的中间件, 可以直接拿来使用,Scrapy 官方的文档中给出了下面的代码范例 :

for i, url in enumerate(urls):

yield scrapy.Request("http://www.example.com", meta={'cookiejar': i},

callback=self.parse_page)

def parse_page(self, response):

# do some processing

return scrapy.Request("http://www.example.com/otherpage",

meta={'cookiejar': response.meta['cookiejar']},

callback=self.parse_other_page)

第六步:修改爬虫文件- FormRequest(登录表单提交)

Scrapy 提供了 FormRequest 类,是 Request 类的扩展,专门用来进行 Form 表单提交。我们主要使用 FormRequest.from_response()方法来模拟简单登陆,通过FormRequest.from_response 提交后,交给回调函数处理。代码如下:

import scrapy

class GithubSpider(scrapy.Spider):

name='gh'

allowed_domains=['github.com']

def start_requests(self):

urls=['https://github.com/login']

for url in urls:

# 重写 start_requests 方法,通过 meta 传入特殊 key cookiejar,爬取 url 作为参数传给回调函数

yield scrapy.Request(url, meta={'cookiejar': 1}, callback=self.github_login)

def github_login(self, response):

# 首先获取authenticity_token,这里可以借助scrapy shell ”url“来获取页面

# 然后从源码中获取到authenticity_token的值

authenticity_token=response.xpath("//input[@name='authenticity_token']/@value").extract_first()

# 利用 scrapy 内置 logger 打印 info 信息

self.logger.info('authenticity_token='+ authenticity_token)

# url 可以从 fiddler 抓取中获取,dont_click 作用是如果是 True,表单数据将被提交,而不需要单击任何元素。

return scrapy.FormRequest.from_response(

response,

url='https://github.com/session',

meta={'cookiejar': response.meta['cookiejar']},

headers=self.headers,

formdata={'utf8':'?',

'authenticity_token': authenticity_token,

'login': 'xxxxxx@qq.com',

'password': 'xxxxxx'},

callback=self.github_after,

dont_click=True,

)

第七步:修改爬虫文件- 伪装头部。

为了更真实的模拟浏览器登陆网站,需要进行头部伪装, 在 scrapy 中 Request 和 FormRequest 初始化的时候都有一个 headers 字段, 可以自定义头部, 这样我们可以添加 headers 字段。

# 头信息直接从 fiddler 中复制出来

headers={

"Connection": "keep-alive",

"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36",

"Referer": "https: // github.com /",

"Content - Type": "application / x - www - form - urlencoded",

}

第八步:修改爬虫文件- 增加回调函数,主要是登陆成功之后,获取登录之后返回的页面(response)的元素进行断言,验证登录结果。

登录之后,主页如下:

# 回调函数

def github_after(self, response):

# 获取登录页面主页中的字符串'Browse activity'

list=response.xpath("//a[@class=‘tabnav-tab selected‘]/text()").extract()

# 如果含有字符串,则打印日志说明登录成功

if 'Browse activity' in list:

self.logger.info('我已经登录成功了,这是我获取的关键字:Browse activity')

else:

self.logger.error('登录失败')

第九步:整理完整的爬虫文件

import scrapy

class GithubSpider(scrapy.Spider):

name='gh'

allowed_domains=['github.com']

# 头信息直接从 fiddler 中复制出来

headers={

"Connection": "keep-alive",

"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36",

"Referer": "https: // github.com /",

"Content - Type": "application / x - www - form - urlencoded",

}

def start_requests(self):

urls=['https://github.com/login']

for url in urls:

# 重写 start_requests 方法,通过 meta 传入特殊 key cookiejar,爬取 url 作为参数传给回调函数

yield scrapy.Request(url, meta={'cookiejar': 1}, callback=self.github_login)

def github_login(self, response):

# 首先获取authenticity_token,这里可以借助scrapy shell ”url“来获取页面

# 然后从源码中获取到authenticity_token的值

authenticity_token=response.xpath("//input[@name='authenticity_token']/@value").extract_first()

# 利用 scrapy 内置 logger 打印 info 信息

self.logger.info('authenticity_token='+ authenticity_token)

# url 可以从 fiddler 抓取中获取,dont_click 作用是如果是 True,表单数据将被提交,而不需要单击任何元素。

return scrapy.FormRequest.from_response(

response,

url='https://github.com/session',

meta={'cookiejar': response.meta['cookiejar']},

headers=self.headers,

formdata={'utf8':'?',

'authenticity_token': authenticity_token,

'login': '55666727@qq.com',

'password': 'xny8816056'},

callback=self.github_after,

dont_click=True,

)

# 回调函数

def github_after(self, response):

# 获取登录页面主页中的字符串'Browse activity'

list=response.xpath("//a[@class=‘tabnav-tab selected‘]/text()").extract()

# 如果含有字符串,则打印日志说明登录成功

if 'Browse activity' in list:

self.logger.info('我已经登录成功了,这是我获取的关键字:Browse activity')

else:

self.logger.error('登录失败')

第十步:查看运行的结果。

通过运行的结果说明登录成功。

1.3 策略三:直接使用保存登陆状态的 Cookie 模拟登陆

如果实在没办法了,可以用策略三这种方法模拟登录,虽然麻烦一点,但是成功率100%。

案例步骤:

第一步:创建项目。

在 dos下切换到目录

D:\scrapy_project

新建一个新的爬虫项目:scrapy startproject renren2

第二步:创建爬虫。

在 dos下切换到目录。

D:\scrapy_project\renren2\renren2\spiders

用命令 scrapy genspider ren2 "renren.com" 创建爬虫。

第三步: 通过浏览器登录人人网,使用 fiddler 抓包抓取登录后的Cookis。

第四步: 开始前的准备工作。

(一)、在 scrapy.cfg 同级目录下创建 pycharm 调试脚本 run.py,内容如下:

# -*- coding: utf-8 -*-

from scrapy import cmdline

cmdline.execute("scrapy crawl renren".split())

(二)修改 settings 中的 ROBOTSTXT_OBEY=True 参数为 False,因为默认为 True,就是要遵守 robots.txt 的规则, robots.txt 是遵循 Robot协议 的一个文件,它保存在网站的服务器中,它的作用是,告诉搜索引擎爬虫,本网站哪些目录下的网页不希望你进行爬取收录。在 Scrapy 启动后,会在第一时间访问网站的 robots.txt 文件,然后决定该网站的爬取范围。查看 robots.txt 可以直接网址后接 robots.txt 即可。

修改 settings 文件。

(三)模拟登陆时,必须保证 settings.py 里的 COOKIES_ENABLED ( Cookies 中间件) 处于开启状态。

COOKIES_ENABLED=True

第五步: 编写爬虫文件。

import scrapy

class RenrenSpider(scrapy.Spider):

name="renren"

allowed_domains=["renren.com"]

Cookies={

"anonymid": "jlvxr345k9ondn",

"wp_fold": "0",

"depovince": "GW",

"jebecookies": "3af719cc-f819-4493-bcb6-c967fc59f04a|||||",

"_r01_": "1",

"JSESSIONID": "abcwnUubDsWO467i0mgxw",

"ick_login":"27af5597-30d7-469c-b7c4-184a6e335fcb",

"jebe_key":"d1f5682d-03b4-46cd-87c0-dc297525ed11%7Ccfcd208495d565ef66e7dff9f98764da%7C1536628574466%7C0%7C1536628572944",}

# 可以重写 Spider 类的 start_requests 方法,附带 Cookie 值,发送 POST 请求

def start_requests(self):

url='http://www.renren.com/PLogin.do'

# FormRequest 是 Scrapy 发送 POST 请求的方法

yield scrapy.FormRequest(url, cookies=self.Cookies, callback=self.parse_page)

# 处理响应内容

def parse_page(self, response):

print("===========" + response.url)

with open("xiao2.html", "wb") as filename:

filename.write(response.body)

第六步: 运行程序,查看运行结果。

Xiao2.html 中显示的内容正是登录自己人人网之后的主页内容,说明登录成功。

节重点来介绍一下JSON,JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,我们称之为JavaScript对象表示法。也就是说,JSON是一种格式。首先搞清楚三个概念,即什么是JSON字符串,什么是JavaScript对象,还有什么又叫做JSON对象?先来说一个事,在没有JSON之前,前台页面和Java等语言充当的服务器层,到底是如何传输数据的呢?没错,是通过XML来传输的。比如一个登陆页面。

页面上有用户名和密码两个输入框,当我点击登录按钮,这两个数据就会被传递到服务器层。那么,如何传输呢?如果用XML,也许是这样的:

<LoginData> <name>zhangsan</name> <password>123</password></LoginData>

后台接收到这个数据,然后就可以开始解析,最终拿到zhangsan和123两个字面量。时间线再往前推,在XML还没有出来的时候,怎么办呢?聪明的程序开发人员则会规定几种特殊的格式,拼接一个特殊的字符串,传递到后台中去。比如像这样的:

"name=zhangsan&password=123"

那么后台的程序员也知道这个规则,如果是Java的话,就可以使用String的splite方法,先通过逗号把这个字符串分割成两份,也就是变成:name=zhangsan还有password=123两个字符串,然后再通过“=”分割,将“name=zhangsan”分割成“name”和“zhangsan”,把“password=123”分割成“password”和“123”两部分。终于,到底还是拿到用户名和密码了。

接下来还是谈JSON,其实JSON就是一种数据格式。诸如:

{
 key1 : value1 ,
 key2 : value2
};

这样的格式就是JSON格式,它是一系列键值对的集合,不同的键值对之间用逗号分隔,最后一个键值对不需要加逗号。符合这种格式的字符串就是JSON字符串。比如:

"{'name' : 'Jack'}"

它归根到底还是一个字符串,不是一个对象。而JSON对象,其实就是Javascript对象,我们可以通过字面值的方式直接创造一个对象,比如:

var person={name : 'Jack'}

等同于:

var person={'name' : 'Jack'}

在上边这个例子中,name可加单引号,也可加双引号,甚至可以什么都不加。而右边的值必须是一个实实在在的东西,比如字符串,或者一个对象,甚至是一个函数。我们不考虑JS内部的对象机制,只是简单地说明一下,是有这么个事情的。这就是所谓的JSON对象,也就是js对象。在JavaScript中,对象是键值对的集合,符合JSON格式。我们可以通过下面的方法,把JS对象转换成JSON格式的字符串。

var person={'name' : 'Jack'}
alert(JSON.stringify(person));

同样,一个JSON格式的字符串,可以变成一个JS对象,如:

console.log(JSON.parse("{\"name\":\"Jack\"}"));

做个小结,JSON字符串就是符合JSON格式的字符串,他还是字符串,JSON对象就是JavaScript对象,我们推荐使用字面值的方式来创建一个JS对象。然后,JS对象和JSON字符串可以互相转换。通过这一个特点,我们能够实现JS对象的拷贝。一般来说,比如我有一个js对象。

var person={'name' : 'Jack'}
var person2=person;

这样做,并不是对象的复制,person2仅仅是一个指针,他和person一样,指向了{'name' : 'Jack'}这一片内存空间。当person发生改变,person2必然也跟着改变。

var person={'name' : 'Jack'}
var person2=person;
person.age=10; //给person动态地添加一个属性
alert(JSON.stringify(person2)); //person2也跟着变了

那有没有什么办法可以实现对象的复制呢?一个好的解决方案就是,先把person转换成JSON字符串,然后再转成JS对象,这个时候就是另外一个JS对象了。比如:

var person={'name' : 'Jack'}
var person2=JSON.parse(JSON.stringify(person));
person.age=10; //给person动态地添加一个属性
alert(JSON.stringify(person2)); //person2不变

接下来说说js对象内容的访问和操作,我们上面已经说了,JS对象中无非是一些键值对的集合,他更像是一个容器,既然是容器,自然有内容,我们如何访问其中的内容呢?在上面的例子中,我们已经通过“对象.属性名”的方式来访问JS对象的具体内容。比如:

var obj={
 id : 1
};
var id=obj.id;
alert(id);

另外一种方式,就是通过 对象["属性名"] 来操作其内容。比如:

var id=obj['id']

可以用双引号,也可以用单引号,看个人习惯了。在JS对象中,属性名永远都是字符串,虽然诸如这样的代码:

var obj={
 id : 1
};

id没有加上引号,但它实际上还是以字符串的形式被保存起来的。再说一遍,如果你要访问和操作JS对象的内容,有两种方式,第一种方式是用点,第二种方式则是用中括号。两种方式如果做一个比较,显然是第二种方式较为灵活,因为它是用字符串去找对应的键值对,而不是用一个标识符。比如刚才的例子,你这样写:

var id=obj.id;

我问你,obj.id中的id是什么?为了符合规范,id必须是标识符,你不能写 obj.123 吧。这显然是不合法,也无法运行通过的。比如,你能这样写吗?

var obj={
 123 : 'Hello JavaScript!'
};
var id=obj.123;
alert(id);

肯定不行,会报错的:

但是,如果你用中括号就可以:

var obj={
 123 : 'Hello JavaScript!'
};
var id=obj['123'];
alert(id);

具体用那种方式,随你喜好而定。

现在,我们已经对JSON格式和JS对象有了一个比较充分的了解,我要在此抛出一个问题,有没有什么办法能够获取JS对象的属性详情呢?注意我的用词,是属性详情,也就是说,比如有一个JS对象:

var obj={
 message: 'Hello JavaScript!'
};

message就是它的属性,关于这个属性,有没有什么详细的描述信息呢?答案是有的,在JS中,有一个内置的Object对象,它给我们提供了一个getOwnPropertyDescriptor方法,可以看到某个对象的某个属性的具体情况。你可以把这个理解为Java中的静态类调用方法。我们可以这样做:

var obj={
 message : 'Hello JavaScript!'
};

console.log(Object.getOwnPropertyDescriptor(obj,'message'));

可以看到,我们成功挖掘出了四个属性,如果你不明白我在说什么,我就说得更加直白一些,就是说,

var obj={
 message: 'Hello JavaScript!'
};

obj里面有一个属性message,而message又有四个描述性的东西,分别是configurable(可配置),enumerable(可枚举),value(值),还有 writable(可写入)。这四样东西,专业术语叫做属性描述符,或者数据描述符。目前我们看到的数据描述符都被赋予了默认值,我们也可以通过defineProperty方法对其进行个性化配置。

比如,我们把message设置为只读:

var obj={
 message : 'Hello JavaScript!'
};
console.log(Object.getOwnPropertyDescriptor(obj,'message'));
Object.defineProperty(obj,'message',{
 writable:false
});
obj.message='haha';
alert(obj.message);

不好意思,修改无效,因为我已经把这个属性设置为只读了。在严格模式下,甚至会报错,啥,你问我什么叫做严格模式?好吧,其实就是一句话的事。

这就是严格模式,你不要问为什么这样就行了,我不会告诉你,因为我也不懂。我只知道,这样写就可以,于是乎,接下来运行就报错了。

?

本文就介绍到这里,对JSON进行了一个简单的说明。至于深入的学习,还请各位自行去百度吧。

本专栏前篇文章中介绍了HttpBasic模式,该模式比较简单,只是进行了通过携带Http的Header进行简单的登录验证,而且没有可以定制的登录页面,所以使用场景比较窄。

对于一个完整的应用系统,与登录验证相关的页面都是高度定制化的,非常美观而且提供多种登录方式。这就需要Spring Security支持我们自己定制登录页面,也就是本文给大家介绍的FormLogin模式登录认证模式。

1. 新建项目

在介绍相关内容之前,需要先搭建一个demo,新建一个项目spring-security-02,需要添加依赖如下:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>
复制代码

除此之外其实还需要添加web、thymeleaf的依赖,这里就不在贴出来了

demo结构如下:

2. 新建登录页面

这里不再使用Security默认的页面,自己定制一个,代码如下:

单纯的一个表单登录页面,需要注意以下几个参数:

  1. action:security登录的url,可以自定义,下文介绍
  2. username:security登录的用户名,可以自定义,下文介绍
  3. password:security登录的密码,可以自定义,下文介绍

以上三个参数都可以在security通过配置的方式定义

3. 新建首页

这个是登录成功后跳转的首页,代码如下:

4. 新建接口

在security中一切的接口都称之为资源,下面新建两个测试接口,代码如下:

5. formLogin配置

在介绍如何配置之前,先来看下formLogin模式登录的5个要素:

  1. 登录认证逻辑-登录URL:这个URL在security中默认是/login且POST请求,但是也可以通过配置自定义
  2. 如何接收登录参数:用户名、密码默认接收的字段分别是username、password,同样也是可以通过配置自定义
  3. 登陆成功后逻辑:登录成功后的处理逻辑,比如跳转到指定的页面、返回特定的JSON数据,这个也是可以定制
  4. 资源访问控制规则:这个用于控制什么用户、什么角色可以访问什么资源,可以静态指定也可以从数据库中加载
  5. 用户具有角色权限:配置某个用户拥有什么角色、拥有什么权限,可以静态指定也可以从数据库中加载

一般来说,使用权限认证框架的的业务系统登录验证逻辑是固定的,而资源访问控制规则和用户信息是从数据库或其他存储介质灵活加载的。但本文所有的用户、资源、权限信息都是代码配置写死的,旨在为大家介绍formLogin认证模式,如何从数据库加载权限认证相关信息我还会结合RBAC权限模型再写文章的。

针对上述5个的要素,formLogin配置代码如下:

首先,我们要继承WebSecurityConfigurerAdapter ,重写configure(HttpSecurity http) 方法,该方法用来配置登录验证逻辑。请注意看代码中的注释信息。

上述代码分为两个部分:

第一部分是formLogin配置段,用于配置登录验证逻辑相关的信息。如:登录页面、登录成功页面、登录请求处理路径等。

  • .loginPage("/login/page"):指定的第2步定制的登录页面,需要写个mvc接口跳转到login.html,见源码
  • .loginProcessingUrl("/login"):指定处理登录的逻辑的url,这个接口不需要开发者定义,security中通过过滤器UsernamePasswordAuthenticationFilter处理,后文介绍
  • .usernameParameter("username"):指定用户名的接收参数的字段,默认是username,具体逻辑在UsernamePasswordAuthenticationFilter
  • .passwordParameter("password"):指定密码的接收参数的字段,默认是username,具体逻辑在UsernamePasswordAuthenticationFilter
  • .defaultSuccessUrl("/"):登录认证成功后默认转跳的路径,这里/则是跳转到/index.html,可以自定义
  • .failureUrl("/login/page"):登陆失败的跳转的路径

第二部分是authorizeRequests配置段,用于配置资源的访问控制规则

  • .antMatchers("/login/page","/login").permitAll():配置登录页面、登录接口直接放行,不需要拦截登录
  • .antMatchers("/","/hello1").hasAnyAuthority("ROLE_user","ROLE_admin"):设置/hello1、/这两个资源需要user和admin的角色才可以访问
  • .antMatchers("/hello2").hasAnyRole("admin"):配置/hello2这个资源需要admin的角色才可以访问
  • .anyRequest().authenticated():除了上面的配置的规则,访问其他的资源都需要登录认证通过才可以访问

6. 用户、角色配置

在上述的规则中配置了一些资源需要特定的角色才可以访问,比如user、admin,那么这些角色如何去指定呢?

在security中提供了配置的方式,代码如下:

上述的代码配置很简单,创建了两个用户且指定了角色,分别如下:

  • user:密码123456,赋予的角色为user
  • admin:密码123456,赋予的角色为user、admin

配置解释如下:

  • .inMemoryAuthentication():指的是在内存里面存储用户的身份认证和授权信息;这里还可以配置从数据库中动态加载,后文介绍
  • withUser("user"):用户名是user
  • password(passwordEncoder().encode("123456")):密码是加密之后的123456
  • roles():方法用于指定用户的角色,一个用户可以有多个角色
  • passwordEncoder(passwordEncoder()):指定密码的加密方式,使用的是BCryptPasswordEncoder,后文介绍

7. 简单测试

按照上述6个步骤基本实现了一个表单登录,下面测试一下

浏览器访问http://localhost:8081/hello2,第一次访问由于未登录会自动跳转到登录页面,如下图:

输入用户名和密码,由于/hello2这个资源需要admin的角色才能访问,因此必须用admin这个用户登录,否则将会报403的错误,登录成功后将能够正常访问

如果用户名或者密码错误将会触发.failureUrl("/login/page")这个配置,自动跳转到登录页面

8. 自定义登录结果

在第5步的配置中,和登录结果相关的配置有如下两个:

  • .defaultSuccessUrl("/"):登录认证成功后默认转跳的路径,这里/则是跳转到/index.html,可以自定义
  • .failureUrl("/login/page"):登陆失败的跳转的路径

这两个配置都是指定URL的方式:

  • 当我们登录成功的时候,是由AuthenticationSuccessHandler进行登录结果处理,默认跳转到defaultSuccessUrl配置的路径对应的资源页面(一般是首页index.html)。
  • 当我们登录失败的时候,是由AuthenticationfailureHandler进行登录结果处理,默认跳转到failureUrl配置的路径对应的资源页面(一般也是跳转登录页login.html,重新登录)。

但是在web应用开发过程中需求是千变万化的,有时需要我们针对登录结果做个性化处理,比如:

  • 我们希望不同的人登陆之后,看到不同的首页(及向不同的路径跳转)
  • 我们应用是前后端分离的,验证响应结果是JSON格式数据,而不是页面跳转
  • …… 其他未尽的例子

因此需要自定义的登录结果,这篇文章先介绍如何定制跳转页面,关于JSON格式数据就是前后端分离架构下需要用到,后文介绍

8.1 自定义登录成功结果

AuthenticationSuccessHandler接口是Security提供的认证成功处理器接口,我们只需要去实现它即可。但是通常来说,我们不会直接去实现AuthenticationSuccessHandler接口,而是继承SavedRequestAwareAuthenticationSuccessHandler 类,这个类会记住用户上一次请求的资源路径,比如/hello2这个路径,登录成功后将会自动跳转到/hello2这个页面而不是首页

代码如下:

8.2 自定义登录失败结果

这里我们同样没有直接实现AuthenticationFailureHandler接口,而是继承SimpleUrlAuthenticationFailureHandler 类。该类中默认实现了登录验证失败的跳转逻辑,即登陆失败之后回到登录页面。我们可以利用这一点简化我们的代码。

代码如下:

8.3 SecurityConfig中配置

配置如下:

将自定义的AuthenticationSuccessHandler和AuthenticationFailureHandler注入到Spring Security配置类中

使用formlogin模式,配置successHandler和failureHandler。

不要配置defaultSuccessUrl和failureUrl,否则自定义handler将失效。handler配置与URL配置只能二选一

总结

本篇文章介绍了Spring Security 的 formLogin的配置方式,需要注意的是这里不支持前后端分离架构,关于前后端分离架构如何整合,后文会介绍

来源:https://juejin.cn/post/7140096326829621261