App Engine中的Google Services身份验证,第2部分

在本教程的第一部分中, 我描述了如何使用OAuth进行Google API服务的访问/身份验证。 不幸的是,正如我稍后发现的那样,我使用的方法是OAuth 1.0,显然现在Google正式弃用了OAuth 1.0,改用OAuth 2.0版本。 显然,发现这一点让我有些沮丧,并承诺我将创建一个新博客条目,其中包含有关如何使用2.0的说明。 好消息是,有了2.0支持,Google添加了一些附加的帮助程序类,这些类使事情变得更加容易,特别是如果您使用的是Google App Engine,这正是我在本教程中使用的。

Google Developers网站现在对如何设置OAuth 2.0进行了很好的描述 。 但是,事实证明,配置一个真实的示例如何完成它是一个挑战,因此我想记录下我学到的东西。
教程场景在上一个教程中,我创建的项目说明了如何访问用户的Google Docs文件列表。 在本教程中,我做了一些改动,而是使用YouTube的API来显示用户喜欢的视频的列表。 访问用户的收藏夹确实需要使用OAuth进行身份验证,因此这是一个很好的测试。

入门 (可以在此处找到本教程的Eclipse项目)。

您必须做的第一件事是遵循Google官方文档中概述的有关使用OAuth 2.0的步骤。 由于我正在创建一个Web应用程序,因此您将需要遵循这些文档中名为“ Web服务器应用程序”的部分。 此外,我之前讨论的用于设置Google App Engine的步骤仍然很重要,因此我将直接跳入代码并跳过这些设置步骤。

(注意:可以在这里找到Eclipse项目-我再次选择不使用Maven,以使那些没有安装Maven或在Maven方面有丰富知识的人保持简单。)

