java - 一段时间后 OAuth "401: Invalid Credentials"

标签 java google-app-engine

我根据此示例 plus-appengine-sample 使用 Google Drive APIOAuth2 身份验证 创建了简单的应用程序

因此,我有两个 servlet 实现:AbstractAppEngineAuthorizationCodeServletAbstractAppEngineAuthorizationCodeCallbackServlet,它们应该为我完成所有艰苦的工作(oauth 工作流程)。

public class DriveServlet extends AbstractAppEngineAuthorizationCodeServlet {
    private static final String MY_APP_NAME = "Drive API demo";
    private static final long serialVersionUID = 1L;

    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
        AuthorizationCodeFlow authFlow = initializeFlow();
        Credential credential = authFlow.loadCredential(getUserId(req));

        if (credential == null) {
            resp.sendRedirect(authFlow.newAuthorizationUrl()
                    .setRedirectUri(OAuthUtils.getRedirectUri(req)).build());
            return;
        }

        Drive drive = new Drive.Builder(OAuthUtils.HTTP_TRANSPORT_REQUEST, 
                OAuthUtils.JSON_FACTORY, credential).setApplicationName(MY_APP_NAME).build();

        // API calls (examines drive structure)
        DriveMiner miner = new DriveMiner(drive);
        req.setAttribute("miner", miner);

        RequestDispatcher view = req.getRequestDispatcher("/Drive.jsp");
        view.forward(req, resp);
    }

    @Override
    protected AuthorizationCodeFlow initializeFlow() throws ServletException, IOException {
        return OAuthUtils.initializeFlow();
    }

    @Override
    protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
        return OAuthUtils.getRedirectUri(req);
    }
}

public class OAuthCallbackServlet extends AbstractAppEngineAuthorizationCodeCallbackServlet {
    private static final long serialVersionUID = 1L;

    @Override
    protected AuthorizationCodeFlow initializeFlow() throws ServletException, IOException {
        return OAuthUtils.initializeFlow();
    }

    @Override
    protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
        return OAuthUtils.getRedirectUri(req);
    }

    @Override
    protected void onSuccess(HttpServletRequest req, HttpServletResponse resp, 
            Credential credential) throws ServletException, IOException {
        resp.sendRedirect(OAuthUtils.MAIN_SERVLET_PATH);
    }

    @Override
    protected void onError(HttpServletRequest req, HttpServletResponse resp, 
            AuthorizationCodeResponseUrl errorResponse) throws ServletException, IOException {
        String nickname = UserServiceFactory.getUserService().getCurrentUser().getNickname();
        resp.getWriter().print(
                "<h3>I am sorry" + nickname+ ", an internal server error occured. Try it later.</h1>");
        resp.setStatus(500);
        resp.addHeader("Content-Type", "text/html");
        return;
    }
}

public class OAuthUtils {
    private static final String CLIENT_SECRETS_FILE_PATH = "/client_secrets.json"; 
    static final JacksonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
    static final UrlFetchTransport HTTP_TRANSPORT_REQUEST = new UrlFetchTransport();
    private static final Set<String> PERMISSION_SCOPES = Collections.singleton(DriveScopes.DRIVE_READONLY);
    private static final AppEngineDataStoreFactory DATA_STORE_FACTORY = AppEngineDataStoreFactory.getDefaultInstance();
    private static final String AUTH_CALLBACK_SERVLET_PATH = "/oauth2callback";
    static final String MAIN_SERVLET_PATH = "/drive";

    private static GoogleClientSecrets clientSecrets = null;

    private OAuthUtils() {}

    private static GoogleClientSecrets getClientSecrets() throws IOException {
        if (clientSecrets == null) {
            InputStream jsonStream = OAuthUtils.class.getResourceAsStream(CLIENT_SECRETS_FILE_PATH);
            InputStreamReader  jsonReader = new InputStreamReader(jsonStream);
            clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, jsonReader);
        }
        return clientSecrets;
    }

    static GoogleAuthorizationCodeFlow initializeFlow() throws IOException {
        return new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT_REQUEST,
                JSON_FACTORY, getClientSecrets(), PERMISSION_SCOPES)
                .setDataStoreFactory(DATA_STORE_FACTORY)
                .setAccessType("offline").build(); 
    }

    static String getRedirectUri(HttpServletRequest req) {
        GenericUrl requestUrl = new GenericUrl(req.getRequestURL().toString());
        requestUrl.setRawPath(AUTH_CALLBACK_SERVLET_PATH);
        return requestUrl.build();
    }
}

身份验证流程以及 Drive API 调用都按预期工作,但不知何故,在一段时间后,我在刷新时遇到此异常:

Uncaught exception from servlet
        com.google.api.client.googleapis.json.GoogleJsonResponseException: 401
        {
        "code" : 401,
        "errors" : [{ "domain" : "global", 
                      "location" : "Authorization", 
                      "locationType" : "header", 
                      "message" : "Invalid Credentials", 
                      "reason" : "authError" }],
        "message" : "Invalid Credentials"
        }
        at com.google.api.client.googleapis.json.GoogleJsonResponseException.from(GoogleJsonResponseException.java:145)
        at com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest.newExceptionOnError(AbstractGoogleJsonClientRequest.java:113)
        at com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest.newExceptionOnError(AbstractGoogleJsonClientRequest.java:40)
        at com.google.api.client.googleapis.services.AbstractGoogleClientRequest$1.interceptResponse(AbstractGoogleClientRequest.java:312)
        at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:1049)
        at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:410)
        at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:343)
        at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.execute(AbstractGoogleClientRequest.java:460)
        at sk.ennova.teamscom.drive.DriveMiner.getRootFolderId(DriveMiner.java:46)
        at org.apache.jsp.Drive_jsp._jspService(Drive_jsp.java:61)

token 似乎已经过期,但是 servlet 使用它们存储的刷新 token 请求新的访问 token 不是一项工作吗?我使用离线访问类型,因此刷新 token 应该在第一次请求时传递给回调servlet。

这里"401 Unauthorized" when trying to watch changes on Google Drive with Java API Client有一些可能出现问题的提示,但如果我使用这些 servlet,处理 token 过期不应该是我的情况(如果我错了,请纠正我)。此外,作用域 DriveScopes.DRIVE_READONLY 似乎可以读取“驱动器”树结构(获取给定文件夹的文件等)。问题可能出在哪里?

最佳答案

您需要首先指定您需要刷新 token 才能进行离线/长期访问,然后保存刷新 token 以供以后在访问 token 过期时使用。您可以使用刷新 token 请求新的访问 token ,直到用户撤销您对其帐户的访问权限。请参阅此处的官方文档:

https://developers.google.com/accounts/docs/OAuth2WebServer#refresh

关于java - 一段时间后 OAuth "401: Invalid Credentials",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28127680/

相关文章:

python - 这是在 App Engine/Python 中执行/处理 url 重写的正确方法吗?

google-app-engine - Google App Engine 上的亚马逊产品广告 API

javascript - 谷歌应用引擎,Ajax,文件上传,

java - 如何在相同和不同的服务中调用 @Transactional 和非事务方法时回滚事务?

java - Java 中的逆变问题

java - 在 java 中,当我输入一个有 10 个零的数字时,出现错误

java - Wicket - 在单击事件上使用可加载可拆卸模型初始化组件

java - ElasticSearch 7 中的运算符 IN

google-app-engine - 使用 Go 的 appengine/datastore 中的 Nilable 值

google-app-engine - 通过谷歌应用引擎访问 Gmail 电子邮件