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
* @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条)