GeekTop
  • 首页
  • 关于

GeekTop

不止代码

设计一个可扩展的用户模型

设计一个可扩展的用户模型

2023年1月12日 Alex Comments 0 Comment

设计表结构

在一个互联网产品中,用户登录是一个非常基础功能。比如说最常见的用户名+密码的登录方式。遇到这种登录方式,也许我们首先想到的就是创建一张 User 表,包含 username 和 password 两个字段,这样一个最简单的用户系统就被我们实现了,像下面这样:

id name password 其它字段
1 alice 12345
2 bob 12345

但是,随着系统的不断演化,我们可能需要支持微信登录,于是我们往这张表里加了一些字段:

id name password wechat_id wechat_access_token wechat_expires_in 其它字段
1 alice 12345 123 xxxxxxxx 7200
2 bob 12345

后来,随着业务的不断发展,我们的系统还需要支持微博登录、QQ 登录,于是这个表又要增加几列。难道说后面每增加一种外部登录方式,我们都要修改表的结构吗?如果这样扩展下去,仅仅修改表的结构都会让人筋疲力尽,更不用说修改代码。

如何设计一个灵活高效的登录系统?我们需要一种可扩展的表结构。

首先分析用户的登录行为,也许用户每次都是微信扫码登录,这种情况下,用户就并不需要存储密码。其次,不管用户以哪种方式登录,登录完成后看到的都是个人信息,不管以哪种方式登录,本质上都是属于用户认证的行为。

于是,我们可以考虑,把用户的 User Profile 和认证信息分开存储。User 表只存储 Profile 信息,如下:

id name nick_name birth等等
1 alice
2 bob

把用户密码单独存储在一张表里,避免在查询用户 Profile 的时候同时也把密码带出来,这是不必要的,密码只在认证时有用,同时有些用户不一定有密码,和 Profile 存在一张表里也造成空间的浪费。

id user_id password
1 2 xxxxxxx

同时,我们也把外部 OAuth2 登录认证信息单独存放在一张表里:

id user_id oauth_name oauth_id access_token refresh_token
1 2 QQ q-dfkk-123 xxxxx xxxx
2 3 wechat w-kkkh-421 xxxx xxxx

后面再对接新的基于 OAuth2 的登录认证方式都可以基于这张表。

通过上面的改造,现在一个用户可以支持多种登录方式,每一种登录方式在登录后都可以通过 user_id 来获取 user 表里的 User Profile 信息,并且 user 表不存储认证信息,这样做到了敏感信息的隔离。

密码、身份证ID等敏感信息如何存储

到底怎么样存储用户的敏感信息,我觉得有两点需要注意:1) 用户的敏感信息绝对不能写到日志里面,之前发生的携程CVV码泄露的事件就是一个例子;2) 用户的敏感信息要加密存储。

说到敏感信息加密,第一个复现在脑海里的可能是MD5,SHA1等单向Hash算法。这类算法在一定程度上,确实可以起到保密的作用。但是破解的手段也非常简单,我们可以通过一台性能较好的计算机去无限次的重试,俗称「撞库」,或者我直接把一些常用密码的Hash值算出来,建立反查表。

大量的实践证明,单向的Hash算法是不安全的。于是有些公司去尝试在密码中加入salt的做法,大概类似于md5(salt+password)。如果这个salt是固定的,那么需要保护好这个salt。

事实上,现在可以通过彩虹表来对密码进行暴力破解。彩虹表通常会在100GB以上,和反查表不同的是,彩虹表存储的不是明文和密文的对应信息,而是存储了一条散列链。

尽管彩虹表很强大,但是加盐以后会大大增加破解的难度。例如,我们可以使用PBKDF2算法,该算法原理大致相当于在HASH算法基础上增加随机盐,并进行多次HASH运算,随机盐使得彩虹表的建表难度大幅增加,而多次HASH也使得建表和破解的难度都大幅增加。使用PBKDF2算法时,HASH算法一般选用sha1或者sha256,随机盐的长度一般不能少于8字节,HASH次数至少也要1000次,这样安全性才足够高。

但是,随着现代硬件的发展,PBKDF2也不再是一种值得推荐的加密算法

现在Spring security一般推荐使用 Bcrypt、Scrypt、Argon2等算法,相对于传统的单向Hash,这都是是一些慢的加密算法,这种算法会加入随机salt,加入 pepper 并且经过多次 Hash。这会大大加大破解的难度,在有限的时间内几乎很难破解出原始信息,下面是 Java Password4j这个库整理的主流算法的比较:

认证如何实现

