可扩展的登录——第三方登录

原创 2016年08月31日 09:41:56

   设计一个可扩展的登录系统,分为三步,首先要设计好数据表,其次拿到登录用户的cookie,然后再判断cookie是否有效

一、设计数据表

    常用的第三方登录有qq、微信、微博等。以微博登录为例,由于微博使用OAuth2协议登录,所以,一个登录用户会包含他的微博身份的ID,一个Access Token用于代表该用户访问微博的API和一个过期时间。只要添加另外一个表就可以解决这个问题,把这个表和用户表关联起来。每一种X-Auth表都存储了用户的登录认证信息,并通过user_id关联到Users表。这样一来,不但登录过程简化了,而且一个用户可以使用多种方式登录。只要登录成功,拿到了user_id,最后读取Users表是为了获得用户的Profile,这样读出来的数据也更安全,因为Users表不包含用户口令,不会因为暴露API而不小心把口令给泄露出去。

二、用户认证生成cookie

每个已认证用户的信息都必须通过Cookie来传递,服务器的session也无非是靠一个特殊名称的Cookie来识别而已,只不过由服务器本身帮你完成了解析Cookie、在session中查找User的过程,而代价却是内存占用高,单台服务器变成有状态,无法简单扩展成集群。遇到不懂事的年轻人,什么都敢往session里扔,很快就把服务器搞死了。

确认用户身份,我们需要一个统一的Authenticator接口

public interface Authenticator {
    // 认证成功返回User,认证失败抛出异常,无认证信息返回null:
    User authenticate(HttpServletRequest request, HttpServletResponse response) throws AuthenticateException;
}
接下来,对于每一种类型的认证,我们都编写一个对应的Authenticator的实现类。例如,针对表单登录后的Cookie,需要一个LocalCookieAuthenticator

public LocalCookieAuthenticator implements Authenticator {
    public User authenticate(HttpServletRequest request, HttpServletResponse response) {
        String cookie = getCookieFromRequest(request, 'cookieName');
        if (cookie == null) {
            return null;
        }
        return getUserByCookie(cookie);
    }
}

对于直接用Basic认证的Authorization Header,我们需要一个BasicAuthenticator
public BasicAuthenticator implements Authenticator {
    public User authenticate(HttpServletRequest request, HttpServletResponse response) {
        String auth = getHeaderFromRequest(request, "Authorization");
        if (auth == null) {
            return null;
        }
        String username = parseUsernameFromAuthorizationHeader(auth);
        String password = parsePasswordFromAuthorizationHeader(auth);
        return authenticateUserByPassword(username, password);
    }
}

对于用API Token认证的方式,同样编写一个APIAuthenticator

public APIAuthenticator implements Authenticator {
    public User authenticate(HttpServletRequest request, HttpServletResponse response) {
        String token = getHeaderFromRequest(request, "X-API-Token");
        if (token == null) {
            return null;
        }
        return authenticateUserByAPIToken(token);
    }
}
然后在一个统一的入口处,例如Filter里面,把这些Authenticator全部串起来,让它们依次自己去尝试认证:

public class GlobalFilter implements Filter {
    // 所有的Authenticator都在这里:
    Authenticator[] authenticators = initAuthenticators();

    // 每个页面都会执行的代码:
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
        User user = null;
        for (Authenticator auth : this.authenticators) {
            user = auth.authenticate(request, response);
            if (user != null) {
                break;
            }
        }
        // user放哪?
        chain.doFilter(request, response);
    }
}
认证成功后的User对象放到一个与业务逻辑相关的地方了,比如UserContext
public class GlobalFilter implements Filter {
    Authenticator[] authenticators = initAuthenticators();

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
        // 链式认证获得User:
        User user = tryGetAuthenticatedUser(request, response);
        // 把User绑定到UserContext中:
        try (UserContext ctx = new UserContext(user)) {
            chain.doFilter(request, response);
        }
    }
}
任何地方需要获得当前User

User user = UserContext.getCurrentUser();
三、生成cookie和认证cookie

防止伪造用单向函数,MD5就是单向函数。方法是计算hash的时候,不仅只包含用户口令,还包含Cookie过期时间,以及其他相关随机数,这样计算的hash就非常安全。

假设用户仍以用户名"admin",口令"hello"登录成功,系统可以知道:

  1. 该用户的id,例如,1230001
  2. 该用户的口令,例如,"hello"
  3. Cookie过期时间,可由当前时间戳+固定时长计算,例如,1461288165
  4. 系统固定的一个随机字符串,例如,"secret"

把上面4部分拼起来,得到:

"1230001:hello:1461288165:secret"

当浏览器发送Cookie回服务器时,我们就可以按照下面的方式验证Cookie:

  1. 把Cookie分割成三部分,得到用户id,过期时间和hash值;
  2. 如果过期时间已到,直接丢弃;
  3. 根据用户id查找用户,得到用户口令;
  4. 按照生成Cookie时的算法计算md5,与Cookie自带的hash值对比。

如果用户自己对Cookie进行修改,无论改用户id、过期时间,还是hash值,都会导致最终计算结果不一致。

即使用户知道自己的id和口令,也知道服务器的生成算法,他也无法自己构造出有效的Cookie,原因就在于计算hash时的“系统固定的随机字符串”他不知道。