应用程序流程非常简单(假设是首次使用用户):

  1. 用户访问Web应用程序时(假设您正在使用GAE开发人员模拟器在http:// localhost:8888在本地运行该Web应用程序),则他们必须首先使用其gmail或Google域帐户登录到Google。
  2. 登录后,用户将被重定向到一个简单的JSP页面,该页面具有指向其YouTube最喜欢的视频的链接。
  3. 当点击链接时,servlet将启动OAuth流程以获取对其YouTube帐户的访问权限。 此过程的第一部分将重定向到Google Page,该页面会提示他们是否要授予应用程序访问权限。
  4. 假设用户回答肯定,将显示10个带有链接的收藏夹列表。
  5. 如果他们单击链接,将加载视频。

这是前3页流程的描述:

这是最后两页(假设用户单击了给定的链接):

尽管此示例特定于YouTube,但相同的通用原则也适用于访问任何基于Google的云服务,例如Google +,Google Drive,Docs等。它们创建此类集成的关键推动因素显然是OAuth,因此让我们来看一下该过程有效。

OAuth 2.0处理流程

对于刚开始学习这项技术的新开发人员而言,使用OAuth可能会有些不知所措。 其背后的主要前提是允许用户有选择地确定他们希望外部应用程序可以访问哪些“私有”资源,例如我们正在为本教程开发的资源。 通过使用OAuth,用户可以避免与第三方共享其登录凭据,而可以简单地向该第三方授予访问其某些信息的权限。

为了实现此功能,需要将用户导航到其私人数据所在的源(在本例中为YouTube)。 然后,他们可以允许或拒绝访问请求。 如果他们允许,则私有数据(YouTube)的源将一次性授权代码返回给第三方应用程序。 由于每次需要访问权限时用户都必须授予访问权限是一件很麻烦的事情,因此可以玩一个额外的通话,它将“以旧换新”他们的单次使用授权更长的时间。 我们为本教程开发的Web应用程序的总体流程如下所示。

OAuth流程

进行的第一步是确定用户是否已经使用其gmail或Google Domain帐户登录到Google。 尽管不直接与OAuth流程绑定,但使用户能够使用其Google帐户登录非常方便,而不是要求用户登录您的网站。 这是对Google的第一个标注。 然后,一旦登录,应用程序将确定用户是否具有授予OAuth权限的本地帐户设置。 如果他们是第一次登录,则不会。 在这种情况下,将启动OAuth流程。

该过程的第一步是向OAuth提供程序(在本例中为Google YouTube)指定请求访问的“范围”。 由于Google提供了很多服务,因此它们具有很多范围。 您可以使用其OAuth 2.0沙箱轻松确定。

当您启动OAuth流程时,会向他们提供您想要访问的范围,以及Google为您提供的OAuth客户端凭据(这些步骤实际上是支持OAuth的任何提供程序所通用的)。 为了我们的目的,我们正在寻求访问该用户的YouTube帐户的权限,因此Google提供的范围是:https://gdata.youtube.com/。

如果最终用户授予对由范围标识的资源的访问权限,则Google会将授权码发回给应用程序。 这是在servlet中捕获的。 由于返回的代码只是“一次性”代码,因此将其交换为运行时间更长的访问令牌(和相关的刷新令牌)。 上面的步骤由标题为“请求访问和刷新令牌”的活动/框表示。

一旦配备了访问令牌,应用程序便可以通过将API调用与令牌一起放置来访问用户的私有数据。 如果一切顺利,API将返回结果。

这不是一个可怕的复杂过程,它只涉及几个步骤。 让我们看一些具体的实现细节,首先从servlet过滤器开始,该过滤器确定用户是否已经登录Google和/或已授予OAuth访问权限。

授权过滤器

让我们看一下AuthorizationFilter的前几行(要了解如何将其配置为过滤器,请参阅web.xml文件)。

public void doFilter(ServletRequest req, ServletResponse res,FilterChain chain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) req;HttpServletResponse response = (HttpServletResponse) res;HttpSession session = request.getSession();if not present, add credential store to servlet contextif (session.getServletContext().getAttribute(Constant.GOOG_CREDENTIAL_STORE) == null) {LOGGER.fine('Adding credential store to context ' + credentialStore);session.getServletContext().setAttribute(Constant.GOOG_CREDENTIAL_STORE, credentialStore);}if google user isn't in session, add itif (session.getAttribute(Constant.AUTH_USER_ID) == null) {LOGGER.fine('Add user to session');UserService userService = UserServiceFactory.getUserService();User user = userService.getCurrentUser();session.setAttribute(Constant.AUTH_USER_ID, user.getUserId());session.setAttribute(Constant.AUTH_USER_NICKNAME, user.getNickname());if not running on app engine prod, hard-code my email address for testingif (SystemProperty.environment.value() == SystemProperty.Environment.Value.Production) {session.setAttribute(Constant.AUTH_USER_EMAIL, user.getEmail());} else {session.setAttribute(Constant.AUTH_USER_EMAIL, 'jeffdavisco@gmail.com');}}

前几行简单地将通用servlet请求和响应转换为它们对应的Http等效项-这是必需的,因为我们要访问HTTP会话。 下一步是确定Servlet上下文中是否存在CredentialStore。 正如我们将看到的,它用于存储用户的凭据,因此在后续的servlet中随时可以使用它很方便。 当我们使用以下命令检查用户是否已经在会话中时,事情就开始了:

if (session.getAttribute(Constant.AUTH_USER_ID) == null) {

如果没有,我们将使用Google的UserService类获取其Google登录凭据。 这是GAE用户可用来获取用户的Google用户ID,电子邮件和昵称的帮助程序类。 从UserService获得此信息后,我们将在会话中存储一些用户的详细信息。

目前,我们还没有对OAuth做任何事情,但是在接下来的代码行系列中会有所改变:
尝试{
Utils.getActiveCredential(request,credentialStore); } catch(NoRefreshTokenException e1){ //如果输入了该catch块,则需要执行oauth流程 LOGGER.info('未找到用户–授权URL为:'+ e1.getAuthorizationUrl()); response.sendRedirect(e1.getAuthorizationUrl()); }

大多数OAuth处理都使用一个称为Utils的帮助程序类。 在这种情况下,我们将调用静态方法getActiveCredential()。 稍后我们将看到,如果以前没有为用户捕获OAuth凭据,则此方法将返回NoRefreshTokenException。 作为自定义例外,它将返回URL值,该URL值用于将用户重定向到Google以寻求OAuth批准。

让我们更详细地了解getActiveCredential()方法,因为这是管理许多OAuth处理的地方。

public static Credential getActiveCredential(HttpServletRequest request, CredentialStore credentialStore) throws NoRefreshTokenException {String userId = (String) request.getSession().getAttribute(Constant.AUTH_USER_ID);Credential credential = null;try {if (userId != null) {credential = getStoredCredential(userId, credentialStore);}if ((credential == null || credential.getRefreshToken() == null) && request.getParameter('code') != null) {credential = exchangeCode(request.getParameter('code'));LOGGER.fine('Credential access token is: ' + credential.getAccessToken());if (credential != null) {if (credential.getRefreshToken() != null) {credentialStore.store(userId, credential);}}}if (credential == null || credential.getRefreshToken() == null) {String email = (String) request.getSession().getAttribute(Constant.AUTH_USER_EMAIL);String authorizationUrl = getAuthorizationUrl(email, request);throw new NoRefreshTokenException(authorizationUrl);}} catch (CodeExchangeException e) {e.printStackTrace();} return credential;}

我们要做的第一件事是从会话中获取Google userId(如果没有填充,则无法做到这一点)。 接下来,我们尝试使用Utils静态方法getStoredCredential()从CredentialStore获取用户的OAuth凭据(以相同名称存储在Google类中)。 如果找不到该用户的凭据,则调用名为getAuthorizationUrl()的Utils方法。 如下所示,此方法用于构造浏览器重定向到的URL,该URL用于提示用户授权访问其私有数据(该URL由Google提供,因为它将询问用户批准)。

private static String getAuthorizationUrl(String emailAddress, HttpServletRequest request) {GoogleAuthorizationCodeRequestUrl urlBuilder = null;try {urlBuilder = new GoogleAuthorizationCodeRequestUrl(getClientCredential().getWeb().getClientId(),Constant.OATH_CALLBACK,Constant.SCOPES).setAccessType('offline').setApprovalPrompt('force');} catch (IOException e) {TODO Auto-generated catch blocke.printStackTrace();}urlBuilder.set('state', request.getRequestURI());if (emailAddress != null) {urlBuilder.set('user_id', emailAddress);}return urlBuilder.build();}

如您所见,此方法正在使用称为GoogleAuthorizationCodeRequestUrl的类(来自Google)。 它使用您注册使用OAuth时由Google提供的OAuth客户端凭据来构造HTTP调用(巧合的是,这些凭据存储在一个名为client_secrets.json的文件中。其他参数包括OAuth请求的范围和URL如果获得用户的批准,该用户将被重定向回该URL。该URL是您在注册Google的OAuth访问权限时指定的URL:

现在,如果用户已经授予OAuth访问权限,则getActiveCredential()方法将改为从CredentialStore获取凭据。

回到接收OAuth凭据结果的URL(在本例中为http:// localhost:8888 / authSub),您可能想知道Google如何发布到该内部专用地址? 好吧,实际上是用户的浏览器发回了结果,因此在这种情况下,本地主机就可以很好地解决问题。 让我们看一下用于处理此回调的名为OAuth2Callback的servlet(有关如何完成authSub的servlet映射的信息,请参见web.xml)。

public class OAuth2Callback extends HttpServlet {private static final long serialVersionUID = 1L;private final static Logger LOGGER = Logger.getLogger(OAuth2Callback.class.getName());public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {StringBuffer fullUrlBuf = request.getRequestURL();Credential credential = null;if (request.getQueryString() != null) {fullUrlBuf.append('?').append(request.getQueryString());}LOGGER.info('requestURL is: ' + fullUrlBuf);AuthorizationCodeResponseUrl authResponse = new AuthorizationCodeResponseUrl(fullUrlBuf.toString());check for user-denied errorif (authResponse.getError() != null) {LOGGER.info('User-denied access');} else {LOGGER.info('User granted oauth access');String authCode = authResponse.getCode();request.getSession().setAttribute('code', authCode);response.sendRedirect(authResponse.getState());}}}

该课程最重要的收获是:
AuthorizationCodeResponseUrl authResponse =新的AuthorizationCodeResponseUrl(fullUrlBuf.toString()); Google提供了AuthorizationCodeResponseUrl类,以方便分析OAuth请求的结果。 如果该类的getError()方法不为null,则意味着用户拒绝了该请求。 如果它为null(表示用户已批准该请求),则使用方法调用getCode()来检索一次性授权码。 此代码值放置在用户的会话中,并且在重定向到用户的目标URL(通过过滤器)后调用Utils.getActiveCredential()时,它将将该授权代码交换为长期访问权限并使用电话: 凭证= exchangeCode((String)request.getSession()。getAttribute('code')); 接下来显示Utils.exchangeCode()方法:

public static Credential exchangeCode(String authorizationCode)throws CodeExchangeException {try {GoogleTokenResponse response = new GoogleAuthorizationCodeTokenRequest(new NetHttpTransport(), Constant.JSON_FACTORY, Utils.getClientCredential().getWeb().getClientId(), Utils.getClientCredential().getWeb().getClientSecret(),authorizationCode, Constant.OATH_CALLBACK).execute();return Utils.buildEmptyCredential().setFromTokenResponse(response);} catch (IOException e) {e.printStackTrace();throw new CodeExchangeException();}}

此方法还使用称为GoogleAuthorizationCodeTokenRequest的Google类,该类用于调用Google以将一次性OAuth授权代码交换为较长时间的访问令牌。
现在,我们已经(最终)获得了YouTube API所需的访问令牌,我们准备向用户显示其视频收藏夹中的10个。

调用YouTube API服务

有了访问令牌,我们现在可以继续向用户显示其收藏夹列表。 为此,调用了一个名为“ FavoritesServlet”的servlet。 它将调用YouTube API,通过Jackson将生成的JSON-C格式解析为一些本地Java类,然后将结果发送到JSP页面进行处理。 这是servlet:

public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {LOGGER.fine('Running FavoritesServlet');Credential credential = Utils.getStoredCredential((String) request.getSession().getAttribute(Constant.AUTH_USER_ID), (CredentialStore) request.getSession().getServletContext().getAttribute(Constant.GOOG_CREDENTIAL_STORE));VideoFeed feed = null;if the request fails, it's likely because access token is expired - we'll refreshtry {LOGGER.fine('Using access token: ' + credential.getAccessToken());feed = YouTube.fetchFavs(credential.getAccessToken());} catch (Exception e) {LOGGER.fine('Refreshing credentials');credential.refreshToken();credential = Utils.refreshToken(request, credential);GoogleCredential googleCredential = Utils.refreshCredentials(credential);LOGGER.fine('Using refreshed access token: ' + credential.getAccessToken());retry feed = YouTube.fetchFavs(credential.getAccessToken());}  LOGGER.fine('Video feed results are: ' + feed);request.setAttribute(Constant.VIDEO_FAVS, feed);RequestDispatcher dispatcher = getServletContext().getRequestDispatcher('htmllistVids.jsp');dispatcher.forward(request, response);  }

由于这篇文章主要是关于OAuth流程的,因此我不会过多介绍API调用的放置方式,但是最重要的代码行是:feed = YouTube.fetchFavs(credential.getAccessToken()); feed是VideoFeed的实例。 如您所见,另一个名为YouTube的帮助程序类用于执行繁重的工作。 为了总结一下,我将展示fetchFavs()方法。

public static VideoFeed fetchFavs(String accessToken) throws IOException, HttpResponseException {HttpTransport transport = new NetHttpTransport();final JsonFactory jsonFactory = new JacksonFactory();HttpRequestFactory factory = transport.createRequestFactory(new HttpRequestInitializer() {@Overridepublic void initialize(HttpRequest request) {set the parserJsonCParser parser = new JsonCParser(jsonFactory);request.addParser(parser);set up the Google headersGoogleHeaders headers = new GoogleHeaders();headers.setApplicationName('YouTube Favorites1.0');headers.gdataVersion = '2';request.setHeaders(headers);}});build the YouTube URLYouTubeUrl url = new YouTubeUrl(Constant.GOOGLE_YOUTUBE_FEED);url.maxResults = 10;url.access_token = accessToken;build the HTTP GET requestHttpRequest request = factory.buildGetRequest(url);HttpResponse response = request.execute();execute the request and the parse video feedVideoFeed feed = response.parseAs(VideoFeed.class);return feed;}

它使用称为HttpRequestFactory的Google类构造对YouTube的出站HTTP API调用。 由于我们使用的是GAE,因此我们只能使用哪些类来发出此类请求。 注意代码行:
url.access_token = accessToken;
那就是我们使用通过OAuth流程获取的访问令牌的地方。

因此,虽然需要花费大量代码才能使OAuth内容正常运行,但是一旦到位,您就可以准备调用各种Google API服务来进行滚动!

参考:我们的JCG合作伙伴 Jeff Davis在Jeff's SOA Ruminations博客上为Google Services进行了认证,第2部分 。


翻译自: https://www.javacodegeeks.com/2012/06/google-services-authentication-in-app_20.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/372218.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/372218.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

字符串常用操作

1 常用:分割、长度、索引、切片2 r (1,2,3,4,5)#只读列表元组3 name "liangml"4 strip5 username input("user:")6 if username.strip() "liangml":#strip可以将输入前后的空格都换掉7 print("welcome")8 9 split …

java 保存bufferedimage_java - 如何将BufferedImage保存为Fi

答案在于Java Documentation的编写/保存图像教程。SaveImage.java类提供以下保存图像的方法:static boolean ImageIO.write(RenderedImage im, String formatName, File output) throws IOException该教程解释了这一点BufferedImage类实现RenderedImage接口。所以它…

MarkDownPad2 注册码

注册信息 邮箱地址: Soar360live.com 授权秘钥:GBPduHjWfJU1mZqcPM3BikjYKF6xKhlKIys3i1MU2eJHqWGImDHzWdD6xhMNLGVpbP2M5SN6bnxn2kSE8qHqNY5QaaRxmO3YSMHxlv2EYpjdwLcPwfeTG7kUdnhKE0vVy4RidP6Y2wZ0q74f47fzsZo45JE2hfQBFi2O9Jldjp1mW8HUpTtLA2a5/sQytX…

[51nod1297]管理二叉树

一个初始为空的二叉搜索树T,以及1到N的一个排列P: {a1, a2, ..., aN}。我们向这个二叉搜索树T添加这些数,从a1开始, 接下来是 a2, ..., 以aN结束。在每一个添加操作后,输出T上每对节点之间的距离之和。例如:4 7 3 1 8 …

Java Swing中的聊天气泡

本文将向您解释“如何在Java swing应用程序中绘制聊天气泡?” 聊天气泡与呼出气泡或思想气泡相同。 今天,大多数聊天应用程序都以这种格式显示转换,因此本文将帮助您在用Java swing创建的桌面应用程序中进行相同的操作。 以下课程用于绘制第一…

java内存模型按照线程隔离性_深入理解Java多线程与并发框(第③篇)——Java内存模型与原子性、可见性、有序性...

一、Java内存模型Java Memory Modle,简称 JMM,中文名称 Java内存模型,它是一个抽象的概念,用来描述或者规范访问内存变量的方式。因为各中计算机的操作系统和硬件不同,方式机制也可能不同,Java内存模型用于…

PHP通过PDO连接Microsoft Access数据库

1连接到access数据库 $db new PDO("odbc:Driver{Microsoft Access Driver (*.mdb, *.accdb)}; dbq" .realpath("yourfilepath\# ddsbbn3A02.Mdb")) or die("Connect Error"); realpath函数用来规范化绝对路径 2修改数据库中BM_sitelink表中字段…

ZK实际应用:样式和布局

在之前的ZK in Action帖子中,我们使用ZK MVVM实现了CRUD功能 。 我们还快速浏览了一些样式代码,可能需要更多的解释。 在本文中,我们将讨论如何在ZK小部件上附加新CSS样式规则,以及如何覆盖现有样式。 我们还将介绍ZK中UI布局的一…

java面向对象的三大特征是6_Java面向对象的三大特征

面向对象的本质:以类的方式组织代码,以对象的方式组织数据。面向对象三大特性:封装 继承 多态封装:概念:隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性&…

Tornado(一)

Tornado 特点 Tornado是一个用Python写的相对简单的、不设障碍的Web服务器架构,用以处理上万的同时的连接口,让实时的Web服务通畅起来。虽然跟现在的一些用Python写的Web架构相似,比如Django,但Tornado更注重速度,能够…

Android下Opengl ES实现单屏幕双眼显示

http://blog.csdn.net/u011371324/article/details/68946779 默认情况下,Opengl ES使用系统提供的帧缓冲区作为绘图表面,一般情况下,如果只在屏幕的表面绘图的话,系统提供的默认帧缓冲区很高效,但是很多应用程序需要渲…

Oracle Service Bus –线程阻塞案例研究

本案例研究描述了在AIX 6.1和IBM Java VM 1.6上运行的Oracle Service Bus 11g遇到的线程阻塞问题的完整根本原因分析过程。 本文也是您提高线程转储分析技能的绝佳机会,我强烈建议您学习并正确理解以下分析方法。 与过早的中间件(Weblogic)重…

java 可以重载等于号码_Java面试之Java基础4——重载与重写的区别

目录重载与重写的概念重载与重写的区别重载与重写的总结构造器是否能被重写override为什么函数不能根据返回类型来区分重载重载与重写的概念重载:同样一个方法可以根据输入参数列表的不同,做出不同的处理。普通方法和构造器方法都能够重载。方法重载&…

二维数组、多维数组

二维数组: 定义二维数组 int[,] myArray new int[几个一维数组,数组中的个数]; 数组可以具有多个维度。例如,下列声明创建一个四行两列的二维数组(可以理解为4个1维数组,数组中包含2个元素): int[,] myArray new int[4,2]; int[…

一张大图片有多个小图片

这个页面也是我看到别人的写的,感觉不错,就自己留下了为了以后自己可以容易找到,也希望可以方便到别人。 写这个页面 需要注意的是: 1.写每一个小图片的位置时候,要用id,这样等级就高了,不然不起作用。 2.因…

java中如何调用dal接口案例_关于Java:接口的目的

好吧,我认为接口是一种强制对象实现一定数量功能的方法,而不必使用继承。有点像合同。我半明白他们的意思。但是,如果界面中的所有内容都是:public interface animal{void eat(object food);}它没有这样的实现,那么无论…

Android Studio混淆

这一篇说一下Android Studio的代码混淆: 第一步:要想使混淆生效,要修改项目(App)下的build.gradle一处内容:minifyEnabled 的值 设置为true,当前项目就可以使用混淆了。 apply plugin: com.and…

内存访问模式很重要

在高性能计算中,通常会说高速缓存未命中的代价是算法的最大性能损失。 多年来,处理器速度的提高大大超过了延迟到主内存的速度。 通过更宽的多通道总线,到主内存的带宽已大大增加,但是延迟并未显着减少。 为了掩盖这种延迟&#x…

上传头像将光标去掉

οnfοcus"this.blur();" unselectable"on" οnfοcus"this.blur();"支持火狐,谷歌等主流浏览器 unselectable支持ie浏览器转载于:https://www.cnblogs.com/jar-gon/p/6841239.html

java底层 文件操作_JAVA的文件操作【转】

11.3 I/O类使用由于在IO操作中,需要使用的数据源有很多,作为一个IO技术的初学者,从读写文件开始学习IO技术是一个比较好的选择。因为文件是一种常见的数据源,而且读写文件也是程序员进行IO编程的一个基本能力。本章IO类的使用就从…