@RestController @RequestMapping("/twoFactor") public class TwoFactorController { protected MapuserSecret = new ConcurrentHashMap<>(); @GetMapping("/bind") public String bind(@RequestParam String username) { String secret = MultiFactorAuthenticatorUtil.generateSecretKey(); userSecret.put(username,secret); return MultiFactorAuthenticatorUtil.getQRBarcode(username, secret); } @PostMapping("/check") public Object check(@RequestParam String username, @RequestParam String codeInput) { String secret = userSecret.get(username); return MultiFactorAuthenticatorUtil.checkCode(secret, Long.parseLong(codeInput), System.currentTimeMillis()); } }
@Slf4j public class MultiFactorAuthenticatorUtil { // this is the issuer, you can change it to your company/project name. private static final String ISSUER = "Hello"; // you can change it to any string. private static final String SEED = "thisisgoogleauthenticator"; // taken from Google pam docs - we probably don't need to mess with these private static final String RANDOM_NUMBER_ALGORITHM = "SHA1PRNG"; private static final int SECRET_SIZE = 10; // suggest: the smaller the value, the safer it is. (from 1 to 17) private static final int WINDOW_SIZE = 1; public static String generateSecretKey() { try { SecureRandom 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) { log.error("generate secret exception:{[]}", e); } return null; } public static String getQRBarcode(String user, String secret) { String format = "otpauth://totp/%s?secret=%s&issuer=%s"; String imageContent = String.format(format, user, secret, ISSUER); log.info(imageContent); return QRCodeUtil.getbase64QRCode(imageContent); } public static boolean checkCode(String secret, long code, long time) { base32 codec = new base32(); byte[] decodedKey = codec.decode(secret); // convert unix msec time into a 30 second "window" // this is per the TOTP spec (see the RFC for details) long t = (time / 1000L) / 30L; // Window is used to check codes generated in the near past. // You can use this value to tune how far you're willing to go. for (int i = -WINDOW_SIZE; i <= WINDOW_SIZE; ++i) { long hash; try { hash = verifyCode(decodedKey, t + i); } catch (Exception e) { // Yes, this is bad form - but // the exceptions thrown would be rare and a static configuration problem e.printStackTrace(); throw new RuntimeException(e.getMessage()); //return false; } if (hash == code) { return true; } } // The validation code is invalid. return false; } private static int verifyCode(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; // We're using a long because Java hasn't got unsigned int. long truncatedHash = 0; for (int i = 0; i < 4; ++i) { truncatedHash <<= 8; // We are dealing with signed bytes: // we just keep the first byte. truncatedHash |= (hash[offset + i] & 0xFF); } truncatedHash &= 0x7FFFFFFF; truncatedHash %= 1000000; return (int) truncatedHash; } private MultiFactorAuthenticatorUtil() { } }
public class QRCodeUtil { private static final Integer WIDTH = 140; private static final Integer HEIGHT = 140; private static final Integer LOGO_WIDTH = 22; private static final Integer LOGO_HEIGHT = 22; private static final String IMAGE_FORMAT = "png"; private static final String CHARSET = "utf-8"; private static final String base64_IMAGE = "data:image/png;base64,%s"; public static String getbase64QRCode(String content) { return getbase64Image(content, WIDTH, HEIGHT, null, null, null); } public static String getbase64QRCode(String content, String logoUrl) { return getbase64Image(content, WIDTH, HEIGHT, logoUrl, LOGO_WIDTH, LOGO_HEIGHT); } public static String getbase64QRCode(String content, Integer width, Integer height, String logoUrl, Integer logoWidth, Integer logoHeight) { return getbase64Image(content, width, height, logoUrl, logoWidth, logoHeight); } private static String getbase64Image(String content, Integer width, Integer height, String logoUrl, Integer logoWidth, Integer logoHeight) { ByteArrayOutputStream os = new ByteArrayOutputStream(); BufferedImage bufferedImage = crateQRCode(content, width, height, logoUrl, logoWidth, logoHeight); try { ImageIO.write(bufferedImage, IMAGE_FORMAT, os); } catch (IOException e) { System.out.println("[生成二维码,错误"+e+"]"); } // 转出即可直接使用 return String.format(base64_IMAGE, base64.encode(os.toByteArray())); } private static BufferedImage crateQRCode(String content, Integer width, Integer height, String logoUrl, Integer logoWidth, Integer logoHeight) { if (StrUtil.isNotBlank(content)) { ServletOutputStream stream = null; HashMaphints = new HashMap<>(4); // 指定字符编码为utf-8 hints.put(EncodeHintType.CHARACTER_SET, CHARSET); // 指定二维码的纠错等级为中级 hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M); // 设置图片的边距 hints.put(EncodeHintType.MARGIN, 2); try { QRCodeWriter writer = new QRCodeWriter(); BitMatrix bitMatrix = writer.encode(content, BarcodeFormat.QR_CODE, width, height, hints); BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { bufferedImage.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF); } } if (StrUtil.isNotBlank(logoUrl)) { insertLogo(bufferedImage, width, height, logoUrl, logoWidth, logoHeight); } return bufferedImage; } catch (Exception e) { e.printStackTrace(); } finally { if (stream != null) { try { stream.flush(); stream.close(); } catch (IOException e) { e.printStackTrace(); } } } } return null; } private static void insertLogo(BufferedImage source, Integer width, Integer height, String logoUrl, Integer logoWidth, Integer logoHeight) throws Exception { // logo 源可为 File/InputStream/URL Image src = ImageIO.read(new URL(logoUrl)); // 插入LOGO Graphics2D graph = source.createGraphics(); int x = (width - logoWidth) / 2; int y = (height - logoHeight) / 2; graph.drawImage(src, x, y, logoWidth, logoHeight, null); Shape shape = new RoundRectangle2D.Float(x, y, logoWidth, logoHeight, 6, 6); graph.setStroke(new BasicStroke(3f)); graph.draw(shape); graph.dispose(); } public static void getQRCode(String content, OutputStream output) throws IOException { BufferedImage image = crateQRCode(content, WIDTH, HEIGHT, null, null, null); ImageIO.write(image, IMAGE_FORMAT, output); } public static void getQRCode(String content, String logoUrl, OutputStream output) throws Exception { BufferedImage image = crateQRCode(content, WIDTH, HEIGHT, logoUrl, LOGO_WIDTH, LOGO_HEIGHT); ImageIO.write(image, IMAGE_FORMAT, output); } }
依赖
com.google.zxing javase3.3.3
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)