前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >谷歌authenticator接入与使用

谷歌authenticator接入与使用

作者头像
叔牙
发布2023-08-09 15:07:59
5.2K1
发布2023-08-09 15:07:59
举报
文章被收录于专栏:一个执拗的后端搬砖工

内容目录

一、authenticator解决了什么问题二、authenticator的原理三、springboot集成authenticator四、做成可复用starter五、参考

一、authenticator解决了什么问题

1.authenticator是什么?

谷歌Authenticator是谷歌推出的一种双因素身份验证应用程序。它是一种为用户提供额外层次的账户保护的安全工具。传统的认证方式通常只依赖于用户名和密码,而双因素身份验证则需要用户提供两个不同类型的验证信息,以增加账户的安全性。

谷歌Authenticator通过生成动态的一次性密码来实现双因素身份验证。当你登录一个已启用谷歌Authenticator的系统或服务时时,需要输入用户名和密码,然后打开 Authenticator 应用来获取当前的一次性密码。这个密码每30秒钟更新一次,只在短暂的时间段内有效。

这种一次性密码是通过基于时间戳的算法计算得出的,同时还需要通过与账户绑定的密钥进行验证。由于每个密码只在极短的时间内有效,并且密码是动态变化的,即使有人获得了您的用户名和密码,他们也无法成功登录您的账户,因为他们没有有效的一次性密码。

谷歌Authenticator还可以与多个账户关联,这意味着您可以在一个应用中管理多个账户的一次性密码。它对于保护您的各种在线账户(如电子邮件、社交媒体、金融服务)非常有用。

简而言之,谷歌Authenticator是一种提供额外层次安全保护的双因素身份验证应用程序。它通过生成动态的一次性密码来增加账户的安全性,并在登录过程中要求用户提供额外的验证信息。

2.它本质上解决了什么问题?

谷歌Authenticator本质上解决了以下问题:

  • 强化账户安全性:谷歌 Authenticator 提供了一种额外的身份验证层,以保护您的帐户免受未经授权的访问。即使有人获得了您的用户名和密码,他们仍然需要有效的一次性密码才能成功登录。
  • 降低密码泄露风险:由于谷歌 Authenticator 生成的一次性密码在每30秒钟更新一次,并且只在特定的时间段内有效,即使密码泄露,攻击者也只能在一个非常短的时间窗口内进行利用。这大大降低了密码被滥用的风险。
  • 抵御钓鱼和网络针对性攻击:通过生成每30秒钟更改的动态一次性密码,谷歌 Authenticator 防止了恶意用户和攻击者使用被窃取的认证凭据进行登录。这增加了实施钓鱼和网络针对性攻击的难度。
  • 提供离线身份验证:由于一次性密码是基于时间戳计算的,所以即使在没有网络连接的情况下,您仍然可以进行身份验证。这对于旅行、临时网络中断或无法接收短信验证码的情况非常有用。

总之,谷歌Authenticator增加了双因素身份验证的安全性,提供了一种简便而有效的方式来保护您的帐户免受未经授权访问和针对性攻击的威胁。

二、authenticator的原理

1.基于时间的TOTP

