文章目錄
- 后端
- 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>