【问题标题】:Connect to GMail with JavaMail API without enabling less secure access使用 JavaMail API 连接到 GMail,而不启用不太安全的访问
【发布时间】:2020-03-13 20:49:28
【问题描述】:

有没有什么方法可以使用 JavaMail API 连接到 GMail 而无需启用“不太安全的访问”?

【问题讨论】:

    标签: java oauth-2.0 gmail jakarta-mail


    【解决方案1】:

    您必须使用 OAuth2 进行身份验证以避免开启不太安全的访问。

    每个很难找到的文档

    示例代码:

    Properties props = (Properties) System.getProperties().clone();
    props.put("mail.imaps.ssl.enable", "true");
    props.put("mail.imaps.auth.mechanisms", "XOAUTH2");
    // props.put("mail.debug.auth", "true");
    
    Session session = Session.getDefaultInstance(props);
    // session.setDebug(true);
    
    store = session.getStore("imaps");
    String accessToken = getAccessToken(user, clientId, clientSecret);
    store.connect(hostname, user, accessToken);
    

    下一个技巧是获取访问令牌。

    这是一个你可以在本地使用的:

    您首先需要在https://console.developers.google.com/apis/credentials 创建一个应用并获取客户端 ID 和秘密 JSON

    您可以在任何端口上启动本地 Web 服务器并在任何端口上使用 http://localhost 重定向 URL(需要在前面提到的屏幕上添加)。

    private static String getAccessToken(String emailAddress, String clientId, String clientSecret) throws Exception {
        NetHttpTransport transport = GoogleNetHttpTransport.newTrustedTransport();
        JsonFactory jsonFactory = Utils.getDefaultJsonFactory();
    
        GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(jsonFactory, new StringReader("{\n" +
                "  \"installed\": {\n" +
                "    \"client_id\": \"" + clientId + "\",\n" +
                "    \"client_secret\": \"" + clientSecret + "\"\n" +
                "  }\n" +
                "}"));
        GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(transport, jsonFactory, clientSecrets, Collections.singleton(GMAIL_SCOPE)).setAccessType("offline").build();
    
        AtomicReference<String> redirectUri = new AtomicReference<>();
    
        String authorizationCode = doRedirect("code=([^&$]+)", RE.wrapConsumer(p -> {
            redirectUri.set("http://localhost:" + p);
            Desktop.getDesktop().browse(flow.newAuthorizationUrl().setRedirectUri(redirectUri.get()).toURI());
        }));
    
        // second redirect URL needs to be set and match the one on the newAuthorization flow but isn't actually used
        GoogleTokenResponse execute = flow.newTokenRequest(authorizationCode).setRedirectUri(redirectUri.get()).execute();
    
        String refreshToken = execute.getRefreshToken();
        String accessToken = execute.getAccessToken();
    
        return accessToken;
    }
    
    private static String doRedirect(String pattern, Consumer<Integer> portConsumer) {
        try {
            ServerSocket socket = new ServerSocket(0);
            portConsumer.accept(socket.getLocalPort());
            Socket connection = socket.accept();
            BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            OutputStream out = new BufferedOutputStream(connection.getOutputStream());
            PrintStream pout = new PrintStream(out);
    
            try {
                String request = in.readLine();
                Matcher matcher = Pattern.compile(pattern).matcher(request);
                String response = "<html><body>Window can be closed now.</body></html>";
                pout.println("HTTP/1.1 200 OK");
                pout.println("Server: MyApp");
                pout.println("Content-Type: text/html");
                pout.println("Content-Length: " + response.length());
                pout.println();
                pout.println(response);
                pout.flush();
                if (matcher.find())
                    return matcher.group(1);
                else
                    throw new RuntimeException("Could not find match");
            } finally {
                in.close();
            }
        } catch (Exception ex) {
            throw new RuntimeException("Error while listening for local redirect", ex);
        }
    }
    

    获取刷新令牌并将其保存以备后用是最好的选择。每Getting null Refresh token

    GoogleAuthorizationCodeFlow flow = 
        new GoogleAuthorizationCodeFlow.Builder(transport, jsonFactory, clientSecrets, Collections.singleton(GMAIL_SCOPE))
            .setApprovalPrompt("force") // Needed only if you users didn't accept this earlier
            .setAccessType("offline")
            .build();
    

    【讨论】:

    • 如果搜索“JavaMail OAuth2”将您直接带到文档中,找到文档有多难?
    • 在您知道它与 OAuth2 相关之前,没有迹象表明它应该成为搜索查询的一部分;如果没有 OAuth2 关键字,所有排名靠前的结果都表示使用用户名/密码,并且您可能需要启用不太安全的访问
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-12-07
    • 1970-01-01
    • 2012-03-05
    • 2017-09-10
    • 1970-01-01
    • 2014-09-29
    • 1970-01-01
    相关资源
    最近更新 更多