对于前端开发来讲,鉴权的流程主要在后端那边,本文的目的就是为了让大家了解一下常见的鉴权的方式和原理以及antd是如何鉴权的
认知:HTTP 是一个无状态协议,所以客户端每次发出请求时,下一次请求无法得知上一次请求所包含的状态数据。
1.HTTP Basic Authentication:用的比较少,平常FTP登录用的这种方式,可以用在内部网系统。
2.session-cookie:这个在老的系统见得多,只适用于web系统。以前用java servlet写服务端时候,都会自动维护session,会在cookie写一个JSESSIONID的值。
3.Token:现在主流大多用这个,适用于app鉴权,微信开发平台access token也是差不多这种思路。
4.OAuth:这个是未来的趋势,现在想要推广自己的应用都先接入微信 QQ等登录,降低用户使用门槛。特别是微信渠道的手游,都是接入了微信开发授权登录。
这种授权方式是浏览器遵守http协议实现的基本授权方式,HTTP协议进行通信的过程中,HTTP协议定义了基本认证认证允许HTTP服务器对客户端进行用户身份证的方法。
认证过程:
1. 客户端向服务器请求数据,请求的内容可能是一个网页或者是一个ajax异步请求,此时,假设客户端尚未被验证,则客户端提供如下请求至服务器:
Get /index.html HTTP/1.0
Host:www.google.com
2. 服务器向客户端发送验证请求代码401,(WWW-Authenticate: Basic realm=”google.com”这句话是关键,如果没有客户端不会弹出用户名和密码输入界面)服务器返回的数据大抵如下:
HTTP/1.0 401 Unauthorised
Server: SokEvo/1.0
WWW-Authenticate: Basic realm=”google.com”
Content-Type: text/html
Content-Length: xxx
3. 当符合http1.0或1.1规范的客户端(如IE,FIREFOX)收到401返回值时,将自动弹出一个登录窗口,要求用户输入用户名和密码。
4. 用户输入用户名和密码后,将用户名及密码以BASE64加密方式加密,并将密文放入前一条请求信息中,则客户端发送的第一条请求信息则变成如下内容:
Get /index.html HTTP/1.0
Host:www.google.com
Authorization: Basic d2FuZzp3YW5n
注:d2FuZzp3YW5n表示加密后的用户名及密码(用户名:密码 然后通过base64加密,加密过程是浏览器默认的行为,不需要我们人为加密,我们只需要输入用户名密码即可)
5. 服务器收到上述请求信息后,将Authorization字段后的用户信息取出、解密,将解密后的用户名及密码与用户数据库进行比较验证,如用户名及密码正确,服务器则根据请求,将所请求资源发送给客户端
总结:
客户端发一个请求,发401要求输入用户名密码,用户的发送信息以base64加密,服务器收到后对比数据库校验身份,这种加密方式一般多被用在内部安全性要求不高的的系统上,只是相对的多,总的来说现在使用这种鉴权比较少了。如果项目需要部署在公网上,这种方式不推荐,当然你也可以和SSL来加密传输,这样会好一点。
PS:状态码:信息响应(100
–199
),成功响应(200
–299
),重定向(300
–399
),客户端错误(400
–499
)和服务器错误 (500
–599
)(15条消息) 面试被问了三次的http状态码到底有什么_chenweicoo1的博客-CSDN博客
第二种这个方式是利用服务器端的session(会话)和浏览器端的cookie来实现前后端的认证,由于http请求时是无状态的,服务器正常情况下是不知道当前请求之前有没有来过,这个时候我们如果要记录状态,就需要在服务器端创建一个会话(seesion),将同一个客户端的请求都维护在各自得会会话中,每当请求到达服务器端的时候,先去查一下该客户端有没有在服务器端创建seesion,如果有则已经认证成功了,否则就没有认证。
session-cookie认证主要分四步:
1,服务器在接受客户端首次访问时在服务器端创建seesion,然后保存seesion(可以将seesion保存在内存中,也可以保存在redis中,推荐使用后者),然后给这个session生成一个唯一的标识字符串,然后在响应头中种下这个唯一标识字符串。
2.签名。这一步只是对sid进行加密处理,服务端会根据这个secret密钥进行解密。(非必需步骤)
3.浏览器中收到请求响应的时候会解析响应头,然后将sid保存在本地cookie中,浏览器在下次http请求de 请求头中会带上该域名下的cookie信息,
4.服务器在接受客户端请求时会去解析请求头cookie中的sid,然后根据这个sid去找服务器端保存的该客户端的session,然后判断该请求是否合法,校验sid。
使用基于 Token 的身份验证方法,大概的流程是这样的:
1. 客户端使用用户名跟密码请求登录
2. 服务端收到请求,去验证用户名与密码
3. 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
4. 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里
5. 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
6. 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据
总的来说就是客户端在首次登陆以后,服务端再次接收http请求的时候,就只认token了,请求只要每次把token带上就行了,服务器端会拦截所有的请求,然后校验token的合法性,合法就放行,不合法就返回401(鉴权失败)。
乍的一看好像和前面的seesion-cookie有点像,seesion-cookie是通过seesionid来作为浏览器和服务端的链接桥梁,而token验证方式貌似是token来起到seesionid的角色。其实这两者差别是很大的。
1. sessionid 他只是一个唯一标识的字符串,服务端是根据这个字符串,来查询在服务器端保持的seesion,这里面才保存着用户的登陆状态。但是token本身就是一种登陆成功凭证,他是在登陆成功后根据某种规则生成的一种信息凭证,他里面本身就保存着用户的登陆状态。服务器端只需要根据定义的规则校验这个token是否合法就行。
2. session-cookie是需要cookie配合的,居然要cookie,那么在http代理客户端的选择上就是只有浏览器了,因为只有浏览器才会去解析请求响应头里面的cookie,然后每次请求再默认带上该域名下的cookie。但是我们知道http代理客户端不只有浏览器,还有原生APP等等,这个时候cookie是不起作用的,或者浏览器端是可以禁止cookie的(虽然可以,但是这基本上是属于吃饱没事干的人干的事)…,但是token 就不一样,他是登陆请求在登陆成功后再请求响应体中返回的信息,客户端在收到响应的时候,可以把他存在本地的cookie,storage,或者内存中,然后再下一次请求的请求头重带上这个token就行了。简单点来说cookie-session机制他限制了客户端的类型,而token验证机制丰富了客户端类型。
3. 时效性。session-cookie的sessionid实在登陆的时候生成的而且在登出事时一直不变的,在一定程度上安全就会低,而token是可以在一段时间内动态改变的。
4. 可扩展性。token验证本身是比较灵活的,一是token的解决方案有许多,常用的是JWT,二来我们可以基于token验证机制,专门做一个鉴权服务,用它向多个服务的请求进行统一鉴权。
下面就拿最常用的JWT(JSON WEB TOKEN)来说:
JWT是Auth0提出的通过对JSON进行加密签名来实现授权验证的方案,就是登陆成功后将相关信息组成json对象,然后对这个对象进行某中方式的加密,返回给客户端,客户端在下次请求时带上这个token,服务端再收到请求时校验token合法性,其实也就是在校验请求的合法性。
OAuth(开放授权)是一个开放标准,允许用户授权第三方网站访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方网站或分享他们数据的所有内容,为了保护用户数据的安全和隐私,第三方网站访问用户数据前都需要显式的向用户征求授权。我们常见的提供OAuth认证服务的厂商有支付宝,QQ,微信。
OAuth协议又有1.0和2.0两个版本。相比较1.0版,2.0版整个授权验证流程更简单更安全,也是目前最主要的用户身份验证和授权方式。
下面是一张auth2.0的流程图:
从图中我们可以看出,auth2.0流程分为六布(我们就以csdn登陆为例):
第1步. 向用户请求授权,现在很多的网站在登陆的时候都有第三方登陆的入口,当我们点击等第三方入口时,第三方授权服务会引导我们进入第三方登陆授权页面。
通过第三方请求授权页面的浏览器地址栏地址可以看出,
https://graph.qq.com/oauth2.0/show?which=Login&display=pc&response_type=code&client_id=100270989&redirect_uri=https%3A%2F%2Fpassport.csdn.net%2Faccount%2Flogin%3Foauth_provider%3DQQProvider&state=test
这里的地址里面的%是浏览器强制编码后的显示我们可以使用decodeURIComponent进行解码,解码后是这样:
https://graph.qq.com/oauth2.0/show?which=Login&display=pc&response_type=code&client_id=100270989&redirect_uri=https://passport.csdn.net/account/login?oauth_provider=QQProvider&state=test
这个url地址我们可以看见Auth2.0常见的几个参数:
response_type,返回类型
client_id,第三方应用id,由授权服务器(qq)在第三方应用提交时颁发给第三方应用。
redirect_uri,登陆成功重定向页面
oauth_provider,第三方授权提供方
state,由第三方应用给出的随机码
第2步. 返回用户凭证(code),并返回一个凭证(code),当用户点击授权并登陆后,授权服务器将生成一个用户凭证(code)。这个用户凭证会附加在重定向的地址redirect_uri的后面
https://passport.csdn.net/account/login?code=9e3efa6cea739f9aaab2&state=XXX
第3步. 请求授权服务器授权:
经过第二部获取code后后面的工作就可以交给后台去处理的,和用户的交互就结束了。接下来我的需要获取Access Token,我们需要用他来向授权服务器获取用户信息等资源。
第三方(比如QQ)应用后台通过第二步的凭证(code)向授权服务器请求Access Token,这时候需要以下几个信息:
client_id 标识第三方应用的id,由授权服务器(Github)在第三方应用提交时颁发给第三方应用
client_secret 第三方应用和授权服务器之间的安全凭证,由授权服务器(Github)在第三方应用提交时颁发给第三方应用
code 第一步中返回的用户凭证redirect_uri 第一步生成用户凭证后跳转到第二步时的地址
state 由第三方应用给出的随机码
第4步. 授权服务器同意授权后,返回一个资源访问的凭证(Access Token)。
第5步. 第三方应用(QQ微信)通过第四步的凭证(Access Token)向资源服务器请求相关资源(id,昵称,头像)。
第6步. 资源服务器验证凭证(Access Token)通过后,将第三方应用请求的资源返回。
从用户角度来说,第三方授权可以让我们快速的登陆应用,无需进行繁琐的注册,同时不用记住各种账号密码。只需要记住自己常用的几个账号就ok了。
从产品经理的角度来所,这种授权方式提高用户的体验满意度。另一方面可以获取更多的用户。
授权方式多种多样,主要还是要取决于我们对于产品的定位。如果我们的产品只是在企业内部使用,token和session就可以满足我们的需求,如果是面向互联网的大众用户,那么第三方授权在用户体验度上会有一个很大的提升。
// 项目目录
├── config
│ ├── config.js # 整体的umi配置,包括webpack配置,编译时配置式路由,菜单配置等。路由里面的component 是相对于 src/pages 目录的
│ ├── plugin.config.js # umi拓展的一些webpack配置,是需要导入到上面的config.js里面的
│ ├── router.js # umi的编译时配置式路由,根据路由的name配置,来生成页面上面的菜单栏
├── mock # 本地模拟数据。此目录下所有 js 和 ts 文件会被解析为 mock 文件
├── public # 这个目录的文件不会被打包,会直接被copy到最后的输入目录
│ └── favicon.png
├── src
│ ├── assets # 本地静态资源
│ ├── components # 业务通用组件,这里面如果用到了models,则对应的文件依然是下面那个全局的models文件
│ ├── pageHeader # 这个就是通用的组件了,里面的index.js就为组件的内容
│ ├── index.js
│ ├── e2e # 集成测试用例
│ ├── layouts # 页面整体的通用布局,即页面最外层的框架结构。路由里面的第一级路由数据就是我们的布局,后面的子路由,都嵌套在布局里面
│ ├── BasicLayout.js # 基础页面通用布局,包含了头部导航、侧边栏、通知栏、内容、页脚。用了ProLayout 组件之后,菜单的布局配置是在下面的defaultSetting.ts里面
│ ├── UserLayout.js # 抽离出用于登录注册页面的通用布局。这个和上面的一般是互斥的,都是第一级路由
│ ├── locales # 国际化资源(即可以切换到不同的语言)
│ ├── zh-CN.ts # 切换到语言为zh-CN的时候。umi/locales中引入的FormattedMessage组件的id,对应的配置
│ ├── en-US.ts # 切换到语言为en-US的时候。umi/locales中引入的FormattedMessage组件的id,对应的配置
│ ├── models # 全局 dva model,pages里面的所有组件都可以访问到这个model
│ ├── pages # 业务页面入口和常用模板
│ ├── .umi # 代码运行了之后umi 的临时目录,可以在这里做一些验证,但请不要直接在这里修改代码。并且由于每次build的时候被重新生成,所以不要把这个上传到git
│ ├── user # 这个即业务组件文件夹,这里为实例的User组件
│ ├── components # 这个即业务组件文件夹,里面单独抽离出来的组件,服务于当前的user组件
│ ├── PopAddModal.js
│ ├── PopDeleteModal.js
│ ├── models
│ ├── user.js # 这个为,只有当前组件可以访问的model文件
│ ├── index.js # 登陆的js文件
│ ├── user.Less # 登陆的css文件,由于是CSS Modules,所以引入到js文件的时候,需要用css module的写法来获取对应的css类
│ ├── Authorized.js # 配合路由里面的Routes使用,来进行权限控制,限制菜单栏的可见与隐藏
│ ├── document.ejs # 入口模板文件,相当于index.html
│ ├── services # 后台接口服务,即所有的请求都写在这里面
│ ├── api.ts # 这个即,model里面的请求,就到这里面;然后这个需要调用下面的requese.js
│ ├── utils # 工具库
│ ├── request.js # 这个文件是用来封装请求的,里面用的es6的fetch()函数;services文件夹里面的所以请求,都要调用这个文件
│ ├── utils.less # 这里可以放置一些工具函数供调用,比如清除浮动 .clearfix
│ ├── app.js # 运行时配置文件。提供了几个函数,比如在组件的render生命周期执行之前被调用的函数;路由切换的时候被调用的函数;还有改变整个路由的函数等。具体使用请看umi的运行时配置
│ ├── defaultSetting.ts # 这个是全局的页面主题配置,包括菜单位置等的配置。需要用ProLayout组件
│ ├── global.less # 此文件不走 css modules,且会自动被引入,可以在这里写全局样式,以及做样式覆盖
│ └── global.ts # 此文件会在入口文件的最前面被自动引入,可以在这里加载补丁,做一些初始化的操作等
├── tests # 测试工具
├── README.md
└── package.json
效果:
直接输入地址但却没有权限
1、没有准入权限的菜单将不显示
2、直接在浏览器输入没有准入权限的的地址,将跳转到403页面
前端实际上是很难有权限验证的,由于从安全的角度来讲,前端没有绝对的安全,攻击者老是能够修改前端的代码。对于 API 的权限能够由服务端保证,可是对于页面的权限可能就比较麻烦了。最好的方法固然仍是后端控制——也就是 NodeJs 的后端。若是不能达到这个安全级别的话(不过许多应用也不必),那么剩下的方法都没有什么太大区别,不过若是须要支持动态配置,就须要服务端路由。
首先,有关权限的文件有这几个
以上是一般情况下,我们会涉及到的权限管理文件,下面,我们来逐一分析各个文件之间的关联
通过看路由,我们进入后台管理首先进的是 src\pages\user\Login\index.jsx
同时也是登陆页面的逻辑所在,它const了:
//它引用了'@/services/ant-design-pro/api';
const LoginMessage
// 获取用户信息,初始化用户信息
const Login{
const fetchUserInfo 获取用户信息
const handleSubmit提交函数
失败就显示重试
return (界面演示、form)
}
export default Login;
src\services\ant-design-pro\api.js :
我们已经看了两个,然后我们看page的utils
src\pages\utils\authority.js
import { reloadAuthorized } from './Authorized'; // use localStorage to store the authority info, which might be sent from server in actual project.
//获取身份
export function getAuthority(str) {
const authorityString =
typeof str === 'undefined' && localStorage ? localStorage.getItem('antd-pro-authority') : str; // authorityString could be admin, "admin", ["admin"]
let authority;
try {
if (authorityString) {
authority = JSON.parse(authorityString);
}
} catch (e) {
authority = authorityString;
}
if (typeof authority === 'string') {
return [authority];
} // preview.pro.ant.design only do not use in your production.
// preview.pro.ant.design 专用环境变量,请不要在你的项目中使用它。
if (!authority && ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site') {
return ['admin'];
}
return authority;
}
//设置身份
export function setAuthority(authority) {
const proAuthority = typeof authority === 'string' ? [authority] : authority;
localStorage.setItem('antd-pro-authority', JSON.stringify(proAuthority)); // auto reload
reloadAuthorized();
}
用处:文件就做了两件事情,一个就是从本地读取当前用户角色,一个是保存角色,而保存角色setAuthority这个函数呢,在用户登录的时候,进行保存
然后回到index.jsx,我们可以看到,Authrized作为变量被传入renderAuthorize这个方法
const RenderAuthorize = renderAuthorize(Authorized);
renderAuthorize 这个函数,实际上是返回了一个以*currentAuthority* 作为参数,以*Authorized*作为返回值的函数,并将CURRENT这个变量暴露了出去,至于这个变量的作用(实际上就是当前用户登陆时的角色,只是对其进行了一些处理),下文我们会涉及,现在咱们先继续哈!这个权限对象是关键!
src\pages\utils\Authorized.js
import RenderAuthorize from '@/components/Authorized';
import { getAuthority } from './authority';
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable import/no-mutable-exports */
let Authorized = RenderAuthorize(getAuthority()); // Reload the rights component
const reloadAuthorized = () => {
Authorized = RenderAuthorize(getAuthority());
};
/** Hard code block need it。 */
window.reloadAuthorized = reloadAuthorized;
export { reloadAuthorized };
export default Authorized;
而Authorized呢,实则是一个组件(jsx第一公民优越的地方,能够将组件当作变量使用),
它接收
综上,实际上是一个以Authorized组件为参数的函数,它最后返回的结果仍旧是Authorized,整个文件的目的在于,对路由携带的权限和用户所拥有的权限进行比对,返回通过的组件或者未通过的组件
实际使用方法
在config\routes.js
中会发现如下代码
}
authority={['admin', 'user', 'guest']}
redirectPath="/user/login"
/>
其中authority
对象就是准入身份的数组,表示只有这些身份的人可以登录,我们在配置的时候一定不要忘记在这更新我们新增的身份
然后就是menu.js
,如下,展示了我们在配置菜单的时候怎么配身份
const menuData = [{
name: '题库管理',
path: 'question',
icon: 'warning',
authority: ['admin', 'user'],
children: [{
name: '题库列表',
path: 'list',
}, {
name: '编辑题目',
path: 'create-question',
hideInMenu: true,
}, {
name: '科目管理'
}]
}, {
name: '账号管理',
icon: 'warning',
path: 'account',
children: [{
name: '账号列表',
path: 'list',
authority: 'admin',
}, {
name: '建设中',
path: '',
authority: ['admin', 'user'],
}]
}]
在src\utils\authority.js中有两个函数
//设置身份
export function setAuthority(authority) {
return localStorage.setItem('antd-pro-authority', authority);
}
//获取身份
export function getAuthority() {
return localStorage.getItem('antd-pro-authority');
}
它们把新的身份值存在localStorage里边,用到getAuthority
import RenderAuthorized from '../components/Authorized';
import { getAuthority } from './authority';
let Authorized = RenderAuthorized(getAuthority());
const reloadAuthorized = () => {
Authorized = RenderAuthorized(getAuthority());
};
export { reloadAuthorized };
export default Authorized;
RenderAuthorized: (currentAuthority: string | () => string) => Authorized
权限组件默认 export RenderAuthorized 函数,它接收当前权限作为参数,返回一个权限对象
前端需知道的常见登录鉴权方案_傲娇的koala的博客-CSDN博客
Ant Design Pro开发后台管理系统(权限)-阿里云开发者社区
Antd Pro的权限组件 - JavaShuo
Antd Pro V4 权限管理详解_爱吃辣锅的北极海豹的博客-CSDN博客_antdpro权限管理