手动编码实现 facebook、google 第三方登陆
1、什么是 oauth2.0
开发授权(Open Authorization, OAuth)是一种资源提供商用于授权第三方应用代表资源所有者获取有限访问权限的授权机制。由于整个授权过程中,第三方应用都无需触及用户的密码就可以取得部分资源的使用权限,所以 OAuth 是安全开放的。
OAuth 的目的是:用户不将用户名和密码交给第三方应用的情况下,让第三方应用可以有权限访问用户在资源服务器上的资源。
OAuth定义了四种角色:
- resource owner(资源所有者)
- resource server(资源服务器)
- client(客户端):代表资源所有者并且经过所有者授权去访问受保护的资源的应用程序
- authorization server(授权服务器):在成功验证资源所有者并获得授权后向客户端发出访问令牌
2、OAuth2.0 流程
OAuth2.0 运行流程描述:
- (A) 客户端向资源所有者请求其授权
- (B) 客户端收到资源所有者的授权许可,这个授权许可是一个代表资源所有者授权的凭据
- (C) 客户端向授权服务器请求访问令牌,并出示授权许可
- (D) 授权服务器对客户端身份进行认证,并校验授权许可,如果都是有效的,则发放访问令牌
- (E) 客户端向资源服务器请求受保护的资源,并出示访问令牌
- (F) 资源服务器校验访问令牌,如果令牌有效,则提供服务
3、授权码模式
一个授权许可是一个凭据,它代表资源所有者对访问受保护资源的一个授权,是客户端用来获取访问令牌的。
授权类型有四种:authorization code(授权码模式), implicit(简化模式或隐式授权模式), resource owner password credentials(密码授权模式), and client credentials(客户端授权模式)。授权码模式功能最完整,流程最严格,所以使用也最多。
授权码模式:
授权码流程描述:
- (A) 客户端通过将资源所有者的用户代理指向授权端点来启动这个流程。客户端包含它的客户端标识符,请求范围,本地状态,和重定向URI,在访问被允许(或者拒绝)后授权服务器立即将用户代理返回给重定向URI。
- (B) 授权服务器验证资源所有者(通过用户代理),并确定资源所有者是否授予或拒绝客户端的访问请求。
- (C) 假设资源所有者授权访问,那么授权服务器用之前提供的重定向URI(在请求中或在客户端时提供的)将用户代理重定向回客户端。重定向URI包括授权码和前面客户端提供的任意本地状态。
- (D) 客户端用上一步接收到的授权码从授权服务器的令牌端点那里请求获取一个访问令牌。
- (E) 授权服务器对客户端进行认证,校验授权码,并确保这个重定向URI和第三步(C)中那个URI匹配。如果校验通过,则发放访问令牌,以及可选的刷新令牌。
4、简单演示使用 OAuth2 获取用户在 facebook/google 的用户信息
4.1、注册应用
到Facebook官网注册开发者账号,创建应用(开发者平台 https://developers.facebook.com),如果尚未注册账号的请注册账号并进行登录)
注册:
创建第一个应用:
获取 client_id(appId)、client_secret(appSecret), 注册回调域
4.2、代码
LoginController
package oy.tpl.controller; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.client.RestTemplate; import com.alibaba.fastjson.JSONObject; import oy.tpl.utils.Config; import oy.tpl.utils.SimpleResponse; import oy.tpl.utils.UtilFunctions; /** * @author oy * @version 1.0 * @date 2020年4月19日 * @time 下午12:55:27 */ @Controller @RequestMapping("/oauth") public class LoginController { private Logger logger = LoggerFactory.getLogger(getClass()); @Resource RestTemplate restTemplate; /** * 引导用户到 Facebook/Google 认证页面,获取授权码 * @param response * @param loginType * @throws Exception */ @RequestMapping(value = "/{login_type}", method = RequestMethod.GET) public void leadToAuthorize(HttpServletResponse response, @PathVariable(value = "login_type") String loginType) throws Exception { if (!"facebook".equalsIgnoreCase(loginType) && !"google".equalsIgnoreCase(loginType)) { response.setStatus(404); return; } String state = UtilFunctions.genRandomCode().toUpperCase(); String url = ""; if ("facebook".equalsIgnoreCase(loginType)) { url = Config.FB_AUTH_URL + "?response_type=code" + "&client_id=" + Config.FB_CLIENT_ID + "&redirect_uri="+ Config.FB_REDIRECT_URI + "&scope=email&" + "state=" + state; } else if ("google".equalsIgnoreCase(loginType)) { url = Config.GG_AUTH_URL + "?response_type=code" + "&client_id=" + Config.GG_CLIENT_ID + "&redirect_uri="+ Config.GG_REDIRECT_URI + "&scope=email&" + "state=" + state; } logger.info("leadToAuthorize, url:{}", url); response.sendRedirect(url); } /** * Facebook/Google callback * 交换授权码获取 access_token * * @param request * @param code 授权码 * @param loginType 登陆类型. 可选值: facebook、google * @return * @throws Exception */ @RequestMapping(value = "/callback/{login_type}", method = RequestMethod.GET) @ResponseBody public SimpleResponse<Object> callback(HttpServletRequest request, @RequestParam(value = "code", required = true) String code, @PathVariable(value = "login_type") String loginType) throws Exception { logger.info("oauth callback begin..."); if (!"facebook".equalsIgnoreCase(loginType) && !"google".equalsIgnoreCase(loginType)) { return new SimpleResponse<>(40502, "参数错误"); } if (code.isEmpty()) { return new SimpleResponse<>(40502, "授权码为空"); } String url = ""; // 申请令牌的 url if ("facebook".equalsIgnoreCase(loginType)) { url = Config.FB_ACCESS_TOKEN + "?client_id=" + Config.FB_CLIENT_ID + "&client_secret=" + Config.FB_CLIENT_SECRET + "&code=" + code + "&redirect_uri="+ Config.FB_REDIRECT_URI; } else if ("google".equalsIgnoreCase(loginType)) { url = Config.GG_ACCESS_TOKEN + "?client_id=" + Config.GG_CLIENT_ID + "&client_secret=" + Config.GG_CLIENT_SECRET + "&code=" + code + "&grant_type=authorization_code" + "&redirect_uri="+ Config.GG_REDIRECT_URI; } logger.info("申请令牌的 url:{}", url); HttpHeaders requestHeaders = new HttpHeaders(); requestHeaders.add("accept", "application/json"); HttpEntity<String> requestEntity = new HttpEntity<String>(null, requestHeaders); JSONObject jsonObj = restTemplate.exchange(url, HttpMethod.POST, requestEntity, JSONObject.class).getBody(); String accessToken = null; if (jsonObj != null) { logger.info("jsonObj:{}", jsonObj.toString()); accessToken = jsonObj.getString("access_token"); request.getSession().setAttribute("access_token", accessToken); } //return thirdPartyLogin(request, loginType, accessToken); if (accessToken != null) { JSONObject userJsonObj = getUserInfo(accessToken, loginType); return new SimpleResponse<>(userJsonObj); } return new SimpleResponse<>(); } /** * 使用 access_token 获取 userinfo * * @param accessToken * @param loginType * @return * @throws Exception */ public JSONObject getUserInfo(String accessToken, String loginType) throws Exception { String url = ""; if ("facebook".equalsIgnoreCase(loginType)) { url = Config.FB_ACCESS_USER_INFO + "?fields=id,name,email&access_token=" + accessToken; } else if ("google".equalsIgnoreCase(loginType)) { url = Config.GG_ACCESS_USER_INFO + "?fields=id,name,email&access_token=" + accessToken; } logger.info("get userinfo, url:{}", url); HttpHeaders requestHeaders = new HttpHeaders(); requestHeaders.add("accept", "application/json"); HttpEntity<String> requestEntity = new HttpEntity<String>(null, requestHeaders); JSONObject userJsonObj = restTemplate.exchange(url, HttpMethod.GET, requestEntity, JSONObject.class).getBody(); logger.info("userJsonObj:{}", userJsonObj.toString()); return userJsonObj; } }
Config
public class Config { /* * third party login config */ public static final String FB_AUTH_URL = "https://www.facebook.com/v3.3/dialog/oauth"; public static String FB_REDIRECT_URI = "http://localhost:8080/tpl/oauth/callback/facebook"; public static final String FB_CLIENT_ID = "xxx304022362xxx"; public static final String FB_CLIENT_SECRET = "xxx"; public static final String FB_ACCESS_TOKEN = "https://graph.facebook.com/v3.3/oauth/access_token"; public static final String FB_ACCESS_USER_INFO = "https://graph.facebook.com/me"; public static final String GG_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth"; public static String GG_REDIRECT_URI = "http://localhost:8080/tpl/oauth/callback/google"; public static final String GG_CLIENT_ID = "xxx47-pmtfa5ded6fxxx.apps.googleusercontent.com"; public static final String GG_CLIENT_SECRET = "xxx"; public static final String GG_ACCESS_TOKEN ="https://www.googleapis.com/oauth2/v4/token"; public static final String GG_ACCESS_USER_INFO = "https://www.googleapis.com/oauth2/v3/tokeninfo"; }
利用上面的代码拿到用户信息后,可以实现第三方登陆。
比如数据库用户表有 uid、email、facebook_id、google_id 等字段。如果通过上面程序拿到 email、facebook_id,查询数据库是否有记录,没有表示是新用户,插入一个用户,并登陆;如果查询到了记录,表示老用户登陆成功。
参考:
2)《Spring Security 实战》--- 陈木鑫
博文中图片引用到 JoJo--慕课网视频教程(https://coding.imooc.com/class/134.html)
posted on 2019-05-29 11:46 wenbin_ouyang 阅读(857) 评论(1) 编辑 收藏 举报
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架