admin管理员组文章数量:1615213
目录
一、OAuth是什么
二、OAuth 实现,前端需要做什么
(一)数据收集表单
(二)获取后端返回值
(三)重定向地址及后续处理
三、项目地址
一、OAuth是什么
OAuth就是让"客户端"安全可控地获取"用户"的授权,与"服务商提供商"进行互动。也就是大家口中熟知的第三方登录,通过微信号或qq号授权去登录各类app或网站。
因为博主目前是一名菜鸡前端,所以对OAuth的后端实现并不是很了解,所以,本篇文章着重讲OAuth的前端实现思路。
想了解后端运作机制的请点击下面这个大佬写的文章。写的还是非常详细的。虽然我看不太懂。理解OAuth 2.0 - 阮一峰的网络日志https://www.ruanyifeng/blog/2014/05/oauth_2_0.html
二、OAuth 实现,前端需要做什么
(一)数据收集表单
大家对于这个表单可能会有一些不了解的地方,下面我来一一解释一下。
字段 | 解释 |
OAuth类型 | OAuth类型你可以理解为个个应用对于请求地址的参数上传是不一致的,你需要对个个应用的地址做一些相对应的处理,而OAuth类型就可以作为对这些应用的区分的关键信息。 |
名称 | 顾名思义,就是名称 |
客户端id | Client ID 由第三方软件生成的,唯一的值。 |
客户端密钥 | Client Secret 由第三方软件生成的,唯一的值。 |
回调地址 | 回调地址是用于 OAuth认证完回跳时的访问地址,默认填充为当前访问地址。通常也需要您在Oauth 服务提供商进行相同的配置 |
自动登录 | 这个是平台实现的功能,就是当你已经登录了自己的平台账号,再去点击第三方登录,就跳过第三方的登录页面,直接进入第三方应用。这里是本产品特有的功能。大家可以忽略不计。 |
这里我们通过表单把数据返回给后端,后端再去做一些处理,拼接地址,加密等等。
handleCreatOauth = values => {
let {
name,
client_id,
client_secret,
oauth_type,
home_url,
redirect_domain,
is_auto_login
} = values;
oauth_type = oauth_type.toLowerCase();
if (oauth_type === 'github') {
home_url = 'https://github';
}
if (oauth_type === 'aliyun') {
home_url = 'https://oauth.aliyun';
}
if (oauth_type === 'dingtalk') {
home_url = 'https://oapi.dingtalk';
}
const obj = {
name,
client_id,
client_secret,
is_auto_login,
oauth_type,
redirect_uri: `${redirect_domain}/console/oauth/redirect`,
home_url,
is_console: true
};
this.handelRequest(obj);
};
handelRequest = (obj = {}, isclone) => {
const { dispatch, eid } = this.props;
const { oauthInfo, oauthTable, isOpen } = this.state;
const arr = [...oauthTable];
obj.eid = eid;
oauthInfo
? (obj.service_id = oauthInfo.service_id)
: (obj.service_id = null);
isclone ? (obj.enable = false) : (obj.enable = true);
if (oauthTable && oauthTable.length > 0) {
oauthTable.map((item, index) => {
const { service_id } = item;
arr[index].is_console = true;
if (oauthInfo && service_id === obj.service_id) {
arr[index] = Object.assign(arr[index], obj);
}
});
}
!oauthInfo && arr.push(obj);
dispatch({
type: 'global/creatOauth',
payload: {
enterprise_id: eid,
arr
},
callback: data => {
if (data && data.status_code === 200) {
notification.success({
message: isOpen
? formatMessage({id:'notification.success.open'})
: isclone
? formatMessage({id:'notification.success.close'})
: oauthInfo
? formatMessage({id:'notification.success.edit'})
: formatMessage({id:'notification.success.add'})
});
this.handelOauthInfo();
}
}
});
};
以上代码是本项目提交事件的触发函数。 可以看到,oauth_type就作为识别各类应用\网站的关键信息。通过识别从而返回不同的home_url,发送给后端。
(二)获取后端返回值
当我们把完整的信息提交给后端以后,在登录页面,我们就要对接对应的第三方登录入口。
如上图,进入页面以后,先调了一个接口,这里返回了一个关键的信息,就是“authorize_url”。这里我们先暂时不做解释,接着往下看。
{oauthServicesList.map(item => {
const { name, service_id } = item;
return (
<div className={styles.thirdCol} key={service_id}>
<Tooltip placement="top" title={name}>
<a
style={inlineBlock}
href={oauthUtil.getAuthredictURL(item)}
title={name}
>
{oauthUtil.getIcon(item)}
</a>
</Tooltip>
</div>
);
})}
这里是html部分,oauthServicesList就是获取的上面那个接口返回的对接好的第三方应用,通过map循环渲染出图标,然后a链接点接以后拿到对应的“item”传进该方法然后return出返回值进行跳转。
getAuthredictURL(item) {
if (item) {
const {
oauth_type: oauthType,
client_id: clientId,
auth_url: authUrl,
redirect_uri: redirectUri,
service_id: serviceId,
authorize_url: authorizeUrl
} = item;
if (oauthType === 'enterprisecenter' && authorizeUrl) {
const str = authorizeUrl;
const agreement = `${window.location.protocol}//`;
const content = window.location.host;
const suffix = str.substring(
str.indexOf('/enterprise-server'),
str.length
);
const newUrl = agreement + content + suffix;
const isRedirectUrl = newUrl.indexOf('redirect_uri=') > -1;
const redirectbefore =
isRedirectUrl && newUrl.substring(0, newUrl.indexOf('redirect_uri='));
const redirectSuffix =
isRedirectUrl &&
newUrl.substring(newUrl.indexOf('/console'), newUrl.length);
const url = isRedirectUrl
? `${`${redirectbefore}redirect_uri=${agreement}${content}`}${redirectSuffix}`
: newUrl;
return url;
}
if (authorizeUrl) {
return authorizeUrl;
}
if (oauthType == 'github') {
return `${authUrl}?client_id=${clientId}&redirect_uri=${redirectUri}?service_id=${serviceId}&scope=user%20repo%20admin:repo_hook`;
}
return `${authUrl}?client_id=${clientId}&redirect_uri=${redirectUri}?service_id=${serviceId}&response_type=code`;
}
return null;
},
以上就是 getAuthredictURL方法。可以看到oauth_type仍然作为区分各种类型的关键信息。从而返回不同的url。其中一种判定方法就是当authorizUrl不为空时,返回authorizUrl。这里的authorizUrl就是我上文提到的,后端返回的关键信息'authorize_url'。
https://rainhome.goodrain/oauth/authorize?client_id=48948d5082eacd0dd4d9&scope=snsapi_login&redirect_uri=http%3A//localhost%3A8080/console/oauth/redirect%3Fservice_id%3D325&response_type=code"
可以看到这条url拼接了很多信息,有client_id,redirect_url等等。并且做了相应的加密处理 。
当我们完成这一步时,前端的工作就已经完成了一半了。
(三)重定向地址及后续处理
还记得在填写表单时有一个重定向地址吗,'redirect_url',在你点击a链接跳转以后,后端进行一系列操作,然后就会调这个重定向的地址,并在地址栏上返回相应的 code码 和 service_id码。然后再调一个认证接口。
当然事前你要写一个对应的路由地址。
/* eslint-disable no-underscore-dangle */
/* eslint-disable camelcase */
import { message } from 'antd';
import { connect } from 'dva';
import { routerRedux } from 'dva/router';
import React, { Component } from 'react';
import { formatMessage, FormattedMessage } from 'umi-plugin-locale';
import Result from '../../components/Result';
import cookie from '../../utils/cookie';
import handleAPIError from '../../utils/error';
import globalUtil from '../../utils/global';
import rainbondUtil from '../../utils/rainbond';
const loginUrl = '/user/login?disable_auto_login=true';
@connect()
export default class ThirdLogin extends Component {
constructor(props) {
super(props);
this.state = {
resultState: 'ing',
title: formatMessage({id:'login.Third.authentication'}),
desc: formatMessage({id:'login.Third.wait_for'})
};
}
// eslint-disable-next-line consistent-return
componentWillMount() {
const code = rainbondUtil.OauthParameter('code');
const service_id = rainbondUtil.OauthParameter('service_id');
const { dispatch } = this.props;
if (
code &&
service_id &&
code !== 'None' &&
service_id !== 'None' &&
code !== '' &&
service_id !== ''
) {
const token = cookie.get('token');
// if user login
if (token) {
dispatch({ type: 'global/hideNeedLogin' });
dispatch({
type: 'user/fetchThirdLoginBinding',
payload: {
code,
service_id
},
callback: res => {
if (res) {
const status = res.response_data && res.response_data.status;
if (status && status === 400) {
this.setState(
{
resultState: 'error',
title: formatMessage({id:'login.Third.Failed'}),
desc: formatMessage({id:'login.Third.Authentication'})
},
() => {
setTimeout(() => {
this.handleLoginUrl();
}, 1000);
}
);
} else if (res.status_code && res.status_code === 200) {
this.setState(
{
resultState: 'success',
title: formatMessage({id:'login.Third.success'}),
desc: ''
},
() => {
if (res.bean && res.bean.token) {
cookie.set('token', res.bean.token);
}
this.handleSuccess();
}
);
} else {
this.handleLoginUrl();
}
} else {
this.handleLoginUrl();
}
},
handleError: err => {
this.handleError(err);
}
});
return null;
}
globalUtil.removeCookie();
// if not login
dispatch({
type: 'user/fetchThirdCertification',
payload: {
code,
service_id,
domain: window.location.host
},
callback: res => {
if (res) {
const status = res.response_data && res.response_data.status;
if (
status &&
(status === 400 || status === 401 || status === 404)
) {
this.setState(
{
resultState: 'error',
title: formatMessage({id:'login.Third.Failed'}),
desc: res.msg_show || formatMessage({id:'login.Third.token'})
},
() => {
setTimeout(() => {
this.handleLoginUrl();
}, 1000);
}
);
} else if (res.status_code === 200) {
const data = res.bean;
if (data && data.token) {
cookie.set('token', data.token);
this.handleSuccess();
return null;
}
if (data && data.result) {
// if not login
if (!data.result.is_authenticated) {
dispatch(
routerRedux.push(
`/user/third/register?code=${data.result.code}&service_id=${data.result.service_id}&oauth_user_id=${data.result.oauth_user_id}&oauth_type=${data.result.oauth_type}`
)
);
} else {
dispatch(
routerRedux.push(
`/user/third/login?code=${data.result.code}&service_id=${data.result.service_id}&oauth_user_id=${data.result.oauth_user_id}&oauth_type=${data.result.oauth_type}`
)
);
}
}
} else {
this.handleLoginUrl();
}
} else {
this.handleLoginUrl();
}
},
handleError: err => {
this.handleError(err);
}
});
} else {
globalUtil.removeCookie();
dispatch(routerRedux.replace(loginUrl));
}
}
handleLoginUrl = () => {
const { dispatch } = this.props;
dispatch(routerRedux.push(loginUrl));
};
handleError = err => {
const status = (err && err.status) || (err.response && err.response.status);
if (status && status === 500) {
message.warning(formatMessage({id:'login.Third.again'}));
} else {
handleAPIError(err);
}
setTimeout(() => {
this.handleLoginUrl();
}, 1000);
};
handleSuccess = () => {
const { dispatch } = this.props;
let redirect = window.localStorage.getItem('redirect');
if (!redirect || redirect === '') {
redirect = '/';
}
window.localStorage.setItem('redirect', '');
if (redirect.startsWith('/')) {
dispatch(routerRedux.push(redirect));
} else {
window.location.href = redirect;
}
};
render() {
const { resultState, title, desc } = this.state;
return (
<Result
type={resultState}
title={title}
description={desc}
style={{
marginTop: '20%',
marginBottom: 16
}}
/>
);
}
}
这里的判断有些多,先判断有没有登录再判断认证成功不成功,如果登录了并认证成功则将返回的token值设置到cookie的token里然后直接进入应用,登录了但失败则返回认证失败。如果没登录则直接跳转到第三方应用的登录页面。如果都登陆了,但登录的账号没有与本平台关联的账号,则跳转到本平台的登录/注册页面。当你再次登陆时就会将登陆的账号与第三方账号进行关联了。
这样就形成了一个闭环。也就完成了OAuth认证前端该做的所有事情。
以下为实现效果。
三、项目地址
主项目地址:
GitHub - goodrain/rainbond: Cloud native multi cloud application management platform | 云原生多云应用管理平台Cloud native multi cloud application management platform | 云原生多云应用管理平台 - GitHub - goodrain/rainbond: Cloud native multi cloud application management platform | 云原生多云应用管理平台https://github/goodrain/rainbond
前端项目地址 :
GitHub - goodrain/rainbond-ui: Rainbond front-end projectRainbond front-end project . Contribute to goodrain/rainbond-ui development by creating an account on GitHub.https://github/goodrain/rainbond-uiOAuth表单地址:
src > components > OauthForm > index.js
登录页面地址:
src > pages > user > login.js
重定向页面地址:
src > pages > user > Third.js
获取a链接url工具地址:
src > utils > oauth.js
代码已经开源,欢迎fork与start!
版权声明:本文标题:OAuth 2.0 (第三方登录)前端流程实现 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/dongtai/1728694289a1169922.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论