这个“系统固定的随机字符串”还有一个用途,就是编写代码的开发人员不知道生产环境服务器配置的随机字符串,他也无法伪造Cookie。

md5算法还可以换成更安全的sha1/sha256。

绑定用户

把User用ThreadLocal绑定到当前处理线程:

public class UserContext {
    public static final ThreadLocal<User> current = new ThreadLocal<User>();
}

别忘了开闭原则“对扩展开放,对修改关闭”

public class UserContext implements AutoCloseable {

    static final ThreadLocal<User> current = new ThreadLocal<User>();

    public UserContext(User user) {
        current.set(user);
    }

    public static User getCurrentUser() {
        return current.get();
    }

    public void close() {
        current.remove();
    }
}

单与否不看代码量本身,而是看调用起来是不是简单。在Filter中调用起来就非常简单:

public class MyFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
        User user = tryGetAuthenticatedUser(request, response);
        try (UserContext context = new UserContext(user)) {
            chain.doFilter(request, response);
        }
    }
}
使用场景如:

try (UserContext context = new UserContext(user)) {
    // 当前用户是user:
    processProfile(UserContext.getCurrentUser());
    // 需要更高权限的admin才能执行的操作怎么办?
    // 方法是获取一个admin用户:
    try (UserContext context = new UserContext(getAdmin())) {
        // 现在的当前用户是admin:
        processAdminJob(UserContext.getCurrentUser());
    }
    // 现在当前用户又自动变回了普通user:
    processProfile(UserContext.getCurrentUser());
}





版权声明:本文为博主原创文章,未经博主允许不得转载。

关于第三方登录,你应该知道的

随着国内及国外巨头们的平台开放战略以及移动互联网的发展,第三方登录已经不是一个陌生的产品设计概念了。那么,究竟该如何认识第三方登录,又该如何正确地设计的第三方登录?这些问题其实细节还是蛮多的,所以,笔...
  • baidu_37464759
  • baidu_37464759
  • 2017年02月07日 09:56
  • 4721

php实现第三方登录

1. oAuth2.0原理 网站为了方便用户快速的登录系统,都会提供使用知名的第三方平台账号进行快速登录的功能,第三方登录都是基于oAuth2.0标准来实现的。下面详细分析【基于账号密码授权】和【基于...
  • villainy13579
  • villainy13579
  • 2017年04月18日 18:20
  • 3620

第三方登录(QQ登录)开发流程详解

第三方登录(QQ登录)开发流程详解   近排由于工作的繁忙,已经一个星期没写博文做分享了,接下来我对网站接入第三方登录----QQ登录的实现逻辑做一个详细的讲解。   对于整个流程...
  • lihe460186709
  • lihe460186709
  • 2017年03月06日 11:36
  • 3243

前端第三方登录集合,QQ、微信、微博

从http://www.cnblogs.com/v-weiwang/p/5732423.html转载的 申请开发者账号之内的就不累赘了,网上一大堆: 说下需求,一个网页要在三类容器运行,公司app...
  • TivonaLH
  • TivonaLH
  • 2017年03月09日 09:28
  • 4950

php中关于qq第三方登录

花了两天多的时间研究了qq的第三方登录,因为用的是基于tp5的自己公司的框架,所以有的路径之类的问题还是得自己研究的,这篇博客只是对主要流程的简单概述。 一、申请自己或自己公司的相关appid和ap...
  • qq_37542212
  • qq_37542212
  • 2017年06月03日 09:36
  • 888

用shareSdk实现的第三方登录与分享

版权声明:本文为博主原创文章,未经博主允许不得转载。说到第三方登录与分享,其实有很多sdk都支持,不过最常用的还算友盟和mob了,我今天打算记录一下sharesdk的第三方登录与分享,其实也就是简单实...
  • zr940326
  • zr940326
  • 2016年06月21日 22:20
  • 1455

简单的faceBook第三方登录Demo

登录Facebook官网注册一个开发者账号:https://developers.facebook.com/创建一个应用(APPS)在自己创建的应用中可以看到自己的应用编号和应用密钥在设置中可以创建一...
  • mxy_0223
  • mxy_0223
  • 2017年04月13日 10:09
  • 6229

web第三方登录接入

其实最麻烦的是在平台申请各种权限,申请地址 1、微博接入(open.weibo.com): 2、QQ接入(open.qq.com) 3、微信接入(open.weixin.qq.com) ...
  • qssjtzj
  • qssjtzj
  • 2015年07月31日 11:48
  • 583

第三方登录功能的实现

刚开始做的时候感觉高大上 为什么要使用第三方登录:一般稍微作为一个大点的项目,为了提高用户的群体都会做第三方登录(如:QQ,微信,新浪等) 在往下看之前先注册第三方网站的开发者账号,创建应用完成审核...
  • CSDN3436
  • CSDN3436
  • 2017年03月28日 17:13
  • 7438

关于接入新浪微博第三方登录

近期,做一个关于联合第三方平台的登录接入,初次接触开放平台,在此做个笔记   开发之前的准备如下:   1、注册新浪微博   2、访问新浪微博开发平台http://open.weib...
  • candle806
  • candle806
  • 2014年03月14日 17:11
  • 45057
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:可扩展的登录——第三方登录
举报原因:
原因补充:

(最多只允许输入30个字)