文章目录
后端
1.使用Google工具类
package com.ruoyi.common.utils.googleAuth;
import org.apache.commons.codec.binary.Base32;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
public class GoogleGenerator {
// 生成的key长度( Generate secret key length)
public static final int SECRET_SIZE = 10;
public static final String SEED = "22150146801713967E8g";
// Java实现随机数算法
public static final String RANDOM_NUMBER_ALGORITHM = "SHA1PRNG";
// 最多可偏移的时间
int window_size = 3; // default 3 - max 17
public static String generateSecretKey() {
SecureRandom sr;
try {
sr = SecureRandom.getInstance(RANDOM_NUMBER_ALGORITHM);
sr.setSeed(Base64.decodeBase64(SEED));
byte[] buffer = sr.generateSeed(SECRET_SIZE);
Base32 codec = new Base32();
byte[] bEncodedKey = codec.encode(buffer);
return new String(bEncodedKey);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
/**
* 这个format不可以修改,身份验证器无法识别二维码
*/
public static String getQRBarcode(String user, String secret) {
String format = "otpauth://totp/%s?secret=%s";
return String.format(format, user, secret);
}
/**
* 根据user和secret生成二维码的密钥
*/
public static String getQRBarcodeURL(String user, String host, String secret) {
String format = "http://www.google.com/chart?chs=200x200&chld=M%%7C0&cht=qr&chl=otpauth://totp/%s@%s?secret=%s";
return String.format(format, user, host, secret);
}
public boolean check_code(String secret, String code, long timeMsec) {
Base32 codec = new Base32();
byte[] decodedKey = codec.decode(secret);
long t = (timeMsec / 1000L) / 30L;
for (int i = -window_size; i <= window_size; ++i) {
long hash;
try {
hash = verify_code(decodedKey, t + i);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
System.out.println("code=" + code);
System.out.println("hash=" + hash);
if (code.equals(addZero(hash))) {
return true;
}
}
return false;
}
private static int verify_code(byte[] key, long t) throws NoSuchAlgorithmException, InvalidKeyException {
byte[] data = new byte[8];
long value = t;
for (int i = 8; i-- > 0; value >>>= 8) {
data[i] = (byte) value;
}
SecretKeySpec signKey = new SecretKeySpec(key, "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signKey);
byte[] hash = mac.doFinal(data);
int offset = hash[20 - 1] & 0xF;
long truncatedHash = 0;
for (int i = 0; i < 4; ++i) {
truncatedHash <<= 8;
truncatedHash |= (hash[offset + i] & 0xFF);
}
truncatedHash &= 0x7FFFFFFF;
truncatedHash %= 1000000;
return (int) truncatedHash;
}
private String addZero(long code) {
return String.format("%06d", code);
}
}
这个 类的 verifyTest 方法可以判断扫描绑定之后的app上面验证码的准确性。
package com.ruoyi.common.utils.googleAuth;
/**
*
*
* 身份认证测试
*
* @author yangbo
*
* @version 创建时间:2017年8月14日 上午11:09:23
*
*
*/
public class GoogleUtils {
//@Test
public String genSecret(String g_name) {// 生成密钥
String secret = GoogleGenerator.generateSecretKey();
// 把这个qrcode生成二维码,用google身份验证器扫描二维码就能添加成功
String qrcode = GoogleGenerator.getQRBarcode(g_name, secret);
System.out.println("qrcode:" + qrcode + ",key:" + secret);
return secret;
}
/**
* 对app的随机生成的code,输入并验证
*/
public static boolean verifyTest(String code,String secret) {
long t = System.currentTimeMillis();
GoogleGenerator ga = new GoogleGenerator();
// ga.setWindowSize(5);
boolean r = ga.check_code(secret, code, t);
System.out.println("检查code是否正确?" + r);
return r;
}
public static void main(String [] args)
{
GoogleUtils gt=new GoogleUtils();
String secret=gt.genSecret("Antpay(web3game)");
verifyTest("Antpay(web3game)","2DYHBGQLNLQWSPZV");
}
}
这个类通过g_user,g_code(就是谷歌验证器的secret,这个你已经插入到数据库 中)来生成相关二维码。
package com.ruoyi.common.utils.googleAuth;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import java.util.HashMap;
import java.util.Map;
/**
* 二维码工具类
*/
public class QRCodeUtil {
/**
* 生成二维码
*
* @param content 二维码的内容
* @return BitMatrix对象
*/
public static BitMatrix createCode(String content) {
//二维码的宽高
int width = 200;
int height = 200;
//其他参数,如字符集编码
Map<EncodeHintType, Object> hints = new HashMap<>();
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
//容错级别为H
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
//白边的宽度,可取0~4
hints.put(EncodeHintType.MARGIN, 0);
BitMatrix bitMatrix = null;
try {
//生成矩阵,因为我的业务场景传来的是编码之后的URL,所以先解码
bitMatrix = new MultiFormatWriter().encode(content,
BarcodeFormat.QR_CODE, width, height, hints);
//bitMatrix = deleteWhite(bitMatrix);
} catch (WriterException e) {
e.printStackTrace();
}
return bitMatrix;
}
/**
* 删除生成的二维码周围的白边,根据审美决定是否删除
*
* @param matrix BitMatrix对象
* @return BitMatrix对象
*/
private static BitMatrix deleteWhite(BitMatrix matrix) {
int[] rec = matrix.getEnclosingRectangle();
int resWidth = rec[2] + 1;
int resHeight = rec[3] + 1;
BitMatrix resMatrix = new BitMatrix(resWidth, resHeight);
resMatrix.clear();
for (int i = 0; i < resWidth; i++) {
for (int j = 0; j < resHeight; j++) {
if (matrix.get(i + rec[0], j + rec[1]))
resMatrix.set(i, j);
}
}
return resMatrix;
}
}
2.用工具类自带的g_user,g_code来生成二维码
这个是SysUser数据库表的部分g_user,g_code数据。
g_user | g_code |
---|---|
Antpay(admin) | PXUPGNVY6QPWRNNQ |
Antpay(payUser) | LLFS2ON52UAXOIKP |
Antpay(shanghu4) | DYFTPDY5MS7CS3HA |
Antpay(hwgame) | AKZQA7ANHHHZ5TQW |
Antpay(baby) | 5GTBWBTRPEYWCSW2 |
Antpay(beartech) | WEPHOIBAQACJ7VNP |
Antpay(gmoney) | 3AZTCIQJAZMV6IGK |
Antpay(ml) | H45DLW4C37QNVUX5 |
Antpay(ml_afr) | 4XOGTVG7AJXMPJBQ |
Antpay(agent1) | J6TCF3TIWYC57WWE |
Antpay(10069) | MKIC4KXOSIU6H2OC |
Antpay(10071) | 7VHKY4YIWCSDBYEC |
Antpay(M10068) | JKBGRRXBFSQGX45Q |
Antpay(M1006801) | FI2TNSP2PYOWZKVX |
Antpay(M1006802) | OTTHGUQHFYNAHDMV |
2.1通过请求来生成相关二维码,后端返回给前端
package com.ruoyi.web.controller.runscore;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.utils.googleAuth.GoogleGenerator;
import com.ruoyi.common.utils.googleAuth.QRCodeUtil;
import com.ruoyi.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
@Anonymous
@RestController
@RequestMapping(value = "/googleAuth")
public class GoogleAuthController extends BaseController {
@Autowired
private ISysUserService sysUserService;
//根据user和secret生成二维码的密钥
@PostMapping(value = "/getQRBarcodeURL")
public AjaxResult getQRBarcodeURL(String user, String host, String secret) {
return success(GoogleGenerator.getQRBarcodeURL(user, host, secret));
}
//查看google 二维码信息
@PostMapping(value = "/getQRBarcode")
public AjaxResult getQRBarcode(String user, String secret) {
return success(GoogleGenerator.getQRBarcode(user, secret));
}
/**
* 生成二维码
*/
@GetMapping(value = "/generateQRCode/{userId}")
public void GenerateQRCode(String content, @PathVariable("userId") String userId, HttpServletResponse response) throws IOException {
/* Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if ("anonymousUser".equals(principal)) {
return ;
}
LoginUser user = (LoginUser) principal;*/
// UserAccountInfoVO userAccountInfo = userAccountService.getUserAccountInfo(user.getUserAccountId());
/* LoginUser user = SecurityUtils.getLoginUser();
String userId = SecurityUtils.getUserId();*/
SysUser user = sysUserService.selectUserById(userId);
content=GoogleGenerator.getQRBarcode(user.getGoogleUser(),user.getGoogleCode());
// content=GoogleGenerator.getQRBarcode("(gemblastmaster)","RGOEVUN2G44TTRZT");
// 设置响应流信息
response.setContentType("image/jpg");
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
OutputStream stream = response.getOutputStream();
//获取一个二维码图片
BitMatrix bitMatrix = QRCodeUtil.createCode(content);
//以流的形式输出到前端
MatrixToImageWriter.writeToStream(bitMatrix, "jpg", stream);
}
//新增用户的时候生成密钥并且保存
@GetMapping(value = "/geSecretKey")
public AjaxResult geSecretKey() {
return success(GoogleGenerator.generateSecretKey());
}
//验证code是否合法
@PostMapping(value = "/checkValidCode")
public AjaxResult checkGoogleValidCode(String secret, String code) {
return success(new GoogleGenerator().check_code(secret, code, System.currentTimeMillis()));
}
}
private boolean isMatchMerchant(String username, String googleCode) {
if(StringUtils.isEmpty(googleCode)){
return false;
}
SysUser sysUser = userService.selectUserByUserName(username);
if(Objects.isNull(sysUser)){
throw new RuntimeException("不存在这个用户!");
}
/* QueryWrapper<Merchant> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("relevance_account_id", sysUser.getUserId());
Merchant merchant = merchantRepo.selectOne(queryWrapper);*/
Merchant merchant = merchantRepo.selectByUserId(sysUser.getUserId());
if (Objects.isNull(merchant)) {
return false;
}
if(googleCode.equals(merchant.getSecretKey()) || GoogleUtils.verifyTest(googleCode, sysUser.getGoogleCode())){
return true;
}
return false;
}
3.第一次通过生成的secret_key登录,之后扫描进行绑定Google验证码,通过验证码进行登录
前端Vue
<template>
<div class="login">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
<h3 class="title">登 录</h3>
<el-form-item prop="username">
<el-input
v-model="loginForm.username"
type="text"
auto-complete="off"
placeholder="账号"
>
<svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="loginForm.password"
type="password"
auto-complete="off"
placeholder="密码"
@keyup.enter.native="handleLogin"
>
<svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
</el-input>
</el-form-item>
<el-form-item prop="code">
<el-input
v-model="loginForm.code"
type="text"
placeholder="Google验证码"
@keyup.enter.native="handleLogin"
>
<!-- <svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" /> -->
</el-input>
</el-form-item>
<!-- <el-form-item prop="code" v-if="captchaEnabled">
<el-input
v-model="loginForm.code"
auto-complete="off"
placeholder="验证码"
style="width: 63%"
@keyup.enter.native="handleLogin"
>
<svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" />
</el-input>
<div class="login-code">
<img :src="codeUrl" @click="getCode" class="login-code-img"/>
</div>
</el-form-item> -->
<el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox> <br>
<el-button type="text" @click="showCode" >扫码关联Google验证器 </el-button>
<el-form-item style="width:100%;">
<el-button
:loading="loading"
size="medium"
type="primary"
style="width:100%;"
@click.native.prevent="handleLogin"
>
<span v-if="!loading">登 录</span>
<span v-else>登 录 中...</span>
</el-button>
<div style="float: right;" v-if="register">
<router-link class="link-type" :to="'/register'">立即注册</router-link>
</div>
</el-form-item>
</el-form>
<!-- 底部 -->
<div class="el-login-footer">
<span>Copyright © 2018-2024 antcash.vip All Rights Reserved.</span>
</div>
</div>
</template>
<script>
import { getCodeImg, login, showQRCode} from "@/api/login";
import Cookies from "js-cookie";
import { encrypt, decrypt } from '@/utils/jsencrypt'
export default {
name: "Login",
data() {
return {
codeUrl: "",
loginForm: {
username: "admin",
password: "admin123",
rememberMe: false,
googleCode: "",
code: "",
uuid: ""
},
loginRules: {
username: [
{ required: true, trigger: "blur", message: "请输入您的账号" }
],
password: [
{ required: true, trigger: "blur", message: "请输入您的密码" }
],
// code: [{ required: true, trigger: "change", message: "请输入验证码" }],
code: [{ required: true, trigger: "blur", message: "请输入google验证码" }],
},
loading: false,
// 验证码开关
captchaEnabled: false,
// 注册开关
register: false,
redirect: undefined
};
},
watch: {
$route: {
handler: function(route) {
this.redirect = route.query && route.query.redirect;
},
immediate: true
}
},
created() {
// this.getCode();
this.getCookie();
},
methods: {
// getCode() {
// getCodeImg().then(res => {
// this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled;
// if (this.captchaEnabled) {
// this.codeUrl = "data:image/gif;base64," + res.img;
// this.loginForm.uuid = res.uuid;
// }
// });
// },
showCode () {
if (this.loginForm.username == null || this.loginForm.username == '') {
this.$message.error('请输入用户名');
return;
}
if (this.loginForm.password == null || this.loginForm.password == '') {
this.$message.error('请输入密码');
return;
}
if (this.loginForm.code == null || this.loginForm.code == '') {
this.$message.error('请输入google验证码');
return;
}
let username1 = this.loginForm.username;
let password1 = this.loginForm.password;
let code1 = this.loginForm.code;
login(username1, password1, code1).then(res =>{
// showQRCode(res.userId).then();
let url='http://localhost/dev-api/googleAuth/generateQRCode/'+ res.userId;
window.open(url, '_blank');
});
},
getCookie() {
const username = Cookies.get("username");
const password = Cookies.get("password");
const rememberMe = Cookies.get('rememberMe')
const code = Cookies.get('googleCode')
this.loginForm = {
username: username === undefined ? this.loginForm.username : username,
password: password === undefined ? this.loginForm.password : decrypt(password),
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe),
// code: code === undefined ? this.loginForm.code : code,
};
},
handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true;
if (this.loginForm.rememberMe) {
Cookies.set("username", this.loginForm.username, { expires: 30 });
Cookies.set("password", encrypt(this.loginForm.password), { expires: 30 });
Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 });
} else {
Cookies.remove("username");
Cookies.remove("password");
Cookies.remove('rememberMe');
// Cookies.remove('googleCode');
}
this.$store.dispatch("Login", this.loginForm).then(() => {
this.$router.push({ path: this.redirect || "/" }).catch(()=>{});
}).catch(() => {
this.loading = false;
if (this.captchaEnabled) {
this.getCode();
}
});
}
});
}
}
};
</script>
<style rel="stylesheet/scss" lang="scss">
.login {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
background-image: url("../assets/images/login-background.jpg");
background-size: cover;
}
.title {
margin: 0px auto 30px auto;
text-align: center;
color: #707070;
}
.login-form {
border-radius: 6px;
background: #ffffff;
width: 400px;
padding: 25px 25px 5px 25px;
.el-input {
height: 38px;
input {
height: 38px;
}
}
.input-icon {
height: 39px;
width: 14px;
margin-left: 2px;
}
}
.login-tip {
font-size: 13px;
text-align: center;
color: #bfbfbf;
}
.login-code {
width: 33%;
height: 38px;
float: right;
img {
cursor: pointer;
vertical-align: middle;
}
}
.el-login-footer {
height: 40px;
line-height: 40px;
position: fixed;
bottom: 0;
width: 100%;
text-align: center;
color: #fff;
font-family: Arial;
font-size: 12px;
letter-spacing: 1px;
}
.login-code-img {
height: 38px;
}
</style>