tornado学习笔记day06-应用安全

应用安全

cookie

普通cookie

一般我们的用户表中都有啥呢

你在购物的时候,加入购物车,让你登录,那你登录之后,他怎么知道你登录了呢

token 这个值是随机的,存在cookie里面

设置
  • 原型: 设置cookie 的方法
def set_cookie(self,name: str,value: Union[str, bytes],domain: str = None,expires: Union[float, Tuple, datetime.datetime] = None,path: str = "/",expires_days: int = None,**kwargs: Any) -> None:
  • 参数
    • name:我们设置的cookie的名字
    • value:cookie的值
    • domain:提交cookie时匹配的域名,也就是哪个ip拿过来的
    • path:提交cookie时匹配的路径
    • expires:设置cookie的有效期,可以是时间磋整数,时间元组,datetime类型,为UTC时间
    • expires_days:设置cookie的有效期(天数)但是他的优先级低于expires
  • 实例:
原理

设置cookie实际上是通过设置headerSet-Cookie来实现的

获取

我们设置完了就要获取,为什么要获取呢,因为以后我们在发送请求就要携带着这个cookie了

  • 原型:
def get_cookie(self, name: str, default: str = None) -> Optional[str]:
  • 参数:
    • name:要获取的cookie 的名称
    • default:如果名为name的获取的cookie不存在,就走这个默认值了
  • 示例:
cookie = self.get_cookie("suck","未登录")
print(cookie)
self.write("getcookie page info tornado!")
self.write(cookie)
清除
  • 原型:
    def clear_cookie(self, name: str, path: str = "/", domain: str = None) -> None:
  • 作用:
    • 删除名为namecookie,并同时匹配domainpathcookie
    • 哟时候有可能这个name相同啊,那这个时候就得用path和domain来匹配一下了
  • 注意:
    • 执行cookie删除后,并不是直接就删除了,
    • 而是给cookie设置内容为null空,并删除延迟时间,
    • 真正删除是浏览器自己清理的,浏览器发现他没有用的话就给他删掉了,就看浏览器的了
  • 还有一个原型:
def clear_all_cookies(self, path: str = "/", domain: str = None) -> None:
  • 作用:删除同时匹配path和domain的所有cookie
  • 示例:
# 清除cookie
class ClearPCookieHandler(RequestHandler):def get(self):# 这个删除cookie后,他这次不能决定# 是下一次删除了# 清除一个cookieself.clear_cookie("suck")# 清除所有cookieself.clear_all_cookies()self.write("ClearPCookieHandler page info tornado!")
安全cookie
概述:

cookie是存储在客户端浏览器的数据,很容易被篡改,不是很安全

tornado提供了一种对于cookie简易的加密的方法,来防止cookie被恶意篡改

设置:
  • 需要为应用配置的进行混淆加密的秘钥
  • 生成一个秘钥:
    我们需要用到一个库base64,你需要知道的就是他是加密的就行了,是一个唯一的标识,其他的你就不用管了,因为其他的你想管你也管不着了
    md5 现在已经不安全了,现在有一些网站已经能够在线破解md5了

我们在python控制台输入如下代码,即可生成一个uuid

base64.b64encode(uuid.uuid4().bytes+uuid.uuid4().bytes)
b'j94bbx0zSY6yYkCgawwJ1bzyM4jzDUuKtLyPC/MMmZA='

我们需要在配置文件config.py中进行配置
内容如下

settings = {# 这写key的名字可不是随便起的奥,是写好的,# 就像upfile就没有,你写了也白扯'template_path': os.path.join(BASE_DIR, "templates"),'static_path': os.path.join(BASE_DIR, "static"),"debug": True,# "autoreload" : True"cookie_secret":"j94bbx0zSY6yYkCgawwJ1bzyM4jzDUuKtLyPC/MMmZA=",# 这个据说一百亿年才能,用完
}

然后给你看一个方法,他的原型如下

    def set_secure_cookie(self,name: str,value: Union[str, bytes],expires_days: int = 30,version: int = None,**kwargs: Any) -> None:

作用:设置一个带有签名和时间戳的cookie,防止cookie被伪造

实际上是这样写的

