一段时间后OAuth“401:凭证无效”

时间:2015-01-24 16:35:21

标签: java google-app-engine

我使用 Google Drive API 创建了简单的应用 OAuth2身份验证,基于此示例plus-appengine-sample

所以,我有两个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)

似乎令牌已已过期,但是servlet是否需要使用存储的刷新令牌来请求新的访问令牌?我使用离线访问类型,因此应在第一次请求时将刷新令牌传递给回调servlet。

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

1 个答案:

答案 0 :(得分:1)

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

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