谷歌Authenticator是基于TOTP算法实现的验证方式,TOTP(Time-Based One-Time Password是谷歌Authenticator中使用的一种身份验证方法。它基于时间的动态密码算法,用于生成一次性密码(One-Time Passwords)。

当启用谷歌Authenticator并为特定帐户配置时,它会与该帐户关联一个密钥。该密钥私密地存储在您的设备上。每30秒钟,该密钥都会与当前时间戳进行计算,并生成一个新的一次性密码。

当您需要进行身份验证时,您可以打开谷歌Authenticator应用程序,输入相关帐户的用户名,然后应用程序会基于与服务器同步的时间戳生成相应的一次性密码。您将此密码输入到身份验证页面或应用程序中,以确认您是合法用户。

TOTP 提供了一种额外的安全层次,因为即使有人获得了您的用户名和密码,仍然需要一个有效的一次性密码才能访问您的帐户。这增加了保护您的帐户免受未经授权访问的可能性。

TOTP是HOTP的一个变种,将HOTP中的计数器C替换为依托时间的参数T,T是由当前时间(CurrentUnixTime、初始时间(T0)、步长(X)决定的。即:

代码语言:javascript
复制
$$ T = (Current Unix time - T0) / X $$
  • CurrentUnixTime:当前的Unix时间。
  • T0: 开始计步初始化时间,默认为0
  • X : 步长,默认情况下为30s

TOTP 是谷歌 Authenticator 中使用的基于时间的动态密码算法,提供了一种增强的身份验证机制,以确保只有授权用户能够访问其帐户。

2.认证流程与原理
  • 登录成功,由服务端程序生成随机秘钥,通过二维码返回给客户端
  • authenticator客户端扫描二维码或者手动输入秘钥进行绑定
  • 应用程序使用authenticator生成的验证码请求服务端验证

三、springboot集成authenticator

1.引入依赖
代码语言:javascript
复制
<dependency>
  <groupId>com.warrenstrange</groupId>
  <artifactId>googleauth</artifactId>
</dependency>
<dependency>
  <groupId>com.google.zxing</groupId>
  <artifactId>javase</artifactId>
</dependency>

googleauth 是一个开源的 Java 库,用于在 Java 应用程序中实现谷歌 Authenticator 功能。它提供了一组类和方法,让开发人员能够轻松地集成谷歌 Authenticator 的功能到他们的 Java 应用程序中。googleauth 库使用谷歌 Authenticator 的算法来生成一次性密码,并提供了验证这些密码的功能。

2.编写实现

编写生成秘钥、二维码以及code验证工具类:

代码语言:javascript
复制
@Slf4j
@Service
public class GoogleAuthenticatorService {
    private static final GoogleAuthenticator googleAuthenticator = new GoogleAuthenticator();
    private static final String KEY_FORMAT = "otpauth://totp/%s?secret=%s";
    private static final String IMAGE_EXT = "png";
    private static final int WIDTH = 300;
    private static final int HEIGHT = 300;
    @Autowired
    private UserDao userDao;
    @PostConstruct
    public void init() {
        googleAuthenticator.setCredentialRepository(new ICredentialRepository() {
            @Override
            public String getSecretKey(String userName) {
                return userDao.getSecretKey(userName);
            }
            @Override
            public void saveUserCredentials(String userName, String secretKey, int validationCode, List<Integer> scratchCodes) {
                userDao.saveUserCredentials(userName, secretKey);
            }
        });
        log.info("GoogleAuthenticator初始化成功...");
    }


    /**
     * 生成二维码链接
     */
    private String getQrUrl(String username) {
        //调用createCredentials都会生成新的secretKey
        GoogleAuthenticatorKey key = googleAuthenticator.createCredentials(username);
        log.info("username={},secretKey={}", username, key.getKey());
        return String.format(KEY_FORMAT, username, key.getKey());
    }
    /**
    * 验证code
    */
    public boolean validCode(String username, int code) {
        return googleAuthenticator.authorizeUser(username, code);
    }
    /**
     * 生成二维码
     */
    public void genQRImage(String username, ServletOutputStream stream) {
        try {
            String content = getQrUrl(username);
            BitMatrix bm = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, WIDTH, HEIGHT);
            MatrixToImageWriter.writeToStream(bm, IMAGE_EXT, stream);
        } catch (WriterException | IOException e) {
            log.error("occur error",e);
        }
    }
} 

编写逻辑入口:

代码语言:javascript
复制
@Slf4j
@Controller
public class IndexController {


    @Autowired
    private GoogleAuthenticatorService googleAuthenticatorService;
    /**
     * 二次验证,生成二维码
     */
    @RequestMapping("/qrcode")
    public void qrcode(String username, HttpServletResponse response) {
        try (ServletOutputStream stream = response.getOutputStream()) {
            googleAuthenticatorService.genQRImage(username, stream);
        } catch (IOException e) {
            log.error("occur error", e);
        }
    }
    /**
     * 二次验证,输入google authenticator上的6位数字,成功跳转到首页
     */
    @RequestMapping("/verify")
    public String verify(String username, int code) {


        boolean validCode = googleAuthenticatorService.validCode(username, code);
        if (validCode){
            return "index";
        }
        return "error";
    }
}
3.使用方式
  • 用户下载authenticator,如果已经下载可跳过
  • 使用账密登录系统,如果没有绑定过authenticator,弹出二维码
  • 使用authenticator扫描二维码进行秘钥绑定,如果已经绑定过跳过
  • 使用authenticator生成的6为数字输入到系统进行验证

四、做成可复用starter

1.与会话同生命周期

用户登录成功后,需要验证authenticator验证码才能跳转到引导页,也就说明一次性验证码验证态与登录态是强绑定的。

  • 用户登录一定触发TOTP验证
  • authenticator验证后有状态标记
  • 如果用户登录成功TOTP验证之前关闭或跳转,对访问其他路径需要拦截
  • 脱离页面的api直接调用需要校验登录态和TOTP验证态
  • 用户主动登出或登录态过期,同时将验证态失效
2.做成拦截器

拦截登录接口,把登录路径做成配置化,由starter读取,登录完成后跳转到绑定authenticator或者输入验证码。并且对于其他路径也要同时校验登录态和TOTP验证态。

3.自主控制开启

通过配置控制开启全局authenticator验证能力,包含绑定和code验证。

在用户管理维度,做成可视化能力,可在用户粒度控制是否启用authenticator二次认证

4.可复用

前边我们把接入authenticator二次验证直接写入到了springboot项目中,那么如果有其他项目要接入,还是要从头到尾写一遍,所以我们可以写成springboot-starter的方式,做成可复用的能力。

属性配置:

代码语言:javascript
复制
@ConfigurationProperties(prefix = "application.url")
@Data
public class GoogleAuthenticatorProperties {
  //是否启用
    private boolean enabled;


    //注册路径
    private String register;


    //登录路径
    private String login;


    //验证code路径
    private String verify;


}

设置是否启用Authenticator以及登录注册路径。

拦截器:

代码语言:javascript
复制
@Slf4j
public class GoogleAuthenticatorInterceptor implements HandlerInterceptor {


    @Autowired
    GoogleAuthenticatorProperties googleAuthenticatorProperties;


    @Autowired
    private GoogleAuthenticatorService googleAuthenticatorService;


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String url = request.getRequestURI();
        if(Objects.equals(url,googleAuthenticatorProperties.getRegister())
                || Objects.equals(url,googleAuthenticatorProperties.getLogin())
                || Objects.equals(url,googleAuthenticatorProperties.getVerify())) {
            return true;
        }
        String token = request.getHeader("token");
        //todo 检查token有效性和合法性,以及从服务端能否拿到TOTP验证态
        if(!(this.checkTokenValid(token) && this.checkTOTPValid(token))) {
            return false;
        }
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }


    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        String url = request.getRequestURI();
        String username = request.getParameter("username");
        //如果是登录,登录成功后跳转到绑定Authenticator秘钥或者输入code
        if(Objects.equals(url,googleAuthenticatorProperties.getLogin())) {
            try (ServletOutputStream stream = response.getOutputStream()) {
                googleAuthenticatorService.genQRImage(username, stream);
                return;
            } catch (IOException e) {
                log.error("发生错误", e);
            }
        }
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }
}