# 安全cookie
class SCookieHandler(RequestHandler):def get(self):self.set_secure_cookie("victor","nice")self.write("SCookieHandler page info tornado!")

然后我们在浏览器中查看一下cookie的值

Set-Cookie: victor="2|1:0|10:1575853257|6:victor|8:bmljZQ
==|ac4c19c8598fc7c7d07c1484f07a0cc8e1a1adb022d084bcc3d3d4
75129fab63"; expires=Wed, 08 Jan 2020 01:00:57 GMT; Path=/

现在这个值,你看不出来内容是啥了
说明:

  • 安全cookie版本,默认使用2
  • 默认为0
  • 时间戳
  • cookie名
  • base64cookie编码的值
  • 签名值(不带长度说明)
获取:

直接就把原型往这里一扔

    def get_secure_cookie(self,name: str,value: str = None,max_age_days: int = 31,min_version: int = None,) -> Optional[bytes]:

然后我这么写

# 获取安全cookie
class GetSCookieHandler(RequestHandler):def get(self):sc = self.get_secure_cookie("victor")print(sc)self.write("getSCookieHandler page info tornado!")self.write(sc)

如果cookie验证通过,就会返回这个cookie的值,否则返回None

max_age_days不同于expries_days设置的浏览器中cookie的有效时间
而max_age_days是过滤安全cookie的时间戳,默认是31天

注意:
  • 这个安全cookie,也不是绝对安全的,只是一定程度上增加了破解的难度
  • 以后cookie中不要存储一些敏感性的数据

XSRF

跨站请求伪造代码

示例:


# cookie计数
class CookieNumHandler(RequestHandler):def get(self):count = self.get_cookie("count",None)if count:# 第n次访问count = str(int(count)+1)passelse:# 第一次访问# 设置cookiecount = '0'self.set_cookie("count",count)self.render("cookienum.html",count = count)

页面是这个样子的:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>搞事情</title>
</head>
<body>
<h1>第{ { count } }次访问</h1>
<p>搞事情网站,我就看看能不能搞坏!!!</p>
<div><img src="http://127.0.0.1:8080/cookienum" alt="">
</div>
</body>
</html>

当我们访问搞事情网站的时候,当我们不知情的时候,未授权的情况下,cookie计数器网站中的cookie被使用,导致cookie计数器网站认为是他自己调用了Handler逻辑

上一个程序使用的是GET方式模拟的攻击,为了防止这种攻击,一般对于相对安全的操作一般不放在GET请求中.

我们常常使用的是POST请求

XSRF保护

同源:同域名同协议同端口
只有同源才让他能访问

开启保护

在配置中添加

"xsrf_cookies":True

然后在这里别忘了也整一下

super(Application, self).__init__(handlers,# 模板路径template_path=settings["template_path"],# 静态路径static_path=settings["static_path"],# cookie签名cookie_secret=settings["cookie_secret"],xsrf_cookies=settings["xsrf_cookies"],
)

现在保护一开,谁都别想访问了
那我们还要干什么?
我们怎么才能让我们自身的能够进行访问啊

附加

配置文件中的键和值不用一个一个写,直接就**打散就OK了

# 是不是傻了,这里直接就** 就行了
super(Application, self).__init__(handlers,**settings
)

应用

在模板中应用
在form表单中加上这么一句话,模板里面就能用了
{ % module xsrf_form_html() % }
完整的就这样了
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Post page</title>
</head>
<body><form action="/postfile" method="post">{ % module xsrf_form_html() % }姓名:<input type="text" name="username"><hr/>密码:<input type="password" name="password"><hr/><input type="submit" value="登录"></form>
</body>
</html>
作用:
  • 为浏览器设置了_xsrf的安全cookie,这个cookie在关闭浏览器后就失效了
  • 为模板添加了一个隐藏的输出,隐藏域,名为_xsrf,值为_xsrf这个cookie的值
  • 表单中的一个隐藏域,名为…值为…,但是还是不绝对安全
