优化了双因素认证的流程和页面
This commit is contained in:
parent
3bb7d2d49b
commit
4de18a6a81
@ -48,6 +48,45 @@ func LoginEndpoint(c echo.Context) error {
|
||||
return Fail(c, -1, "您输入的账号或密码不正确")
|
||||
}
|
||||
|
||||
if user.TOTPSecret != "" {
|
||||
return Fail(c, 0, "")
|
||||
}
|
||||
|
||||
token := utils.UUID()
|
||||
|
||||
authorization := Authorization{
|
||||
Token: token,
|
||||
Remember: loginAccount.Remember,
|
||||
User: user,
|
||||
}
|
||||
|
||||
if authorization.Remember {
|
||||
// 记住登录有效期两周
|
||||
global.Cache.Set(token, authorization, time.Hour*time.Duration(24*14))
|
||||
} else {
|
||||
global.Cache.Set(token, authorization, time.Hour*time.Duration(2))
|
||||
}
|
||||
|
||||
model.UpdateUserById(&model.User{Online: true}, user.ID)
|
||||
|
||||
return Success(c, token)
|
||||
}
|
||||
|
||||
func loginWithTotpEndpoint(c echo.Context) error {
|
||||
var loginAccount LoginAccount
|
||||
if err := c.Bind(&loginAccount); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user, err := model.FindUserByUsername(loginAccount.Username)
|
||||
if err != nil {
|
||||
return Fail(c, -1, "您输入的账号或密码不正确")
|
||||
}
|
||||
|
||||
if err := utils.Encoder.Match([]byte(user.Password), []byte(loginAccount.Password)); err != nil {
|
||||
return Fail(c, -1, "您输入的账号或密码不正确")
|
||||
}
|
||||
|
||||
if !totp.Validate(loginAccount.TOTP, user.TOTPSecret) {
|
||||
return Fail(c, -2, "您的TOTP不匹配")
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ func SetupRoutes() *echo.Echo {
|
||||
e.Use(Auth)
|
||||
|
||||
e.POST("/login", LoginEndpoint)
|
||||
e.POST("/loginWithTotp", loginWithTotpEndpoint)
|
||||
|
||||
e.GET("/tunnel", TunEndpoint)
|
||||
e.GET("/ssh", SSHEndpoint)
|
||||
|
@ -25,6 +25,7 @@ const handleError = (error) => {
|
||||
const handleResult = (result) => {
|
||||
if (result['code'] === 403) {
|
||||
window.location.href = '#/login';
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,18 +1,24 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Button, Card, Checkbox, Form, Input, Typography } from "antd";
|
||||
import React, {Component} from 'react';
|
||||
import {Button, Card, Checkbox, Form, Input, Modal, Typography} from "antd";
|
||||
import './Login.css'
|
||||
import request from "../common/request";
|
||||
import { message } from "antd/es";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import { OneToOneOutlined, LockOutlined, UserOutlined } from '@ant-design/icons';
|
||||
import {message} from "antd/es";
|
||||
import {withRouter} from "react-router-dom";
|
||||
import {LockOutlined, OneToOneOutlined, UserOutlined} from '@ant-design/icons';
|
||||
|
||||
const {Title} = Typography;
|
||||
|
||||
const { Title } = Typography;
|
||||
class LoginForm extends Component {
|
||||
|
||||
formRef = React.createRef()
|
||||
|
||||
state = {
|
||||
inLogin: false,
|
||||
height: window.innerHeight,
|
||||
width: window.innerWidth
|
||||
width: window.innerWidth,
|
||||
loginAccount: undefined,
|
||||
totpModalVisible: false,
|
||||
confirmLoading: false
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
@ -31,6 +37,15 @@ class LoginForm extends Component {
|
||||
|
||||
try {
|
||||
let result = await request.post('/login', params);
|
||||
|
||||
if (result.code === 0) {
|
||||
// 进行双因子认证
|
||||
this.setState({
|
||||
loginAccount: params,
|
||||
totpModalVisible: true
|
||||
})
|
||||
return;
|
||||
}
|
||||
if (result.code !== 1) {
|
||||
throw new Error(result.message);
|
||||
}
|
||||
@ -50,35 +65,88 @@ class LoginForm extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
handleOk = async (values) => {
|
||||
this.setState({
|
||||
confirmLoading: true
|
||||
})
|
||||
let loginAccount = this.state.loginAccount;
|
||||
loginAccount['totp'] = values['totp'];
|
||||
try {
|
||||
let result = await request.post('/loginWithTotp', loginAccount);
|
||||
|
||||
if (result.code !== 1) {
|
||||
throw new Error(result.message);
|
||||
}
|
||||
|
||||
// 跳转登录
|
||||
sessionStorage.removeItem('current');
|
||||
sessionStorage.removeItem('openKeys');
|
||||
localStorage.setItem('X-Auth-Token', result['data']);
|
||||
// this.props.history.push();
|
||||
window.location.href = "/"
|
||||
} catch (e) {
|
||||
message.error(e.message);
|
||||
} finally {
|
||||
this.setState({
|
||||
confirmLoading: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleCancel = () => {
|
||||
this.setState({
|
||||
totpModalVisible: false
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className='login-bg'
|
||||
style={{ width: this.state.width, height: this.state.height, backgroundColor: '#F0F2F5' }}>
|
||||
style={{width: this.state.width, height: this.state.height, backgroundColor: '#F0F2F5'}}>
|
||||
<Card className='login-card' title={null}>
|
||||
<div style={{ textAlign: "center", margin: '15px auto 30px auto', color: '#1890ff' }}>
|
||||
<div style={{textAlign: "center", margin: '15px auto 30px auto', color: '#1890ff'}}>
|
||||
<Title level={1}>Next Terminal</Title>
|
||||
</div>
|
||||
<Form onFinish={this.handleSubmit} className="login-form">
|
||||
<Form.Item name='username' rules={[{ required: true, message: '请输入登录账号!' }]}>
|
||||
<Input prefix={<UserOutlined />} placeholder="登录账号" />
|
||||
<Form.Item name='username' rules={[{required: true, message: '请输入登录账号!'}]}>
|
||||
<Input prefix={<UserOutlined/>} placeholder="登录账号"/>
|
||||
</Form.Item>
|
||||
<Form.Item name='password' rules={[{ required: true, message: '请输入登录密码!' }]}>
|
||||
<Input.Password prefix={<LockOutlined />} placeholder="登录密码" />
|
||||
</Form.Item>
|
||||
<Form.Item name='totp' rules={[]}>
|
||||
<Input prefix={<OneToOneOutlined />} placeholder="TOTP" />
|
||||
<Form.Item name='password' rules={[{required: true, message: '请输入登录密码!'}]}>
|
||||
<Input.Password prefix={<LockOutlined/>} placeholder="登录密码"/>
|
||||
</Form.Item>
|
||||
<Form.Item name='remember' valuePropName='checked' initialValue={false}>
|
||||
<Checkbox>记住登录</Checkbox>
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit" className="login-form-button"
|
||||
loading={this.state.inLogin}>
|
||||
loading={this.state.inLogin}>
|
||||
登录
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
|
||||
<Modal title="双因素认证" visible={this.state.totpModalVisible} confirmLoading={this.state.confirmLoading}
|
||||
onOk={() => {
|
||||
this.formRef.current
|
||||
.validateFields()
|
||||
.then(values => {
|
||||
this.formRef.current.resetFields();
|
||||
this.handleOk(values);
|
||||
})
|
||||
.catch(info => {
|
||||
|
||||
});
|
||||
}}
|
||||
onCancel={this.handleCancel}>
|
||||
|
||||
<Form ref={this.formRef}>
|
||||
|
||||
<Form.Item name='totp' rules={[{required: true, message: '请输入双因素认证APP中显示的授权码'}]}>
|
||||
<Input prefix={<OneToOneOutlined/>} placeholder="请输入双因素认证APP中显示的授权码"/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
|
||||
);
|
||||
|
@ -1,11 +1,12 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Button, Form, Input, Layout, PageHeader, Image } from "antd";
|
||||
import { itemRender } from '../../utils/utils'
|
||||
import React, {Component} from 'react';
|
||||
import {Button, Card, Form, Image, Input, Layout, PageHeader} from "antd";
|
||||
import {itemRender} from '../../utils/utils'
|
||||
import request from "../../common/request";
|
||||
import { message } from "antd/es";
|
||||
import {message} from "antd/es";
|
||||
import Logout from "./Logout";
|
||||
|
||||
const { Content } = Layout;
|
||||
const {Content} = Layout;
|
||||
const {Meta} = Card;
|
||||
|
||||
const routes = [
|
||||
{
|
||||
@ -19,12 +20,12 @@ const routes = [
|
||||
];
|
||||
|
||||
const formItemLayout = {
|
||||
labelCol: { span: 3 },
|
||||
wrapperCol: { span: 6 },
|
||||
labelCol: {span: 3},
|
||||
wrapperCol: {span: 6},
|
||||
};
|
||||
const formTailLayout = {
|
||||
labelCol: { span: 3 },
|
||||
wrapperCol: { span: 6, offset: 3 },
|
||||
labelCol: {span: 3},
|
||||
wrapperCol: {span: 6, offset: 3},
|
||||
};
|
||||
|
||||
class Info extends Component {
|
||||
@ -106,7 +107,7 @@ class Info extends Component {
|
||||
itemRender: itemRender
|
||||
}}
|
||||
extra={[
|
||||
<Logout key='logout' />
|
||||
<Logout key='logout'/>
|
||||
]}
|
||||
subTitle="个人中心"
|
||||
/>
|
||||
@ -125,7 +126,7 @@ class Info extends Component {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input type='password' placeholder="请输入原始密码" />
|
||||
<Input type='password' placeholder="请输入原始密码"/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
{...formItemLayout}
|
||||
@ -139,7 +140,7 @@ class Info extends Component {
|
||||
]}
|
||||
>
|
||||
<Input type='password' placeholder="新的密码"
|
||||
onChange={(value) => this.onNewPasswordChange(value)} />
|
||||
onChange={(value) => this.onNewPasswordChange(value)}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
{...formItemLayout}
|
||||
@ -155,7 +156,7 @@ class Info extends Component {
|
||||
help={this.state.errorMsg || ''}
|
||||
>
|
||||
<Input type='password' placeholder="请和上面输入新的密码保持一致"
|
||||
onChange={(value) => this.onNewPassword2Change(value)} />
|
||||
onChange={(value) => this.onNewPassword2Change(value)}/>
|
||||
</Form.Item>
|
||||
<Form.Item {...formTailLayout}>
|
||||
<Button type="primary" htmlType="submit">
|
||||
@ -174,11 +175,19 @@ class Info extends Component {
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<Form hidden={!this.state.qr} onFinish={this.confirmTOTP}>
|
||||
<Form.Item {...formItemLayout} label="使用TOTP应用扫码">
|
||||
<Image
|
||||
width={200}
|
||||
src={"data:image/png;base64, " + this.state.qr}
|
||||
/>
|
||||
<Form.Item {...formItemLayout} label="二维码">
|
||||
|
||||
<Card
|
||||
hoverable
|
||||
style={{width: 280}}
|
||||
cover={<Image
|
||||
style={{margin: 40, marginBottom: 20}}
|
||||
width={200}
|
||||
src={"data:image/png;base64, " + this.state.qr}
|
||||
/>}
|
||||
>
|
||||
<Meta title="双因素认证二维码" description="有效期30秒,在扫描后请尽快输入。"/>
|
||||
</Card>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
{...formItemLayout}
|
||||
@ -186,7 +195,7 @@ class Info extends Component {
|
||||
label="TOTP"
|
||||
rules={[]}
|
||||
>
|
||||
<Input placeholder="请输入显示的数字" />
|
||||
<Input placeholder="请输入双因素认证APP中显示的授权码"/>
|
||||
</Form.Item>
|
||||
<Form.Item {...formTailLayout}>
|
||||
<Button type="primary" htmlType="submit">
|
||||
|
Loading…
Reference in New Issue
Block a user