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

同时支持多种登录验证

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