一、什么是单点登录单点登录的英文名叫做:Single Sign On(简称SSO),指在同一帐号平台下的多个应用系统中,用户只需登录一次,即可访问所有相互信任的系统。简而言之,多个系统,统一登陆。为什么需要做单点登录系统呢?在一些互联网公司中,公司旗下可能会有多个子系统,每个登陆实现统一管理,多个账户信息统一管理 SSO单点登陆认证授权系统。比如阿里系的淘宝和天猫,显而易见这是两个系统,但是在使用过程中,只要你登录了淘宝,同时也意味着登录了天猫,如果每个子系统都需要登录认证,用户早就疯了,所以我们要解决的问题就是,用户只需要登录一次就可以访问所有相互信任的应用系统。二、单点登录原理sso需要一个独立的认证中心,所有子系统都通过认证中心的登录入口进行登录,登录时带上自己的地址,子系统只接受认证中心的授权,授权通过令牌(token)实现,sso认证中心验证用户的用户名密码正确,创建全局会话和token,token作为参数发送给各个子系统,子系统拿到token,即得到了授权,可以借此创建局部会话,局部会话登录方式与单系统的登录方式相同。 三、单点登录实现方式单点登录的实现方案,一般就包含:Cookies,Session同步,分布式Session,目前的大型网站都是采用分布式Session的方式。1.基于Cookie+Redis的单点登录最简单的单点登录实现方式,用cookie作为媒介存放用户凭证。 用户登录系统之后,会返回一个加密的cookie,当用户访问子应用的时候会带上这个cookie,授权以解密cookie并进行校验,校验通过后即可登录当前用户。redis:在key:生成唯一随机值(ip、用户id等等),在value:用户数据cookie:把redis里面生成key值放到cookie里面 下面对上图简要描述 用户访问系统1的受保护资源,系统1发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数 sso认证中心发现用户未登录,将用户引导至登录页面 用户输入用户名密码提交登录申请 sso认证中心校验用户信息,创建用户与sso认证中心之间的会话,称为全局会话,同时创建授权令牌 sso认证中心带着令牌跳转会最初的请求地址(系统1) 系统1拿到令牌,去sso认证中心校验令牌是否有效 sso认证中心校验令牌,返回有效,注册系统1 系统1使用该令牌创建与用户的会话,称为局部会话,返回受保护资源 用户访问系统2的受保护资源 系统2发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数 sso认证中心发现用户已登录,跳转回系统2的地址,并附上令牌 系统2拿到令牌,去sso认证中心校验令牌是否有效 sso认证中心校验令牌,返回有效,注册系统2 系统2使用该令牌创建与用户的局部会话,返回受保护资源 用户登录成功之后,会与sso认证中心及各个子系统建立会话,用户与sso认证中心建立的会话称为全局会话,用户与各个子系统建立的会话称为局部会话,局部会话建立之后,用户访问子系统受保护资源将不再通过sso认证中心,全局会话与局部会话有如下约束关系 局部会话存在,全局会话一定存在 全局会话存在,局部会话不一定存在 全局会话销毁,局部会话必须销毁Cookie的作用在于充当一个信息载体在Server端和Browser端进行信息传递,而Cookie一般是以域名为分割的,例如a.xxx.com与b.xxx.com的Cookie是不能互相访问的,但是子域名是可以访问上级域名的Cookie的。 即a.xxx.com和b.xxx.com是可以访问xxx.com下的Cookie的,于是就能将顶级域名的Cookie作为OpenId的载体。 验证步骤和上第二个方法非常相似: 在提供验证服务的站点里登录; 将OpenId写入顶级域名Cookie里; 访问子系统(Cookie里带有OpenId) 子系统取出OpenId通过并向验证服务发送OpenId 返回用户认证信息 返回授权后的内容但使用该方式把信任存储在客户端的Cookie中会有两个问题: Cookie不安全 不能跨域实现免登对于第一个问题,通过加密Cookie可以保证安全性,当然这是在源代码不泄露的前提下。如果Cookie的加密算法泄露,攻击者可以通过伪造Cookie伪造成特定用户身份。 对于问题二更是硬伤,所以才有了以下的分布式session方案。2.分布式session方式实现单点登录 流程运行:(1) 用户第一次登录时,将会话信息(用户Id和用户信息),比如以用户Id为Key,写入分布式Session;(2) 用户再次登录时,获取分布式Session,是否有会话信息,如果没有则调到登录页;(3) 一般采用Cache中间件实现,建议使用Redis,因此它有持久化功能,方便分布式Session宕机后,可以从持久化存储中加载会话信息;(4) 存入会话时,可以设置会话保持的时间,比如15分钟,超过后自动超时;结合Cache中间件,实现的分布式Session,可以很好的模拟Session会话。3.通过页面重定向的方式 最后一种介绍的方式,是通过父应用和子应用来回重定向中进行通信,实现信息的安全传递。 父应用提供一个GET方式的登录接口,用户通过子应用重定向连接的方式访问这个接口,如果用户还没有登录,则返回一个的登录页面,用户输入账号密码进行登录。如果用户已经登录了,则生成加密的Token,并且重定向到子应用提供的验证Token的接口,通过解密和校验之后,子应用登录当前用户。这种方式较前面两种方式,接解决了上面两种方法暴露出来的安全性问题和跨域的问题,但是并没有前面两种方式方便。 安全与方便,本来就是一对矛盾。4.使用token实现1.在项目某个模块进行登录,登录之后,按照规则生成字符串,把登陆之后用户包含到生成字符串里面,把字符串返回(1)可以把字符串通过cookie返回(2)把字符串通过地址栏返回2.再去访问项目其他模块,每次访问在地址栏带着生成的字符串,在访问模块里面获取地址字符串,根据字符串获取用户信息。如果可以获取到就能登录。实现方式如下图:
day54 模板语法之过滤器(类似于内置函数) 使用的时候可以参考源码
1.语法结构
{{ 数据对象|过滤器名称:参数 }} 过滤器最多只能额外传输一个参数
代码演示:
url(r'^index/', views.index), def index(request): s1 = 'hello jason 周一综合征可不能犯啊' return render(request, 'index.html', locals()) # 返回数据的长度 无法统计的话 会返回0 <p>{{ s1|length }}</p> 2.常见过滤器(django模板语法提供了60+过滤器 我们了解几个即可) <p>统计数据的长度:{{ s1|length }}</p> <p>算术加法或者字符串加法:{{ n1|add:111 }}、{{ s1|add:'big baby' }}</p> <p>将数字转成合适的文件计量单位:{{ file_size|filesizeformat }}、{{ file_size1|filesizeformat }}</p> # 返回KB\MB计量单位 <p>判断当前数据对象对应的布尔值是否是False:{{ b|default:'前面的值对应的布尔值是False' }}、{{ s1|default:'前面的值对应的布尔值是False' }}</p> <p>时间格式化:{{ ctime|date:'Y-m-d' }}</p> <p>索引切片:{{ s1|slice:'0:8' }}</p> <p>按照空格截取指定个数的文本:{{ s2|truncatewords:5 }}、{{ s1|truncatewords:1 }}</p> # 数字5表示截取5次 文本后面用...结尾 <p>按照字符个数截取文本(包含三个点):{{ s2|truncatechars:5 }}、{{ s1|truncatechars:10 }}</p> # 数字5表示截取2个字符加上.
Sql-Labs全攻略(持续更新) 平台搭建 使用PHPStudy在本地搭建平台 之前因为工作原因,操作不是很方便,所以就只是在服务器端搭建了一个平台,然后就遇上了各种各样的bug和要迈过去的坎,也不能说浪费了很多时间,至少这些时间和精力也是技术成长的一部分嘛hhh,但是毕竟我现在重点在旧活新整,所以图个省事,现在也没有那么多阻碍了。
到网上看了一眼才发现现在有人整了个很不错的软件出来了,一键搭建,很帅。
我之前在学校里一直用的是xampp,现在这个软件既然这么方便所以直接用上了。
Github Sql-Labs下载地址:https://github.com/Audi-1/sqli-labs
解决方案 SQLi-LABS Page-1(Basic Challenges) Less-1 GET - Error based - Single quotes - String(基于错误的GET单引号字符型注入) 先试验一下
报错信息提示是在1之后,说明可以用字符型闭合来注入。
测试到4的时候开始报错:
说明表单总共有三列的数据。
注:--+可以注释掉后面的闭合,我忘了和#有什么区别来着了……先留个坑。
前段时间考试太多,忙活两年,基本功忘差不多了……
这里使用联合查询
TIP:
所以这里需要改一下:
既需要遍历索引同时需要遍历元素的时候,可以考虑使用enumerate函数,enumerate函数接受一个可遍历的对象,如列表、字符串
s = string.ascii_lowercase e = enumerate(s) print(s) print(list(e)) abcdefghijklmnopqrstuvwxyz [(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd'), (4, 'e'), (5, 'f'), (6, 'g'), (7, 'h'), (8, 'i'), (9, 'j'), (10, 'k'), (11, 'l'), (12, 'm'), (13, 'n'), (14, 'o'), (15, 'p'), (16, 'q'), (17, 'r'), (18, 's'), (19, 't'), (20, 'u'), (21, 'v'), (22, 'w'), (23, 'x'), (24, 'y'), (25, 'z')]
里氏代换原则
里氏替换原则LSP讲的是基类和子类的关系。只有当这种关系存在时,里氏代换关系才存在。如果两个具体的类A,B之间的关系违反了LSP的设计,(假设是从B到A的继承关系)那么根据具体的情况可以在下面的两种重构方案中选择一种。
<?php //举例说明继承的风险,我们需要完成一个两数相减的功能,由类A来负责。 class a{ public $width; public $height; public function func1($a, $b){ return $a - $b; } } $a = new a(); echo $a->func1(100,50); //运行结果100-50=50 后来,我们需要增加一个新的功能:完成两数相加,然后再与100求和,由类B来负责。即类B需要完成两个功能:
采用类B继承类A代码如下:
class b extends a{ public function func1($a, $b){ return $a + $b; } public function func2($a, $b){ return $this->func1($a, $b) + 100; } } $b = new b(); echo $b->func2(100, 50); 假设类B在给方法起名时无意中重写了父类的方法,造成所有运行相减功能的代码全部调用了类B重写后的方法,造成原本运行正常的功能fun1出现了错误(错误的原因是减法变成了加法而其他使用者并不知道。别总想着代码是一个人写的呦!还有大家没有时间去逐行读你的代码,他们只是按照规则进行应用)。
里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。它包含以下4层含义:
1、子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
2、子类中可以增加自己特有的方法。
3、当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
使用多个专门的接口比使用单一的总接口要好。
一个类对另外一个类的依赖性应当是建立在最小的接口上的。
一个接口代表一个角色,不应当将不同的角色都交给一个接口。没有关系的接口合并在一起,形成一个臃肿的大接口,这是对角色和接口的污染。
“不应该强迫客户依赖于它们不用的方法。接口属于客户,不属于它所在的类层次结构。”这个说得很明白了,再通俗点说,不要强迫客户使用它们不用的方法,如果强迫用户使用它们不使用的方法,那么这些客户就会面临由于这些不使用的方法的改变所带来的改变。 这个图的意思是:类A依赖接口I中的方法1、方法2、方法3,类B是对类A依赖的实现。类C依赖接口I中的方法1、方法4、方法5,类D是对类C依赖的实现。对于类B和类D来说,虽然他们都存在着用不到的方法(也就是图中红色字体标记的方法),但由于实现了接口I,所以也必须要实现这些用不到的方法。可以看到,如果接口过于臃肿,只要接口中出现的方法,不管对依赖于它的类有没有用处,实现类中都必须去实现这些方法,这显然不是好的设计。如果将这个设计修改为符合接口隔离原则,就必须对接口I进行拆分。在这里我们将原有的接口I拆分为三个接口,拆分后的设计如图所示 接口隔离原则的含义是:建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。也就是说,我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。本文例子中,将一个庞大的接口变更为3个专用的接口所采用的就是接口隔离原则。在程序设计中,依赖几个专用的接口要比依赖一个综合的接口更灵活。接口是设计时对外部设定的“契约”,通过分散定义多个接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。
说到这里,很多人会觉的接口隔离原则跟之前的单一职责原则很相似,其实不然。其一,单一职责原则原注重的是职责;而接口隔离原则注重对接口依赖的隔离。其二,单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节;而接口隔离原则主要约束接口接口,主要针对抽象,针对程序整体框架的构建。
采用接口隔离原则对接口进行约束时,要注意以下几点:
接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性是不挣的事实,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。
为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系。
提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。
运用接口隔离原则,一定要适度,接口设计的过大或过小都不好。设计接口的时候,只有多花些时间去思考和筹划,才能准确地实践这一原则。
同步:https://zhufn.fun/archives/handwritejson/ 主要实现思路是递归。我们把单个值也当作合法的json,先判断当前json类型,如果是单个值就直接return对应类型(解析时)或toString(编码时),如果是Object就遍历它的key值,然后取冒号后面的部分递归解析/编码,Array也差不多。 经解码再编码测试非常完美。 知识点:java泛型类如HashMap<String, Object>无法用instanceof检测。 但是,可以新写一个空类去继承他,这样就不是泛型了。
(Java快加个rust那种枚举吧)
Warning有点多,将就看
package fun.zhufn.oldcomm; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; class BadJsonException extends RuntimeException { } class JsonMap extends HashMap<String, Object> { } class JsonList extends ArrayList<Object> { } public class MyJsonParser { static void print(String s) { System.out.println(s); } public static Object parse(String json) throws BadJsonException { json = json.trim(); if (json.startsWith([) && json.endsWith(])) { JsonList ret = new JsonList(); json = json.
实验任务3 with open('data3.txt','r',encoding='utf-8') as f: be=[] b=f.readlines() for i in b[1::]: i=eval(i.strip('\n')) be.append(i) print(f'原始数据:\n{be}') a=[] for i in be: if i%1>=0.5: i=int(i)+1 a.append(i) else: i=int(i) a.append(i) print(f'四舍五入后数据:\n{a}') with open('data3.processeed.txt','w') as f: be1=['原始数据'] be2=be1+be a1=['四舍五入后数据'] a2=a1+a for i in range(len(be)+1): f.write(str(be2[i])+'\t'+str(a2[i])+'\n') 实验任务4 with open('data4.txt','r') as f: list1=f.readlines() b=[] major=[] score=[] name=list1[0] list1.remove(name) for i in list1: i=i.split('\t') c=i[2] major.append(c) a=i[0]+'\t'+i[1] d=i[3] score.append(d) b.append(a) list2=sorted(list(zip(major,b,score))) list3=[list(i) for i in list2[0:7]] list4=[i[::-1] for i in list3] list5=sorted(list4,reverse=True) list6=[list(i) for i in list2[8::]] list7=[i[::-1] for i in list6] list8=sorted(list7,reverse=True) lista=list5+list8 tir1=[] tir2=[] tir3=[] for j in lista: tir1.
过期时间不续期 tryLock(long time, TimeUnit unit) 会等待指定的时间,如果时间到了还没获得锁就返回 false;如果在时间范围内获得了锁就立刻返回 true,不用等待时间结束。
无论是返回 true 还是 false,都会继续执行之后的代码。
RLock rLock=redisson.getLock(key); Boolean lockStatus =rLock.tryLock(1, TimeUnit.MINUTES); if (lockStatus){ //todo } 使用tryLock无参方法时,redisson会自动添加一个定时任务,定时刷新锁的失效时间,如果unlock时失败(比如kill -9项目),则会出现该锁一直不释放的情况。
而当tryLock传释放时间时,则不会添加这个定时任务。
解锁报错 finally { if (lockStatus){ rLock.unlock(); } } 代码的意思是,如果当时获取到了锁,最终就要unLock();
看起来没问题,但如果lockStatus是true,但是到rLock.unLock()的时候 锁过期自动释放了,那么解锁的代码就会报错(因为释放的时候 并没有锁)
所以应该改成这样:释放的时候重新检测一下是不是持有锁。
finally { if (rLock.isLocked()){ rLock.unlock(); } } Lock和tryLock的区别 1: lock拿不到锁会一直等待。tryLock是去尝试,拿不到就返回false,拿到返回true。
2:tryLock是可以被打断的,被中断 的,lock是不可以
前言 JavaScript中函数定义基本分3种方式:函数声明,函数表达式,构造函数。
函数声明 函数声明使用function 关键字, 格式如下
function 函数名称([参数]) { // 执行代码 } 函数声明后,它不会自己执行,需要调用才会执行,调用函数使用函数名称加括号,如
function fun1() { // 执行代码 console.log('hello') } // 调用函数 fun1() // hello 函数中使用return 获取返回值
function fun1() { // 执行代码 return 'hello world' } // 调用函数 var r = fun1() // 获取函数返回值 console.log(r) 函数声明提升 之前学习变量的时候,学到一个词:声明提升,函数声明也会被提升。 hoisting(声明提升):函数声明和变量声明总是会被解释器悄悄地被提升到方法体的最顶部。
var r = fun1() // 获取函数返回值 console.log(r) // 函数声明会被提升 function fun1() { // 执行代码 return 'hello world' } 函数声明的名称fun1会被提升到最顶部,所以上面代码不会报错,一般习惯上我们是先声明函数后调用。