web 应用主流的认证方式有两种,基于 session/cookie 的认证方式和基于 token 的认证方式。由于 session/cookie 的认证方式不能脱离浏览器,传统的 web 应用主要使用这种方式,我们主要介绍基于 token 的认证方式。

基于 token 的认证方式又分为有状态的 token 和无状态的 token。

无状态的 token 主要是使用类似于 jwt 这种自包含用户信息的 token。由于这种 token 的颁发之后时没有办法撤销的,只能等到自然过期,所以应用并不广泛。

下面我们可以基于 Spring Security 实现基于 OAuth2 的用户认证过程。

大致看下 Spring Security 认证过程:

file

@Configuration
@EnableResourceServer
public class OAuth2ResourceServer extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .anyRequest()
            .authenticated()
        .and()
            .requestMatchers()
            .antMatchers("/api/**");
    }
}
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServer extends
        AuthorizationServerConfigurerAdapter {

    // 用户认证
    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints)
            throws Exception {
        endpoints.authenticationManager(authenticationManager);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients)
            throws Exception {
        clients.inMemory()
            .withClient("clientapp")
            .secret("112233")
            // 密码模式
            .authorizedGrantTypes("password")
            .scopes("read_userinfo", "read_contacts");
    }

}

以上是配置,代码并不完整,后面有时间写一写如何基于 Spring Security 实现生产可用的用户系统。


数据库, 编程

Post navigation

NEXT
姥姥
PREVIOUS
使用 Apple 的 Keychain 保存 SSH 的 passphase

发表回复 取消回复

您的电子邮箱地址不会被公开。 必填项已用*标注

最近文章

  • 姥姥
  • 设计一个可扩展的用户模型
  • 使用 Apple 的 Keychain 保存 SSH 的 passphase
  • 解决 ABA 问题
  • 关于 macOS 上面部分 emoji 无法显示的问题
  • 这些年我技术栈的变化
  • 搬瓦工、狗云、hostodo、oracle对比测试
  • Google Drive 无法上传文件
  • 使用 socks5 代理 git ssh 协议
  • 到底要不要“润”
  • 可复用的代码
  • 关于疫情的一点点反思
  • 我的风控策略(投资篇)
  • 我的风控策略(生活篇)
  • Spring Boot是如何处理异常的
  • 编码与解码
  • 基于统计的图像目标检索
  • 有限状态机和状态模式
  • API安全设计
  • 用户密码的存储策略

近期评论

  • ǝɔɐǝԀʎzɐɹϽ发表在《可复用的代码》
  • Alex发表在《可复用的代码》
  • ǝɔɐǝԀʎzɐɹϽ发表在《到底要不要“润”》
  • ǝɔɐǝԀʎzɐɹϽ发表在《可复用的代码》
  • ǝɔɐǝԀʎzɐɹϽ发表在《我的风控策略(生活篇)》
  • ǝɔɐǝԀʎzɐɹϽ发表在《如何写出简洁优雅的代码》
  • 张志亮发表在《如何写出简洁优雅的代码》

分类

  • AI (2)
  • Java应用安全之道 (1)
    • 加密与解密 (1)
  • Odoo (2)
  • Python (1)
  • 图像处理 (1)
  • 年鉴 (1)
  • 数据库 (10)
  • 编程 (14)
    • Spring (1)
  • 读书笔记 (2)
  • 运维 (5)
  • 随笔 (10)

归档

  • 2023年1月 (2)
  • 2022年8月 (1)
  • 2022年7月 (4)
  • 2022年6月 (2)
  • 2022年5月 (2)
  • 2022年4月 (3)
  • 2021年10月 (1)
  • 2021年7月 (1)
  • 2021年5月 (1)
  • 2020年11月 (1)
  • 2020年7月 (1)
  • 2020年3月 (2)
  • 2020年2月 (1)
  • 2019年1月 (1)
  • 2018年12月 (2)
  • 2018年11月 (2)
  • 2017年4月 (1)
  • 2016年11月 (1)
  • 2016年9月 (1)
  • 2016年7月 (1)
  • 2016年5月 (3)
  • 2016年4月 (2)
  • 2016年3月 (1)
  • 2016年2月 (2)
  • 2015年12月 (1)
  • 2015年11月 (2)
  • 2015年8月 (1)
  • 2015年4月 (1)
  • 2015年3月 (1)

标签

Database devops Java MySQL PostgreSQL Python shell Spring SpringBoot Spring Security 安全 年鉴 总结 编程 随笔
© 2015-2023   Geektop.net All Rights Reserved.