/
AbstractAuthorizationCodeServlet.java
193 lines (180 loc) · 7.54 KB
/
AbstractAuthorizationCodeServlet.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
/*
* Copyright (c) 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.api.client.extensions.servlet.auth.oauth2;
import com.google.api.client.auth.oauth2.AuthorizationCodeFlow;
import com.google.api.client.auth.oauth2.AuthorizationCodeRequestUrl;
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.http.HttpResponseException;
import java.io.IOException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Thread-safe OAuth 2.0 authorization code flow HTTP servlet that manages and persists end-user
* credentials.
*
* <p>This is designed to simplify the flow in which an end-user authorizes your web application to
* access their protected data. Your application then has access to their data based on an access
* token and a refresh token to refresh that access token when it expires. Your main servlet class
* should extend {@link AbstractAuthorizationCodeServlet} and implement the abstract methods. To get
* the persisted credential associated with the current request, call {@link #getCredential()}. It
* is assumed that the end-user is authenticated by some external means by which a user ID is
* obtained. This user ID is used as the primary key for persisting the end-user credentials, and
* passed in via {@link #getUserId(HttpServletRequest)}. The first time an end-user arrives at your
* servlet, they will be redirected in the browser to an authorization page. Next, they will be
* redirected back to your site at the redirect URI selected in {@link
* #getRedirectUri(HttpServletRequest)}. The servlet to process that should extend {@link
* AbstractAuthorizationCodeCallbackServlet}, which should redirect back to this servlet on success.
*
* <p>Although this implementation is thread-safe, it can only process one request at a time. For a
* more performance-critical multi-threaded web application, instead use {@link
* AuthorizationCodeFlow} directly.
*
* <p>Sample usage:
*
* <pre>
* public class ServletSample extends AbstractAuthorizationCodeServlet {
*
* @Override
* protected void doGet(HttpServletRequest request, HttpServletResponse response)
* throws IOException {
* // do stuff
* }
*
* @Override
* protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
* GenericUrl url = new GenericUrl(req.getRequestURL().toString());
* url.setRawPath("/oauth2callback");
* return url.build();
* }
*
* @Override
* protected AuthorizationCodeFlow initializeFlow() throws IOException {
* return new AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
* new NetHttpTransport(),
* new GsonFactory(),
* new GenericUrl("https://server.example.com/token"),
* new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
* "s6BhdRkqt3",
* "https://server.example.com/authorize").setCredentialStore(
* new JdoCredentialStore(JDOHelper.getPersistenceManagerFactory("transactions-optional")))
* .build();
* }
*
* @Override
* protected String getUserId(HttpServletRequest req) throws ServletException, IOException {
* // return user ID
* }
* }
* </pre>
*
* @since 1.7
* @author Yaniv Inbar
*/
public abstract class AbstractAuthorizationCodeServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/** Lock on the flow and credential. */
private final Lock lock = new ReentrantLock();
/** Persisted credential associated with the current request or {@code null} for none. */
private Credential credential;
/**
* Authorization code flow to be used across all HTTP servlet requests or {@code null} before
* initialized in {@link #initializeFlow()}.
*/
private AuthorizationCodeFlow flow;
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws IOException, ServletException {
lock.lock();
try {
// load credential from persistence store
String userId = getUserId(req);
if (flow == null) {
flow = initializeFlow();
}
credential = flow.loadCredential(userId);
// if credential found with an access token, invoke the user code
if (credential != null && credential.getAccessToken() != null) {
try {
super.service(req, resp);
return;
} catch (HttpResponseException e) {
// if access token is null, assume it is because auth failed and we need to re-authorize
// but if access token is not null, it is some other problem
if (credential.getAccessToken() != null) {
throw e;
}
}
}
// redirect to the authorization flow
AuthorizationCodeRequestUrl authorizationUrl = flow.newAuthorizationUrl();
authorizationUrl.setRedirectUri(getRedirectUri(req));
onAuthorization(req, resp, authorizationUrl);
credential = null;
} finally {
lock.unlock();
}
}
/**
* Loads the authorization code flow to be used across all HTTP servlet requests (only called
* during the first HTTP servlet request).
*/
protected abstract AuthorizationCodeFlow initializeFlow() throws ServletException, IOException;
/** Returns the redirect URI for the given HTTP servlet request. */
protected abstract String getRedirectUri(HttpServletRequest req)
throws ServletException, IOException;
/**
* Returns the user ID for the given HTTP servlet request. This identifies your application's user
* and is used to fetch persisted credentials for that user. Most commonly, this will be a user id
* stored in the session or even the session id itself.
*/
protected abstract String getUserId(HttpServletRequest req) throws ServletException, IOException;
/**
* Return the persisted credential associated with the current request or {@code null} for none.
*/
protected final Credential getCredential() {
return credential;
}
/**
* Handles user authorization by redirecting to the OAuth 2.0 authorization server.
*
* <p>Default implementation is to call {@code resp.sendRedirect(authorizationUrl.build())}.
* Subclasses may override to provide optional parameters such as the recommended state parameter.
* Sample implementation:
*
* <pre>
* @Override
* protected void onAuthorization(HttpServletRequest req, HttpServletResponse resp,
* AuthorizationCodeRequestUrl authorizationUrl) throws ServletException, IOException {
* authorizationUrl.setState("xyz");
* super.onAuthorization(req, resp, authorizationUrl);
* }
* </pre>
*
* @param authorizationUrl authorization code request URL
* @param req HTTP servlet request
* @throws ServletException servlet exception
* @since 1.11
*/
protected void onAuthorization(
HttpServletRequest req,
HttpServletResponse resp,
AuthorizationCodeRequestUrl authorizationUrl)
throws ServletException, IOException {
resp.sendRedirect(authorizationUrl.build());
}
}