如果是登录、注册或者验证code路径,preHandle跳过校验,否则检查用户登录态和code验证态。对于postHandle处理完请求返回数据视图之前,如果检查是登录请求,那么返回绑定TOTP验证二维码或者输入code表单。

自动注入配置类:

代码语言:javascript
复制
@Configuration
@EnableConfigurationProperties(GoogleAuthenticatorProperties.class)
@ConditionalOnProperty(prefix = "application.url", name = "enabled", havingValue = "true")
public class GoogleAuthAutoConfiguration {
    @Bean
    public GoogleAuthenticatorService googleAuthenticatorService() {
        return new GoogleAuthenticatorService();
    }
    @Bean
    public GoogleAuthenticatorInterceptor googleAuthenticatorInterceptor() {
        return new GoogleAuthenticatorInterceptor();
    }
    @Configuration
    public class CustomInterceptorConfig implements WebMvcConfigurer {
        private GoogleAuthenticatorInterceptor googleAuthenticatorInterceptor;


        public CustomInterceptorConfig() {
            googleAuthenticatorInterceptor = googleAuthenticatorInterceptor();
        }
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(googleAuthenticatorInterceptor)
                    .addPathPatterns("/**") // 可以根据需要指定拦截的路径
                    .order(Ordered.LOWEST_PRECEDENCE); // 设置执行顺序为最低优先级
        }
    }
}

主要生命相关依赖的bean以及拦截器,拦截器优先级会放到比较低的位置,从而不影响springboot项目本身的拦截器执行顺序。

入口配置:

在starter模块创建META-INF/spring.factories配置

代码语言:javascript
复制
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
authenticator.GoogleAuthAutoConfiguration

在依赖该starter的springboot项目启动时会自动加载并解析实例化配置类。

上述步骤是主要的步骤和实现,在具体编码实现时还需要调整和打磨细节问题,完成上述步骤后把starter打成jar,在springboot应用引入并做好相关配置就能使用authenticator能力了。

五、参考

https://github.com/wstrange/GoogleAuth

https://rstyro.github.io/blog/2019/04/29/SpringBoot-Google%E4%BA%8C%E6%AD%A5%E9%AA%8C%E8%AF%81/

https://www.cnblogs.com/50614090/p/5848409.html

https://juejin.cn/post/6898891886677721102

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-07-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 PersistentCoder 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、authenticator解决了什么问题
    • 1.authenticator是什么?
      • 2.它本质上解决了什么问题?
      • 二、authenticator的原理
        • 1.基于时间的TOTP
          • 2.认证流程与原理
          • 三、springboot集成authenticator
            • 1.引入依赖
              • 2.编写实现
                • 3.使用方式
                • 四、做成可复用starter
                  • 1.与会话同生命周期
                    • 2.做成拦截器
                      • 3.自主控制开启
                        • 4.可复用
                        • 五、参考
                        相关产品与服务
                        验证码
                        腾讯云新一代行为验证码(Captcha),基于十道安全栅栏, 为网页、App、小程序开发者打造立体、全面的人机验证。最大程度保护注册登录、活动秒杀、点赞发帖、数据保护等各大场景下业务安全的同时,提供更精细化的用户体验。
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档