在非模板中应用
手动设置_xsrf的cookie
'''设置XsrfCookieHandler'''
class SetXsrfCookieHandler(RequestHandler):def get(self):# 设置一个_xsrf的cookieself.xsrf_tokenself.finish("OK")
第一种
  • 我们可以不在页面中写那一行代码 而是写一个script的JS代码,其中实现手动的添加一个隐藏input
  • 手动创建input,并设置name的属性值为_xsrf,value的属性值为名为_xsrf的cookie的值
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Post page</title>
</head>
<body><form action="/postfile" method="post"><input id="hi" type="hidden" name="_xsrf" value="">姓名:<input type="text" name="username"><hr/>密码:<input type="password" name="password"><hr/><input type="submit" value="登录"></form><script>function getCookie(name) {// 这里写的就不用管他是啥意思了var cook = document.cookie.match("\\b" + name + "=([^;]*)\\b");// 我们要返回的是cookie,不是cook// 如果有,那我就拿他的第一个值,就是这个cookie值return cook ? cook[1] : undefined}// 获取cookiegc = getCookie("_xsrf");console.log(gc);document.getElementById("hi").value = gc;</script>
</body></html>
第二种

发起ajax请求:
我们把这个表单去掉,然后在js中点击这个按钮就发起ajax请求
我们没有学过原生的ajax吧!那么就需要借助jQuery了吧!

具体代码如下:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Post page</title><!--    导入jQuery--><script type="text/javascript" charset="utf-8" src="{{static_url('js/jquery.min.js')}}"></script>
</head>
<body><input id="hi" type="hidden" name="_xsrf" value="">
姓名:<input type="text" name="username">
<hr/>
密码:<input type="password" name="password">
<hr/>
<input type="submit" value="登录">
<button id="btn" onclick="login()">登录</button>
<script>function getCookie(name) {// 这里写的就不用管他是啥意思了var cook = document.cookie.match("\\b" + name + "=([^;]*)\\b");// 我们要返回的是cookie,不是cook// 如果有,那我就拿他的第一个值,就是这个cookie值return cook ? cook[1] : undefined}function login() {// 发起ajax请求我们有很多种方式// 发起post请求我们有两种方式,一种是这个$.post("/postfile","_xsrf=" + getCookie("_xsrf") + "&username=" + "victor" + "&passwd=" + "123456",// 请求成功后的回调函数function (data) {alert("ok\\(^o^)/~");alert(data);});// TODO 其实这里要通过DOM元素获取username,和passwd的值的,// TODO 因为这里不是作为重点就写了/*** 这里主要是要带着这个xsrf防护,不然这个是不能给你响应的*/}
</script>
</body></html>
第三种

其实这种才是最常用的方式,这个$.post仅仅只能发起post请求,所携带的参数或者说对于他的控制是比较少的
$.ajax才是最NB的方式

来吧!直接就上代码

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Post page</title><!--    导入jQuery--><script type="text/javascript" charset="utf-8" src="{{static_url('js/jquery.min.js')}}"></script>
</head>
<body><input id="hi" type="hidden" name="_xsrf" value="">
姓名:<input type="text" name="username">
<hr/>
密码:<input type="password" name="password">
<hr/>
<input type="submit" value="登录">
<button id="btn" onclick="login()">登录</button>
<script>function getCookie(name) {// 这里写的就不用管他是啥意思了var cook = document.cookie.match("\\b" + name + "=([^;]*)\\b");// 我们要返回的是cookie,不是cook// 如果有,那我就拿他的第一个值,就是这个cookie值return cook ? cook[1] : undefined}function login() {// TODO 这块还是那么回事,数据可以通过DOM来动态获取data = {"username": "victor","passwd": "123456",};// 我们需要把我们的数据给他变成一个字符串// 这个方法是JS中的东西,不是python,jQuery的var data_str = JSON.stringify(data);// 这个ajax怎么写呢?,他里面就一个参数,就是一个字典$.ajax({url: "/postfile",// 这个请求方式你写啥,他就是啥,他都能发method: "POST",data: data_str,// 你看吧这个字典中的一个键里面对应的值是函数,// 虽然外面就一个字典类型的参数,但是回调函数还是有的success: function () {alert("ok!!!")},// 到这里绝壁是不能成功的,因为我这个_xsrf都没写怎么能成功呢// 如果想发起请求,我们还需要添加一个headers,也就是请求头headers:{// 来这个跟着写就对了// 这个值就设置成之前那个cookie值就行了"X-XSRFToken": getCookie("_xsrf"),},})// 以后发起ajax请求,建议使用这个,功能更加强大}
</script>
</body></html>
问题

其实帅的人是不会用第一种方式写的

需要手动设置token
解决:其实一般我们进入一个网站的时候,通常是不是都先进入主页啊,你进入主页的时候就把这个xsrf写进去了

首先我们添加一个进入主页的静态默认页面

(r"/(.*)$",# 系统这个我们不能用,所以我们继承,# tornado.web.StaticFileHandler,# 让我们自己写这个继承自系统给我们这个index.MyStaticFileHandler,{"path": os.path.join(BASE_DIR, "static/html"),"default_filename": "index.html"}
),

然后重写StaticFileHandler

from tornado.web import StaticFileHandler# 设置静态默认
class MyStaticFileHandler(StaticFileHandler):# 我们用他的时候只需要重写他的init就可以了def __init__(self,*args,**kwargs):super(MyStaticFileHandler,self).__init__(*args,**kwargs)self.xsrf_token
补充
  • _xsrf 加一个下划线,我们把他看成是私有的
  • __xsrf就真的是私有的了

用户验证

概念

在收到用户请求后,进行一个预先判断用户的认证状态(是否登录),若验证通过正常处理,否则进入登录界面去,滚去登录

tornado.web.authenticate 装饰器

tornado将确保这个方法的主体(他修饰的东西)只有合法的用户才能调用

get_current_user

我们自己定义一个方法get_current_user()
功能:验证用户的逻辑,应该写在该方法中,
如果该方法返回的为True说明验证成功,否则验证失败
验证失败:访客重定向到配置中的所制定的路由(登录界面)
需要在配置文件config.py中写上配置的位置,因为你不一定要跳转到登录界面吧!

"login_url":"/login",

这样你要是没登录,他就给你踢到那个页面中了

然后直接就上代码

'''首先 路由是这样的 '''
# 登录界面
tornado.web.url(r"/login", index.LoginHandler,name='login'),
# home页面
(r"/home", index.HomeHandler),
# 一个无名的普通页面,用于测试的
(r"/cart", index.CartHandler),(r"/(.*)$",# 系统这个我们不能用,所以我们继承,# tornado.web.StaticFileHandler,# 让我们自己写这个继承自系统给我们这个index.MyStaticFileHandler,{"path": os.path.join(BASE_DIR, "static/html"),"default_filename": "index.html"}
),
'''登录页面'''
class LoginHandler(RequestHandler):def get(self):# 进入登录我就要知道我是从哪里进的了next = self.get_argument("next","/")url = "login" + "?next=" + nextself.render("login.html", url=url)def post(self):'''咱也不用管是get还是post了name = self.get_body_argument("username")passwd = self.get_body_argument("passwd")'''# 直接我就写name = self.get_argument("username")passwd = self.get_argument("passwd")# TODO 这里还是把账号和密码写死了if name == "victor" and passwd == "123456":# 这个玩意代表从哪个页面跳来的,处理完了# 要是没毛病,我还得给你发到那里去next = self.get_argument("next", "/")# 后面"/"是默认值# 重定向走你,但是光重定向不行# 我们还得加一个标记self.redirect(next + "?flag=logined")else:next = self.get_argument("next", "/")'''# 验证失败了,# 路由的反向解析,听着挺高大上的# 密码输入错误,好几次也得记着人家是从哪里过来的'''log = self.reverse_url("login")+"?next="+next# 我还是得重定向self.redirect(log)
''' 这个是主页面 '''
class HomeHandler(RequestHandler):def get_current_user(self):'''# 返回True代表验证成功,否则凉凉# return False# 然后我们这里就能通过之前的flag来判断了# 这里还要设置一个默认值否则就是# WARNING:tornado.general:400 GET /home (127.0.0.1):Missing argument flag'''flag = self.get_argument("flag", None)# 如果能取到flag,就返回true# 要是娶不到,自然就返回Falsereturn flag@tornado.web.authenticateddef get(self):self.render("home.html")
''' 这个cart是一个普通的页面'''
class CartHandler(RequestHandler):def get_current_user(self):return self.get_argument("flag", None)@tornado.web.authenticateddef get(self):self.render("cart.html")

相似文章

tornado学习笔记day01
tornado学习笔记day02
tornado学习笔记day03
tornado学习笔记day04
tornado学习笔记day05
tornado学习笔记day06
tornado学习笔记day07
tornado学习笔记day08

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/455232.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

托福试卷真题_干货解答考生疑惑,自考真题考过了还会在出吗?

重视真题&#xff01;重视真题&#xff01;重视真题&#xff01;重要的话要说三遍。想自考的你们一定要注意&#xff0c;对于历年真题&#xff0c;从来都是“备考必做”的态度。做自考真题&#xff0c;除了可以让自己尽快熟悉考试题型和考点外&#xff0c;还有什么好处呢&#…

x264 struct学习 1

x264_t 结构体维护着CODEC的诸多重要信息 其中成员frames是一个指示和控制帧编码过程的结构。其中current是已经准备就绪可以编码的帧&#xff0c;其类型已经确定&#xff1b;next是尚未确定类型的帧&#xff1b;unused用于回收不使用的frame结构体以备今后再次使用。 struct …

2016 ACM/ICPC Asia Regional Dalian Online

自己还是太菜&#xff0c;补题离不开题解。。。 但还是留个博客&#xff0c;万一以后忘了。。。 1001 Different Circle Permutation Polya定理&#xff0c;第一次遇见&#xff0c;学习了一下。不旋转的时候可以得到 f[i]f[i-1]f[i-2] 斐波那契数列&#xff0c;旋转后就可以通过…

tornado学习笔记day07-同步与异步

同步 概念 同步就是按部就班的依次执行我们的代码 进阶 但是有些情况我们有一些比较耗时的从操作,比如去别的地方拿点资源,去其他网站请求数据,去访问数据库,上传文件等等,所以这里面优点瑕疵,有小编一一道来 比如这样 本模块的功能:<同步异步demo># 这个就相等于一个…

关键字: on

关键字: on 数据库在通过连接两张或多张表来返回记录时&#xff0c;都会生成一张中间的临时表&#xff0c;然后再将这张临时表返回给用户。 在使用left jion时&#xff0c;on和where条件的区别如下&#xff1a; 1、 on条件是在生成临时表时使用的条件&#xff0c;它不管on中的条…

天融信安全接入客户端_天融信提示您警惕物联网设备Ripple20漏洞风险

近日&#xff0c;天融信阿尔法实验室在JSOF实验室发布的由Treck公司开发的TCP/IP软件库中获取到一系列0day漏洞。JSOF实验室发布的这批漏洞共计19个&#xff0c;被JSOF研究人员称为"Ripple20"。受此软件库影响的产品数量估计超过数亿&#xff0c;其中包括智能家居设备…

Service-Oriented Architecture,SOA(转)

http://blog.csdn.net/WOOSHN/article/details/8036910 介绍&#xff1a; IT体系结构已非常成熟&#xff0c;它是一种成功处理典型IT问题的方法。体系结构中一个受到很大重视且相对较新的分支是面向服务的体系结构(SOA)。SOA经常被吹捧为企业用于解决应用程序灵活性和高维护成本…

tornado学习笔记day08-tornado中的异步

概述 应为epoll主要用来解决网络的并发问题,所以tornado中的异步也是主要体现在网络的IO异步上,即异步web请求 tornado.httpclient.AsyncHTTPClient tornado提供异步web请求客户端,可以用来进行异步web请求, 这个客户端和服务端是相对来说的,当tornado的Handler去其他位置去…

GreenSock (TweenMax) 动画案例(二)

实现效果 动画分解 1.灯光闪烁2.文字出现3.水流4.心电图 知识点 1.AI(可尽情骚扰UI欧巴)2.SVG(了解基本的知识点)3.TweenMax(GreenSock)4.CSS animation 写在前面 写过第一篇文章后GreenSock (TweenMax) 动画案例(一)再回头看发现代码太多&#xff0c;根本没耐心去看完。所以每…

vue 用key拿对象value_利用 WeakMap 对 Vue 新建数组中的对象赋予 :key

需求在 Vue 中&#xff0c;对组件进行循环都需要加入key以便“就地复用”&#xff0c;可是在某些情况下&#xff0c;我们需要新建多个对象&#xff0c;而这些对象不是从后端获取到的&#xff0c;而是前端生成的&#xff0c;没有唯一值&#xff0c;且 Vue 目前版本只允许字符串&…

无限轮播图片的实现原理

无限轮播图相信是很多开发人员常用的一个功能&#xff0c;这里总结一下常用的两种方式的实现原理 一、使用UIScrollview实现无限轮播用UIScrollView实现&#xff0c;在scrollView上添加3个UIImageView&#xff0c;分别用来显示上一张图片&#xff0c;当前显示的图片&#xff0c…

开启 JM 的 trace 功能

[JM代码] 开启 JM 的 trace 功能本帖最后由 firstime 于 2009-6-15 11:16 AM 编辑 城里汉子说过&#xff1a; trace文件对分析码流结构很有效。我说的是trace文件&#xff0c;不是一步一步跟踪&#xff0c;就是编解码同时生成的 trace_enc.txt 这个文件&#xff0c;里面对每个比…

kafka入门介绍(转载)

Kafka作为一个分布式的流平台&#xff0c;这到底意味着什么&#xff1f; 我们认为&#xff0c;一个流处理平台具有三个关键能力&#xff1a; 发布和订阅消息&#xff08;流&#xff09;&#xff0c;在这方面&#xff0c;它类似于一个消息队列或企业消息系统。 以容错的方式存储…

Cmd Markdown 编辑阅读器

欢迎使用 Cmd Markdown 编辑阅读器 我们理解您需要更便捷更高效的工具记录思想&#xff0c;整理笔记、知识&#xff0c;并将其中承载的价值传播给他人&#xff0c;Cmd Markdown 是我们给出的答案 —— 我们为记录思想和分享知识提供更专业的工具。 您可以使用 Cmd Markdown&…

关于在smarty中实现省市区三级联动

刚开始接触php&#xff0c;&#xff0c;其实对于一些比较深入的东西还不是很了解&#xff0c;就像是这次的省市区联动&#xff0c;都是用三张表为基础编码的&#xff0c;原谅我的无知&#xff0c;谢谢。 接下来就是编码部分了&#xff1a; <?php require(./smarty/Smarty.c…

Ubuntu GitLab CI Docker ASP.NET Core 2.0 自动化发布和部署(1)

相关博文&#xff1a; Ubuntu 简单安装和配置 GitLabUbuntu 简单安装 DockerUbuntu Docker 简单安装 GitLabUbuntu Docker 安装和配置 GitLab CI 持续集成服务器版本 Ubuntu 16.04 LTS。 经过上面四篇博文中的相关安装和配置&#xff0c;我们主要完成了两个容器的创建和运行&am…

X264学习笔记(1)

X264学习笔记&#xff08;1&#xff09; X264编码流程 参数的初始化 1.opt&#xff0c;param根据输入的参数和标准的规定&#xff0c;进行初始化设置。 Opt的说明如下&#xff1a; Opt->hin用于给出读入的yuv文件的指针地址 Opt->hout给出了输出的文件的指针地址 Opt->…

python 数字转化excel行列_Python实现excel的列名称转数字、26进制(A-Z)与10进制互相转换...

Python实现excel的列名称转数字、26进制(A-Z)与10进制互相转换sequence list( map( lambda x: chr( x ), range( ord( A ), ord( Z ) 1 ) ) )##-----字母转数字(python实现 1-26A-Z, then AA-AZ)def ten2TwentySix(num):L []numnum-1; #实现从1对应Aif num > 25:while Tr…

错误提示:'……' is not assignable to Android.app.Activity Manifest XML

1 问题描述&#xff1a; 针对这段代码&#xff1a; <activity android:name".fragament.fragment_bulter" /> <activity android:name".fragament.fragment_girl" /> <activity android:name".fragament.fragment_user" />…

关于Lambda和匿名内部类

先上代码&#xff1a; //gcache(f)public <T,R> Function<T,R> cache(Function<T,R> f){final Map<T,R> cachenew HashMap<>();Function<T,R> gt->{if(cache.containsKey(t)){System.out.println("cached t:"t);return cache…