文章目录
- 后端
- 1.使用Google工具类
- 这个 类的 verifyTest 方法可以判断扫描绑定之后的app上面验证码的准确性。
- 这个类通过g_user,g_code(就是谷歌验证器的secret,这个你已经插入到数据库 中)来生成相关二维码。
- 2.用工具类自带的g_user,g_code来生成二维码
- 2.1通过请求来生成相关二维码,后端返回给前端
- 3.第一次通过生成的secret_key登录,之后扫描进行绑定Google验证码,通过验证码进行登录
- 前端Vue
后端
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 17public 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 {//@Testpublic 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");//容错级别为Hhints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);//白边的宽度,可取0~4hints.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 {@Autowiredprivate 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-inputv-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-inputv-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-inputv-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-inputv-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>