提交 aa0a8707 authored 作者: vipcxj's avatar vipcxj

同时支持多种登录验证

上级 f0c86e58
const proxy = {
"target": "http://192.168.1.200:8090",
"target": "http://192.168.1.22:8080",
"changeOrigin": true,
"pathRewrite": {
"^/api": "/big-machine/restful-services"
"^/api": "/bm/api"
}
};
const resource_proxy = {
"target": "http://192.168.1.200:8090",
"target": "http://192.168.1.22:8080",
"changeOrigin": true,
"pathRewrite": {
"^/": "/big-machine/"
"^/resource": "/bm/resource"
}
};
......
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Input, Spin } from 'antd';
import { debounce } from 'lodash';
import { getCert, init, sign } from '../../utils/uca';
class UCA extends Component {
constructor(props, context) {
super(props, context);
this.state = {
ready: false,
};
}
componentDidMount() {
this.task = setInterval(() => {
const res = init();
if (res === 0) {
clearInterval(this.task);
this.task = null;
this.setState({
ready: true,
});
}
}, 300);
}
componentWillUnmount() {
if (this.task) {
clearInterval(this.task);
}
}
onChange = (value) => {
if (this.state.ready && this.props.onChange) {
if (value) {
if (this.props.data) {
this.props.onChange(sign(value, this.props.data));
} else {
this.props.onChange(getCert(value));
}
}
}
};
render() {
const onChange = (e) => {
return debounce(this.onChange, 300)(e.target.value);
};
const { loading, ...rest } = this.props;
return (
<Spin spinning={!this.state.ready && loading} size="small">
<Input {...rest} onChange={onChange} type="password" />
</Spin>
);
}
}
UCA.propTypes = {
loading: PropTypes.bool,
data: PropTypes.string,
};
UCA.defaultProps = {
loading: false,
};
export default UCA;
......@@ -36,3 +36,8 @@ app.router(routerConfig);
initApp(app).then(theApp => theApp.start('#root'));
export default app;
export const getStore = () => {
// eslint-disable-next-line no-underscore-dangle
return app._store;
};
import { routerRedux } from 'dva/router';
import { login, userInfo } from '../services/login';
import { validate } from '../services/login/password';
import { fullPath } from '../utils/helper';
import { authorize, login, userInfo } from '../services/login';
import { validate as passValidate } from '../services/login/password';
import { requestCode, validate as ucaValidate } from '../services/login/uca';
import { fullPath, encrypt } from '../utils/helper';
import { setToken, setUser, setDomain, histories } from '../utils/auth';
import { switchDomain, currentDomain } from '../services/domain';
import { errors } from '../utils/error';
import config from '../utils/config';
import { getStore } from '../index';
const successAuthed = async (tokenId) => {
await setToken(tokenId);
const uInfo = await userInfo();
await setUser(uInfo.id, uInfo.name);
const path = await histories.getLatest('domain');
if (!path) {
getStore().dispatch(routerRedux.push(fullPath('/domain')));
} else {
await switchDomain(path);
const domain = await currentDomain();
if (domain) {
await setDomain(domain.name, path);
const latest = await histories.getLatest('module');
if (latest && config.fastNavigationPage) {
getStore().dispatch(routerRedux.push(fullPath('/fastNav')));
} else {
getStore().dispatch(routerRedux.push(fullPath('/main')));
}
} else {
getStore().dispatch(routerRedux.push(fullPath('/domain')));
}
}
};
const processAuthRequirements = (requirements, supports) => {
let res = [];
for (const requirement of requirements) {
const { authTypes } = requirement;
if (authTypes && authTypes.length > 0) {
const filteredAuthTypes = authTypes.filter(authType => supports.indexOf(authType) !== -1);
if (filteredAuthTypes.length === 0) {
throw errors.unsupportedAuthType(authTypes);
}
res = res.map(require => require.filter(item => authTypes.indexOf(item) === -1)).filter(require => require.length > 0);
res.push(filteredAuthTypes);
}
}
return res;
};
let tkId;
export default {
namespace: 'login',
state: {},
reducers: {},
state: {
status: 'login',
userName: '',
ucaCode: '',
authRequires: [],
},
reducers: {
setStatus(state, { payload }) {
return {
...state,
status: payload,
};
},
setUserName(state, { payload }) {
return {
...state,
userName: payload,
};
},
setUCACode(state, { payload }) {
return {
...state,
ucaCode: payload,
};
},
setAuthRequires(state, { payload }) {
return {
...state,
authRequires: payload,
};
},
},
effects: {
*login({ payload }, { call, put }) {
*init(ignored, { put }) {
yield put({ type: 'setStatus', payload: 'login' });
yield put({ type: 'setUCACode', payload: '' });
yield put({ type: 'setAuthRequires', payload: [] });
},
*login({ payload: userName }, { call, put }) {
yield put({ type: 'setUserName', userName });
const { tokenId, remainedAuthRequirements } = yield call(login, {
type: 'userName',
data: userName,
});
tkId = tokenId;
const { requirements } = remainedAuthRequirements;
const requires = processAuthRequirements(requirements, ['password', 'uca']);
if (requires.length === 0) {
yield call(successAuthed, tokenId);
} else {
if (requires.some(req => req.indexOf('uca') !== -1)) {
yield put({ type: 'requestUCACode' });
}
yield put({ type: 'setAuthRequires', payload: requires });
}
yield put({ type: 'setStatus', payload: 'auth' });
},
*requestUCACode(ignored, { call, put }) {
const code = yield call(authorize, yield call(requestCode, encrypt(tkId)));
yield put({ type: 'setUCACode', payload: code });
},
*auth({ payload: { password, uca } }, { call }) {
let response;
if (password) {
response = yield call(authorize, yield call(passValidate, password, encrypt(tkId)));
} else if (uca) {
response = yield call(authorize, yield call(ucaValidate, uca, encrypt(tkId)));
}
if (response) {
const { status, remainedAuthRequirements } = response;
if (status !== 'authed' && status !== 'skipped') {
throw errors.authFailed();
}
if (remainedAuthRequirements.requirements.length === 0) {
yield call(successAuthed, tkId);
}
}
},
/* *login({ payload }, { call, put }) {
const loginRequest = {
type: 'userName',
data: payload.userName,
......@@ -48,7 +167,7 @@ export default {
yield put(routerRedux.push(fullPath('/domain')));
}
}
},
},*/
},
subscriptions: {},
};
import React from 'react';
import { Input, Select } from 'antd';
class AuthInputs extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
index: props.data.length > 0 ? 0 : -1,
};
}
onChange = (value) => {
this.setState({
index: this.props.findIndex(item => item.key === value),
});
};
render() {
const { data } = this.props;
const select = this.state.index >= 0 ? data[this.state.index].key : undefined;
return (
<Input.Group compact>
<Select value={select} onChange={this.onChange}>
{
data.map(item => (<Select.Option value={item.key} key={item.key}> { item.label } </Select.Option>))
}
</Select>
{
React.cloneElement(data[select].node, { value: this.props.value, onChange: this.props.onChange })
}
</Input.Group>
);
}
}
export default AuthInputs;
......@@ -5,34 +5,111 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Form, Icon, Input, Checkbox, Button } from 'antd';
import { connect } from 'dva';
import AuthInputs from './authInput';
import UCA from '../../components/uca';
import config from '../../utils/config';
import styles from './index.less';
const FormItem = Form.Item;
class LoginForm extends React.Component {
constructor(props, context) {
super(props, context);
this.handleSubmit = this::this.handleSubmit;
componentDidMount() {
this.props.dispatch({ type: 'login/init' });
}
handleSubmit(e) {
onUserBlur = (e) => {
const { value } = e.target;
this.props.dispatch({ type: 'login/login', payload: value });
};
handleSubmit = (e) => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
this.props.dispatch({
type: 'login/login',
payload: {
type: 'userName',
authType: 'password',
...values,
},
});
if (this.props.login.status === 'login') {
this.authFocus = true;
this.props.dispatch({ type: 'login/login', payload: values.userName });
} else if (this.props.login.status === 'auth') {
this.props.dispatch({ type: 'login/auth', payload: values });
}
}
});
}
};
createPassword = (focus = false) => {
const { getFieldDecorator } = this.props.form;
return getFieldDecorator('password', {
rules: [{
required: true,
message: '请输入密码。',
}],
})(
<Input autoFocus={focus} prefix={<Icon type="lock" />} type="password" placeholder="密码" />,
);
};
createUCA = (focus = false) => {
const { form, login } = this.props;
const { getFieldDecorator } = form;
return getFieldDecorator('uca', {
rules: [{
required: true,
message: '请输入usb-key的密钥。',
}],
})(
<UCA autoFocus={focus} loading={!login.ucaCode} data={login.ucaCode} prefix={<Icon type="hdd" placeholder="密钥" />} />,
);
};
createValidates = () => {
const { login } = this.props;
if (login.status === 'login') {
return [];
} else if (login.status === 'auth') {
const { authRequires } = login;
return authRequires.map((authRequire) => {
const pass = authRequire.indexOf('password') !== -1;
const uca = authRequire.indexOf('uca') !== -1;
let focus = false;
if (pass || uca) {
if (this.authFocus) {
focus = true;
this.authFocus = false;
}
}
if (pass && uca) {
return (
<Form.Item key="passOrUca">
<AuthInputs
data={[
{
key: 'password',
label: '密码',
node: this.createPassword(focus),
},
{
key: 'uca',
label: '证书',
node: this.createUCA(focus),
},
]}
/>
</Form.Item>
);
} else if (pass) {
return (
<Form.Item key="pass">
{ this.createPassword(focus) }
</Form.Item>
);
} else if (uca) {
return (
<Form.Item key="uca">
{ this.createUCA(focus) }
</Form.Item>
);
} else {
return <Form.Item />;
}
});
}
};
render() {
const { getFieldDecorator } = this.props.form;
......@@ -45,7 +122,7 @@ class LoginForm extends React.Component {
<span>{config.name}</span>
</div>
<Form onSubmit={this.handleSubmit}>
<FormItem>
<Form.Item>
{
getFieldDecorator('userName', {
rules: [{
......@@ -53,23 +130,14 @@ class LoginForm extends React.Component {
message: '请输入用户名。',
}],
})(
<Input prefix={<Icon type="user" />} placeholder="用户名" />,
)
}
</FormItem>
<FormItem>
{
getFieldDecorator('password', {
rules: [{
required: true,
message: '请输入密码。',
}],
})(
<Input prefix={<Icon type="lock" />} type="password" placeholder="密码" />,
<Input autoFocus={!this.authFocus} onBlur={this.onUserBlur} prefix={<Icon type="user" />} placeholder="用户名" />,
)
}
</FormItem>
<FormItem>
</Form.Item>
{
this.createValidates()
}
<Form.Item>
{
getFieldDecorator('remember', {
valuePropName: 'checked',
......@@ -83,7 +151,7 @@ class LoginForm extends React.Component {
登录
</Button>
或者<a>现在注册!</a>
</FormItem>
</Form.Item>
</Form>
</div>
</div>
......
......@@ -7,6 +7,5 @@ export const validate = async (password, token) => {
cipher: password,
},
};
await addToken(request, token);
return request;
return addToken(request, token);
};
......@@ -7,8 +7,7 @@ export const requestCode = async (token) => {
action: 'request',
},
};
await addToken(request, token);
return request;
return addToken(request, token);
};
export const validate = async (signed, token) => {
......@@ -19,7 +18,6 @@ export const validate = async (signed, token) => {
response: encodeURIComponent(signed),
},
};
await addToken(request, token);
return request;
return addToken(request, token);
};
......@@ -2,14 +2,26 @@ import { getToken } from '../../utils/auth';
export const addToken = async (request, token) => {
if (token) {
// eslint-disable-next-line no-param-reassign
request.tkId = token;
return {
tkId: token,
request,
};
} else {
let tkId;
const localToken = await getToken();
if (localToken) {
// eslint-disable-next-line no-param-reassign
request.tkId = localToken;
tkId = localToken;
}
if (tkId) {
return {
tkId,
request,
};
} else {
return {
request,
};
}
}
return request;
};
......@@ -19,6 +19,7 @@ export const errors = {
token_missing: 0x00000001,
wrong_password: 0x00000002,
unsupported_auth_type: 0x00000003,
auth_failed: 0x00000004,
};
export const api = {
......
......@@ -84,6 +84,10 @@ export const errors = {
code: errorCodes.wrong_password,
msg: '密码错误!',
}),
authFailed: () => createError({
code: errorCodes.auth_failed,
msg: '登录验证失败!',
}),
unsupportedAuthType: (...types) => createError({
code: errorCodes.unsupported_auth_type,
msg: `不支持的客户端验证方式:${types.join('、')}.`,
......
import AXO from 'axo';
import bowser from 'bowser';
export const isBrowserSupport = () => {
return bowser.msie;
};
/**
* 初始化
* @return {number} 0: success; 1: blocked by the browser; 2: driver not installed
*/
export const init = () => {
if (bowser.msie || bowser.msedge) {
if (bowser.msie) {
try {
const se = new AXO('SafeEngineCOM.SafeEngineCtl');
if (se) {
......@@ -25,24 +29,34 @@ export const init = () => {
/**
* 获取证书
* @param password usb-key的密钥
* @return {{cert: string, deadTime: number}}
* @return {{[cert]: string, [deadTime]: number, status: number, [error]: string}}
*/
export const getCert = (password) => {
if (bowser.msie || bowser.msedge) {
if (bowser.msie) {
const safeEngine = new AXO('SafeEngineCOM.SafeEngineCtl');
if (!safeEngine) {
throw new Error('未安装USB证书控件。');
return {
status: 1,
error: '未安装USB证书控件。',
};
}
safeEngine.SEH_InitialSession(27, 'com1', password, 100, 2, '', '');
if (safeEngine.ErrorCode !== 0) {
throw new Error('USB-KEY初始化失败,请确认USB-KEY是否插入或密码是否正确。');
return {
status: safeEngine.ErrorCode,
error: 'USB-KEY初始化失败,请确认USB-KEY是否插入或密码是否正确。',
};
}
try {
const cert = safeEngine.SEH_GetSelfCertificate(27, 'com1', '');
if (safeEngine.ErrorCode !== 0) {
throw new Error(`获取个人证书错误,错误代码为:${safeEngine.ErrorCode}。`);
return {
status: safeEngine.ErrorCode,
error: `获取个人证书错误,错误代码为:${safeEngine.ErrorCode}。`,
};
}
const res = {
status: 0,
cert,
};
const deadTime = safeEngine.SEH_GetCertValidDate(cert);
......@@ -52,7 +66,10 @@ export const getCert = (password) => {
safeEngine.SEH_ClearSession();
}
} else {
throw new Error('当前浏览器不支持activeX控件,请切换ie或edge或带ie内核的360之类的浏览器。');
return {
status: 1,
error: '当前浏览器不支持activeX控件,请切换ie或带ie内核的360之类的浏览器。',
};
}
};
......@@ -60,29 +77,41 @@ export const getCert = (password) => {
* 签名
* @param password usb-key的密钥
* @param data 需要签名的数据,从服务端获取
* @return {{deadTime: number, signed: string}}
* @return {{[cert]: string, [deadTime]: number, [signed]: string, status: number, [error]: string}}
*/
export const sign = (password, data) => {
if (bowser.msie || bowser.msedge) {
const safeEngine = new AXO('SafeEngineCOM.SafeEngineCtl');
if (!safeEngine) {
throw new Error('未安装USB证书控件。');
return {
status: 1,
error: '未安装USB证书控件。',
};
}
safeEngine.SEH_InitialSession(27, 'com1', password, 100, 2, '', '');
if (safeEngine.ErrorCode !== 0) {
throw new Error('USB-KEY初始化失败,请确认USB-KEY是否插入或密码是否正确。');
return {
status: safeEngine.ErrorCode,
error: 'USB-KEY初始化失败,请确认USB-KEY是否插入或密码是否正确。',
};
}
try {
const cert = safeEngine.SEH_GetSelfCertificate(27, 'com1', '');
if (safeEngine.ErrorCode !== 0) {
throw new Error(`获取个人证书错误,错误代码为:${safeEngine.ErrorCode}。`);
return {
status: safeEngine.ErrorCode,
error: `获取个人证书错误,错误代码为:${safeEngine.ErrorCode}。`,
};
}
const res = {};
const res = { cert, status: 0 };
const deadTime = safeEngine.SEH_GetCertValidDate(cert);
res.deadTime = parseInt(deadTime, 10);
const signed = safeEngine.SEH_SignData(data, 3);
if (safeEngine.ErrorCode !== 0) {
throw new Error(`数字签名失败,错误代码为:${safeEngine.ErrorCode}。`);
return {
status: safeEngine.ErrorCode,
error: `数字签名失败,错误代码为:${safeEngine.ErrorCode}。`,
};
}
res.signed = signed;
return res;
......@@ -90,6 +119,9 @@ export const sign = (password, data) => {
safeEngine.SEH_ClearSession();
}
} else {
throw new Error('当前浏览器不支持activeX控件,请切换ie或edge或带ie内核的360之类的浏览器。');
return {
status: 1,
error: '当前浏览器不支持activeX控件,请切换ie或带ie内核的360之类的浏览器。',
};
}
};
......@@ -9,16 +9,16 @@ import dva from 'dva';
import createLoading from 'dva-loading';
import DsTable from '../src/components/table/dstable';
import TableInput from '../src/components/table/input-table';
import UCA from '../src/components/uca';
import { makeCancelable } from '../src/utils/promise';
import { login } from '../src/services/login';
import { validate } from '../src/services/login/password';
import { switchDomain } from '../src/services/domain';
import { setToken } from '../src/utils/auth';
const loginIt = async (userName, password, domainPath) => {
const type = 'userName';
const authType = 'password';
const result = await login({ type, authType, userName, password });
const result = await login({ type: 'userName', data: userName, authRequest: await validate(password) });
const { tokenId } = result;
await setToken(tokenId);
await switchDomain(domainPath);
......@@ -86,6 +86,9 @@ const RangePicker = DatePicker.RangePicker;
storiesOf('antd', module)
.add('RangePicker', () => {
return <RangePicker />;
})
.add('uca', () => {
return <UCA onChange={evt => console.log(evt)} />;
});
storiesOf('table-input', module)
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论