新公司需要用到 identityServer4 来做微服务的架构部署。其中一个项目使用前端 ant design pro +react 后端使用 .net core,关于identityServer4 的部署和identityServer4与后端的整合这里就不讲了,网上很多类似的讲解,这里只讲ant design pro +react与identityServer4 的集成,网上也有很多其他前端与identityServer4 的集成,都是现成的拿下来跑,没有怎么详细的讲解,与ant design pro +react也有一些差异。
1、首先把identityServer4 部署好,这里有一个现成的,大家可以直接下载下来运行就行:https://github.com/skoruba/IdentityServer4.Admin,建议把https改成http。其他详细步骤就不讲了。
2、在官网下载 ant design pro +react:https://pro.ant.design。
直接下载,然后安装相关依赖(安装依赖步骤就不讲了)。
antd 的语言版本,TypeScript 或 JavaScript,我这里选的是JavaScript,根据个人需要选择。
3、ant design pro +react想和 identityServer4 需要借助的三方插件oidc-client,所以我们也需要安装oidc-client依赖,安装完成之后在src/services 下新建两个文件
,里面的代码我就直接copy下来了
ApiServerce.js:
/* eslint-disable */
import axios from 'axios'
import Mgr from './SecurityService'
import 'babel-polyfill';
const baseUrl = 'https://localhost:44390/api/';
var user = new Mgr()
export async function defineHeaderAxios () {
await user.getAcessToken().then(
acessToken => {
axios.defaults.headers.common['Authorization'] = 'Bearer ' + acessToken
}, err => {
console.log(err)
})
}
export async function getAll(api){
await this.defineHeaderAxios()
return axios
.get(baseUrl + api)
.then(response => response.data)
.catch(err => {
console.log(err);
})
}
SecurityService.js:
/* eslint-disable */
import Oidc from 'oidc-client';
import 'babel-polyfill';
var mgr = new Oidc.UserManager({
userStore: new Oidc.WebStorageStateStore(),
//authority: 'http://it-dev-win.incubecn.com:44310',
authority: 'http://localhost:44310',
client_id: 'reackjsclient',
redirect_uri: window.location.origin + '/callback.html',
// redirect_uri: window.location.origin + '/page/user/login/index.jsx',
response_type: 'code',
scope: 'openid profile email roles',
post_logout_redirect_uri: window.location.origin + '/index.html',
silent_redirect_uri: window.location.origin + '/silent-renew.html',
accessTokenExpiringNotificationTime: 10,
automaticSilentRenew: true,
filterProtocolClaims: true,
loadUserInfo: true
})
Oidc.Log.logger = console;
Oidc.Log.level = Oidc.Log.INFO;
mgr.events.addUserLoaded(function (user) {
console.log('New User Loaded:', arguments);
console.log('Acess_token: ', user.access_token)
});
mgr.events.addAccessTokenExpiring(function () {
console.log('AccessToken Expiring:', arguments);
});
mgr.events.addAccessTokenExpired(function () {
console.log('AccessToken Expired:', arguments);
alert('Session expired. Going out!');
mgr.signoutRedirect().then(function (resp) {
console.log('signed out', resp);
}).catch(function (err) {
console.log(err)
})
});
mgr.events.addSilentRenewError(function () {
console.error('Silent Renew Error:', arguments);
});
// mgr.events.addUserSignedOut(function () {
// alert('Going out!');
// console.log('UserSignedOut:', arguments);
// mgr.signoutRedirect().then(function (resp) {
// console.log('signed out', resp);
// }).catch(function (err) {
// console.log(err)
// })
// });
export default class SecurityService {
// Renew the token manually
renewToken () {
let self = this
return new Promise((resolve, reject) => {
mgr.signinSilent().then(function (user) {
if (user == null) {
self.signIn(null)
} else{
return resolve(user)
}
}).catch(function (err) {
console.log(err)
return reject(err)
});
})
}
// Get the user who is logged in
getUser () {
let self = this
return new Promise((resolve, reject) => {
mgr.getUser().then(function (user) {
if (user == null) {
self.signIn()
return resolve(null)
} else{
return resolve(user)
}
}).catch(function (err) {
console.log(err)
return reject(err)
});
})
}
// Check if there is any user logged in
getSignedIn () {
let self = this
return new Promise((resolve, reject) => {
mgr.getUser().then(function (user) {
if (user == null) {
self.signIn()
return resolve(false)
} else{
return resolve(true)
}
}).catch(function (err) {
console.log(err)
return reject(err)
});
})
}
// Redirect of the current window to the authorization endpoint.
signIn () {
mgr.signinRedirect().catch(function (err) {
console.log(err)
})
}
// Redirect of the current window to the end session endpoint
signOut () {
mgr.signoutRedirect().then(function (resp) {
console.log('signed out', resp);
}).catch(function (err) {
console.log(err)
})
}
// Get the profile of the user logged in
getProfile () {
let self = this
return new Promise((resolve, reject) => {
mgr.getUser().then(function (user) {
if (user == null) {
self.signIn()
return resolve(null)
} else{
return resolve(user.profile)
}
}).catch(function (err) {
console.log(err)
return reject(err)
});
})
}
// Get the token id
getIdToken(){
let self = this
return new Promise((resolve, reject) => {
mgr.getUser().then(function (user) {
if (user == null) {
self.signIn()
return resolve(null)
} else{
return resolve(user.id_token)
}
}).catch(function (err) {
console.log(err)
return reject(err)
});
})
}
// Get the session state
getSessionState(){
let self = this
return new Promise((resolve, reject) => {
mgr.getUser().then(function (user) {
if (user == null) {
self.signIn()
return resolve(null)
} else{
return resolve(user.session_state)
}
}).catch(function (err) {
console.log(err)
return reject(err)
});
})
}
// Get the access token of the logged in user
getAcessToken(){
let self = this
return new Promise((resolve, reject) => {
mgr.getUser().then(function (user) {
if (user == null) {
self.signIn()
return resolve(null)
} else{
return resolve(user.access_token)
}
}).catch(function (err) {
console.log(err)
return reject(err)
});
})
}
// Takes the scopes of the logged in user
getScopes(){
let self = this
return new Promise((resolve, reject) => {
mgr.getUser().then(function (user) {
if (user == null) {
self.signIn()
return resolve(null)
} else{
return resolve(user.scopes)
}
}).catch(function (err) {
console.log(err)
return reject(err)
});
})
}
// Get the user roles logged in
getRole () {
let self = this
return new Promise((resolve, reject) => {
mgr.getUser().then(function (user) {
if (user == null) {
self.signIn()
return resolve(null)
} else{
return resolve(user.profile.role)
}
}).catch(function (err) {
console.log(err)
return reject(err)
});
})
}
}
在public 文件下面新增这里几个文件,是oidc-client需要用到的
callback.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Waiting...</title>
</head>
<body>
<h1>Authentification callback processing...</h1>
<script src="oidc-client.js"></script>
<script>
var mgr = new Oidc.UserManager({userStore: new Oidc.WebStorageStateStore(), loadUserInfo: true, filterProtocolClaims: true});
mgr.signinRedirectCallback().then(function (user) {
window.location.href = '../';
}).catch(function (err) {
console.log(err);
});
</script>
</body>
</html>
silent-renew.html:
<!DOCTYPE html>
<html>
<head>
<title>Silent Renew Token for SpeEDI Cargo</title>
</head>
<body>
<script src='oidc-client.js'></script>
<script>
console.log('trying to renew the acess_token')
new Oidc.UserManager().signinSilentCallback();
</script>
</body>
</html>
oidc-client.js和oidc-client.min.js直接去百度下载就行了。
从上面直到这里,网上去找一些教程大部分都是有的,下面这些就是自己一点点摸索出来的。
一、配置identityServer4
1.打开后台管理界面
2.点击客户端管理
这里是我新建过的,你们开始的话直接添加客户端就行了
3.客户端标识
客户端标识是要和前端配置一致的,不然访问的时候会提示没有授权,在前端SecurityService.js文件里面:client_id: 'reackjsclient',(上面代码有)
4.基本
其他配置默认一致就行了,主要是这个重定向Uri,这个Uri需要配置和 SecurityService.js文件里面redirect_uri参数一致(redirect_uri: window.location.origin + '/callback.html',),10.18.11.81是我本地IP,8000是前端启动的端口,这里还需要保证这里路劲能正常访问。
5.认证\注销
1.前端通道注销 Uri 照着填就行了,把地址和端口改成你自己前端的,这里他是调用第三方插件(oidc-client)的注销地址,
2.注销重定向 Uri:这玩意也是照着填就行了,把地址和端口改成你自己前端的,这个目前没看着有啥用,意思就是注销之后跳转的界面(不知道对不对,这个不是特别重要,后面如果有大佬知道是什么麻烦告诉我一下,谢谢)。
6.令牌
其他选项默认成一样的就行了,这里只有一个允许跨域来源需要注意一下,前端访问IdentityServer4是会跨域的,我们也不需要使用其他的工具来解决跨域问题,直接在这里配置一下前端的端口就行了(本地IP+端口)。
7.同意屏幕
客户端Uri改成你自己的ip+端口
至此,identityServer4配置基本上配置好了,如果访问过程中,有提示没有授权或者无效的请求就检查前端配置的路劲和identityServer4配置的路劲是否一致。
网上其他一些现成的demo基本上最多就是这么多了,但是官方的ant design pro和网上大部分demo都不一样,所以后续还有很多需要处理的地方。
Ant Design Pro前端配置
1.首先你运行前端需要到id(identityServer4,后面都用id4简称标识)上做登录和权限认证,我这里的做法是运行前端登录界面时直接跳转过去,你也可以设置一个按钮点击跳转,看个人需求
跳转方法是第三方插件oidc-client写好了的,直接调用就行。
2.当你到Id4里面登陆之后,登录信息是存在第三方插件里面的,如role,token,name等,后面都会用到这些信息。
这时候可能发生一种情况,你登录之后跳转到callback.html界面之后又跳转回登录界面,这是因为ant design pro 官方代码里面写了一些逻辑(其他网上demo里面没有这写逻辑的),这对于从来没接触过ant design pro+reack的人来说不太友好。
在APP.JSX里面 将fetchUserInfo方法里面的代码改成下面这样:
const fetchUserInfo = async () => {
try {
const Mgrs = new Mgr()
const currentUser=await Mgrs.getUser();
return currentUser;
} catch (error) {
history.push(loginPath);
}
return undefined;
}; //
这里的意思是从第三方插件里面去获取用户信息包括token等信息,token这些是访问后端API做权限管控需要的,原来这里的代码是向一个后端API里面拿员工信息,因为我们没有部署后端API并且我们是在id4里面登录的,所以原来代码在这里根本拿不到登录信息,当拿不到登录信息时,就会跳转到登录界面,所以就会出现在id4登录之后又重新跳转到前端登录界面的情况。
当这里代码处理好之后就可以正常登录了。
2.登出,登出比登录简单多了,只要调用第三方插件的登出方法就行了,这里就不多讲了。
至此,ant design pro +react+identityServer4的部署集成就完成了,回头来看其实并不难,只是两者都是刚刚接触,不了解其中运行原理,当出现错误时会一头蒙。
主要有两点:
一:id4与前端的配置需要一致
二:前端登录之后的跳转。(获取到role和token等信息)