SpringBoot-Google二步验证

SpringBoot-Google二步验证

2023年7月27日发(作者:)

SpringBoot-Google⼆步验证SpringBoot-Google⼆步验证概念:Google⾝份验证器Google Authenticator是⾕歌推出的基于时间的⼀次性密码(Time-based One-time Password,简称TOTP),只需要在⼿机上安装该APP,就可以⽣成⼀个随着时间变化的⼀次性密码,⽤于帐户验证。Google⾝份验证器是⼀款基于时间与哈希的⼀次性密码算法的两步验证软件令牌,此软件⽤于Google的认证服务。此项服务所使⽤的算法已列于RFC 6238和RFC 4226中。⼀、流程⽤户请求服务器⽣成密钥服务器⽣成⼀个密钥并与⽤户信息进⾏关联,并返回密钥(类似:XX57HWC7D2FA4X4GLOHOASTGPMVI5EFA)和⼀个⼆维码信息(此步骤还没有绑定)⽤户把返回的⼆维码信息传给服务器,⽣成⼀个⼆维码信息⼤概长这样的:otpauth://totp/https%3A%2F%%3Arstyro?secret=XX57HWC7D2FA4X4GLOHOASTGPMVI5EFA&issuer=https%3A%2F%⽤户通过⾝份验证器扫描⼆维码即可⽣成⼀个动态的验证码⽤户传当前动态的验证码和密钥给服务器,校验密钥的正确性(此密钥与⽤户真正的绑定)上⾯有些步骤不必须的,看需求,可以简化为两步。1、⽣成密钥2、扫码上⾯那么多只是为了准确性⽽已⼆、安装⾝份验证器IOS 版本:可以在App Store搜索google authenticator安卓:客户端每30秒就会⽣成新的验证码界⾯⼤概如下:三、代码实现1、前⾔为了⽐较真实所以添加了注册和登录接⼝为了⽅便集成了Swagger-ui 和全部是GET请求,不会⽤Swagger-ui,就直接地址栏请求或者Postman都可注册⽤户全部放Redis登录与Google校验,也以注解⽅式实现登录以token⽅式,所以请求其他接⼝的时候都要带上token,可以把token放在header⾥⾯2、代码UserController控制层接⼝流程从上往下执⾏即可import ;import ration;import red;import ller;import tion.*;import gin;import ntroller;import ;import DTO;import TO;import rvice;import Util;import O;import rvletResponse;import edImage;import Stream;@Controller@RequestMapping("/user")@Api(tags = "⽤户模块")public class UserController extends BaseController { @Autowired private UserService userService; @GetMapping("/register") @ApiOperation("注册") @ResponseBody public Result register(LoginDTO dto) throws Exception { return er(dto); } @GetMapping("/login") @ApiOperation("登录") @ResponseBody public Result login(LoginDTO dto)throws Exception{ return (dto); } @GetMapping("/generateGoogleSecret") @ResponseBody @NeedLogin @ApiOperation("⽣成google密钥") public Result generateGoogleSecret()throws Exception{ return teGoogleSecret(r()); } /** * 显⽰⼀个⼆维码图⽚ * @param secretQrCode generateGoogleSecret接⼝返回的:secretQrCode * @param response * @throws Exception */ @GetMapping("/genQrCode") @ApiOperation("⽣成⼆维码") public void genQrCode(String secretQrCode, HttpServletResponse response) throws Exception{ tentType("image/png"); OutputStream stream = putStream(); (secretQrCode,stream); } @GetMapping("/bindGoogle") @ResponseBody @NeedLogin @ApiOperation("绑定google验证") public Result bindGoogle(GoogleDTO dto)throws Exception{ return ogle(dto,r(),uest()); } @GetMapping("/googleLogin") @ResponseBody @NeedLogin @ApiOperation("google登录") public Result googleLogin(Long code) throws Exception{ return Login(code,r(),uest()); } @GetMapping("/getData") @NeedLogin(google = true) @ApiOperation("获取⽤户数据与token数据,前提需要google认证才能访问") @ResponseBody public Result getData()throws Exception{ return a(); }}UserService服务层,业务代码import red;import emplate;import e;import Utils;import ;import .*;import DTO;import TO;import ;import Authenticator;import ;import rvletRequest;import p;import or;import ;import ;import it;@Servicepublic class UserService { @Autowired private RedisTemplate redisTemplate; /** * 获取缓存中的数据 * @return */ public Result getData(){ Map data = new HashMap<>(); setData(ER_USER_KEY,data); setData(_KEY_LOGIN_KEY,data); return (data); } public void setData(String keyword,Map data){ Set keys = (keyword); Iterator iterator = or(); while (t()){ String key = (); (key,Value().get(key)); } } /** * 注册 * @param dto * @return * @throws Exception */ public Result register(LoginDTO dto) throws Exception { User user = new User(); rId(D()); rname(rname()); sword(5(sword())); addUser(user); return (); } //获取⽤户 public User getUser(String username){ User cacheUser = (User) Value().get((ER_USER, username)); return cacheUser; } //添加注册⽤户 public void addUser(User user){ if(user == null) throw new ApiException(_NULL); User isRepeat = getUser(rname()); if(isRepeat != null ){ throw new ApiException(_IS_EXIST); } Value().set((ER_USER, rname()),user,1, ); } //更新token⽤户 public void updateUser(User user,HttpServletRequest request){ if(user == null) throw new ApiException(_NULL); Value().set(enKey(request,),user,1, ); } /** * 登录 * @param dto * @return * @throws Exception */ public Result login(LoginDTO dto) throws Exception { User user = getUser(rname()); if(user == null){ throw new ApiException(_NOT_EXIST); } if(!sword().equals(5(sword()))){ throw new ApiException(ME_OR_PASSWORD_IS_WRONG); } //随机⽣成token String token = D(); Value().set((_KEY_LOGIN,token),user,1,); Map data = new HashMap<>(); (,token); return (data); } /** * ⽣成Google 密钥 * secret:密钥 * secretQrCode:Google Authenticator 扫描条形码的内容 * @param user * @return */ public Result generateGoogleSecret(User user){ //Google密钥 String randomSecretKey = domSecretKey(); String googleAuthenticatorBarCode = gleAuthenticatorBarCode(randomSecretKey, rname(), ""); Map data = new HashMap<>(); //Google密钥 ("secret",randomSecretKey); //⽤户⼆维码内容 ("secretQrCode",googleAuthenticatorBarCode); return (data); } /** * 绑定Google * @param dto * @param user * @return */ public Result bindGoogle(GoogleDTO dto, User user, HttpServletRequest request){ if(!y(gleSecret())){ throw new ApiException(_IS_BIND); } boolean isTrue = _code(ret(), e(), tTimeMillis()); if(!isTrue){ throw new ApiException(_CODE_NOT_MATCH); } User cacheUser = getUser(rname()); gleSecret(ret()); updateUser(cacheUser,request); return (); } /** * Google登录 * @param code * @param user * @return */ public Result googleLogin(Long code,User user,HttpServletRequest request){ if(y(gleSecret())){ throw new ApiException(_NOT_BIND); } boolean isTrue = _code(gleSecret(), code, tTimeMillis()); if(!isTrue){ throw new ApiException(_CODE_NOT_MATCH); } Value().set(enKey(request,),S,1,); return (); }}GoogleAuthenticatorGoogle⾝份验证器⼯具类import eFormat;import ormatWriter;import Exception;import ToImageWriter;import rix;import 32;import ;import ;import KeySpec;import tputStream;import ption;import ortedEncodingException;import oder;import dKeyException;import AlgorithmException;import Random;public class GoogleAuthenticator { public static String getRandomSecretKey() { SecureRandom random = new SecureRandom(); byte[] bytes = new byte[20]; tes(bytes); Base32 base32 = new Base32(); String secretKey = ToString(bytes); // make the secret key more human-readable by lower-casing and // inserting spaces between each group of 4 characters return rCase(); // .replaceAll("(.{4})(?=.{4})", "$1 "); } public static String getTOTPCode(String secretKey) { String normalizedBase32Key = e(" ", "").toUpperCase(); Base32 base32 = new Base32(); byte[] bytes = (normalizedBase32Key); String hexKey = HexString(bytes); long time = (tTimeMillis() / 1000) / 30; String hexTime = tring(time); return teTOTP(hexKey, hexTime, "6"); } public static String getGoogleAuthenticatorBarCode(String secretKey, String account, String issuer) { String normalizedBase32Key = e(" ", "").toUpperCase(); try { return "otpauth://totp/" + (issuer + ":" + account, "UTF-8") .replace("+", "%20") + "?secret=" + (normalizedBase32Key, "UTF-8").replace( "+", "%20") + "&issuer=" + (issuer, "UTF-8").replace("+", "%20"); } catch (UnsupportedEncodingException e) { throw new IllegalStateException(e); } } public static void createQRCode(String barCodeData, String filePath, int height, int width) throws WriterException, IOException { BitMatrix matrix = new MultiFormatWriter().encode(barCodeData, _CODE, width, height); try (FileOutputStream out = new FileOutputStream(filePath)) { oStream(matrix, "png", out); } } static int window_size = 3; // default 3 - max 17 (from google docs)最多可偏移的时间 /** * set the windows size. This is an integer value representing the number of * 30 second windows we allow The bigger the window, the more tolerant of * clock skew we are. *

* @param s * window size - must be >=1 and <=17. Other values are ignored */ public static void setWindowSize(int s) { if (s >= 1 && s <= 17) window_size = s; } /** * Check the code entered by the user to see if it is valid *

* @param secret * The users secret. * @param code * The code displayed on the users device * @param timeMsec * The time in msec (tTimeMillis() for example) * @return */ public static boolean check_code(String secret, long code, long timeMsec) { Base32 codec = new Base32(); byte[] decodedKey = (secret); // convert unix msec time into a 30 second "window" // this is per the TOTP spec (see the RFC for details) long t = (timeMsec / 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 = verify_code(decodedKey, t + i); } catch (Exception e) { // Yes, this is bad form - but // the exceptions thrown would be rare and a static // configuration problem // tackTrace(); throw new RuntimeException(sage()); // return false; } if (hash == code) { return true; } } // The validation code is invalid. 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 = tance("HmacSHA1"); (signKey); byte[] hash = l(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; }}代码地址Github:Gitee:如果觉得有⽤的话,点个Star呗

发布者:admin,转转请注明出处:http://www.yc00.com/news/1690463486a353015.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信