优化了双因素认证的流程和页面

This commit is contained in:
dushixiang 2021-01-12 22:36:59 +08:00
parent 3bb7d2d49b
commit 4de18a6a81
5 changed files with 154 additions and 36 deletions

View File

@ -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不匹配")
}

View File

@ -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)

View File

@ -25,6 +25,7 @@ const handleError = (error) => {
const handleResult = (result) => {
if (result['code'] === 403) {
window.location.href = '#/login';
return;
}
}

View File

@ -1,18 +1,24 @@
import React, {Component} from 'react';
import { Button, Card, Checkbox, Form, Input, Typography } from "antd";
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 {LockOutlined, OneToOneOutlined, UserOutlined} from '@ant-design/icons';
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,6 +65,40 @@ 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'
@ -65,9 +114,6 @@ class LoginForm extends Component {
<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>
<Form.Item name='remember' valuePropName='checked' initialValue={false}>
<Checkbox>记住登录</Checkbox>
</Form.Item>
@ -79,6 +125,28 @@ class LoginForm extends Component {
</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>
);

View File

@ -1,11 +1,12 @@
import React, {Component} from 'react';
import { Button, Form, Input, Layout, PageHeader, Image } from "antd";
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 Logout from "./Logout";
const {Content} = Layout;
const {Meta} = Card;
const routes = [
{
@ -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
<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">