1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.codelibs.fess.sso.oic;
17
18 import java.io.IOException;
19 import java.util.Arrays;
20 import java.util.HashMap;
21 import java.util.Map;
22
23 import javax.annotation.PostConstruct;
24 import javax.servlet.http.HttpServletRequest;
25 import javax.servlet.http.HttpSession;
26
27 import org.apache.logging.log4j.LogManager;
28 import org.apache.logging.log4j.Logger;
29 import org.codelibs.core.lang.StringUtil;
30 import org.codelibs.core.net.UuidUtil;
31 import org.codelibs.fess.app.web.base.login.ActionResponseCredential;
32 import org.codelibs.fess.app.web.base.login.FessLoginAssist.LoginCredentialResolver;
33 import org.codelibs.fess.app.web.base.login.OpenIdConnectCredential;
34 import org.codelibs.fess.crawler.Constants;
35 import org.codelibs.fess.mylasta.action.FessUserBean;
36 import org.codelibs.fess.sso.SsoAuthenticator;
37 import org.codelibs.fess.sso.SsoResponseType;
38 import org.codelibs.fess.util.ComponentUtil;
39 import org.dbflute.optional.OptionalEntity;
40 import org.lastaflute.web.login.credential.LoginCredential;
41 import org.lastaflute.web.response.ActionResponse;
42 import org.lastaflute.web.response.HtmlResponse;
43 import org.lastaflute.web.util.LaRequestUtil;
44
45 import com.google.api.client.auth.oauth2.AuthorizationCodeRequestUrl;
46 import com.google.api.client.auth.oauth2.AuthorizationCodeTokenRequest;
47 import com.google.api.client.auth.oauth2.TokenResponse;
48 import com.google.api.client.http.GenericUrl;
49 import com.google.api.client.http.HttpTransport;
50 import com.google.api.client.http.javanet.NetHttpTransport;
51 import com.google.api.client.json.JsonFactory;
52 import com.google.api.client.json.JsonParser;
53 import com.google.api.client.json.JsonToken;
54 import com.google.api.client.json.jackson2.JacksonFactory;
55 import com.google.api.client.util.Base64;
56
57 public class OpenIdConnectAuthenticator implements SsoAuthenticator {
58
59 private static final Logger logger = LogManager.getLogger(OpenIdConnectAuthenticator.class);
60
61 protected static final String OIC_AUTH_SERVER_URL = "oic.auth.server.url";
62
63 protected static final String OIC_CLIENT_ID = "oic.client.id";
64
65 protected static final String OIC_SCOPE = "oic.scope";
66
67 protected static final String OIC_REDIRECT_URL = "oic.redirect.url";
68
69 protected static final String OIC_TOKEN_SERVER_URL = "oic.token.server.url";
70
71 protected static final String OIC_CLIENT_SECRET = "oic.client.secret";
72
73 protected static final String OIC_STATE = "OIC_STATE";
74
75 protected final HttpTransport httpTransport = new NetHttpTransport();
76
77 protected final JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
78
79 @PostConstruct
80 public void init() {
81 if (logger.isDebugEnabled()) {
82 logger.debug("Initialize {}", this.getClass().getSimpleName());
83 }
84 ComponentUtil.getSsoManager().register(this);
85 }
86
87 @Override
88 public LoginCredential getLoginCredential() {
89 return LaRequestUtil.getOptionalRequest().map(request -> {
90 if (logger.isDebugEnabled()) {
91 logger.debug("Logging in with OpenID Connect Authenticator");
92 }
93 final HttpSession session = request.getSession(false);
94 if (session != null) {
95 final String sesState = (String) session.getAttribute(OIC_STATE);
96 if (StringUtil.isNotBlank(sesState)) {
97 session.removeAttribute(OIC_STATE);
98 final String code = request.getParameter("code");
99 final String reqState = request.getParameter("state");
100 if (logger.isDebugEnabled()) {
101 logger.debug("code: {}, state(request): {}, state(session): {}", code, reqState, sesState);
102 }
103 if (sesState.equals(reqState) && StringUtil.isNotBlank(code)) {
104 return processCallback(request, code);
105 }
106 return null;
107 }
108 }
109
110 return new ActionResponseCredential(() -> HtmlResponse.fromRedirectPathAsIs(getAuthUrl(request)));
111 }).orElse(null);
112 }
113
114 protected String getAuthUrl(final HttpServletRequest request) {
115 final String state = UuidUtil.create();
116 request.getSession().setAttribute(OIC_STATE, state);
117 return new AuthorizationCodeRequestUrl(getOicAuthServerUrl(), getOicClientId())
118 .setScopes(Arrays.asList(getOicScope()))
119 .setResponseTypes(Arrays.asList("code"))
120 .setRedirectUri(getOicRedirectUrl())
121 .setState(state)
122 .build();
123 }
124
125 protected LoginCredential processCallback(final HttpServletRequest request, final String code) {
126 try {
127 final TokenResponse tr = getTokenUrl(code);
128
129 final String[] jwt = ((String) tr.get("id_token")).split("\\.");
130 final String jwtHeader = new String(Base64.decodeBase64(jwt[0]), Constants.UTF_8_CHARSET);
131 final String jwtClaim = new String(Base64.decodeBase64(jwt[1]), Constants.UTF_8_CHARSET);
132 final String jwtSigniture = new String(Base64.decodeBase64(jwt[2]), Constants.UTF_8_CHARSET);
133
134 if (logger.isDebugEnabled()) {
135 logger.debug("jwtHeader: {}", jwtHeader);
136 logger.debug("jwtClaim: {}", jwtClaim);
137 logger.debug("jwtSigniture: {}", jwtSigniture);
138 }
139
140
141
142 final Map<String, Object> attributes = new HashMap<>();
143 attributes.put("accesstoken", tr.getAccessToken());
144 attributes.put("refreshtoken", tr.getRefreshToken() == null ? "null" : tr.getRefreshToken());
145 attributes.put("tokentype", tr.getTokenType());
146 attributes.put("expire", tr.getExpiresInSeconds());
147 attributes.put("jwtheader", jwtHeader);
148 attributes.put("jwtclaim", jwtClaim);
149 attributes.put("jwtsign", jwtSigniture);
150
151 if (logger.isDebugEnabled()) {
152 logger.debug("attribute: {}", attributes);
153 }
154 parseJwtClaim(jwtClaim, attributes);
155
156 return new OpenIdConnectCredential(attributes);
157 } catch (final IOException e) {
158 if (logger.isDebugEnabled()) {
159 logger.debug("Failed to process callbacked request.", e);
160 }
161 }
162 return null;
163 }
164
165 protected void parseJwtClaim(final String jwtClaim, final Map<String, Object> attributes) throws IOException {
166 try (final JsonParser jsonParser = jsonFactory.createJsonParser(jwtClaim)) {
167 while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
168 final String name = jsonParser.getCurrentName();
169 if (name != null) {
170 jsonParser.nextToken();
171
172
173 switch (name) {
174 case "iss":
175 attributes.put("iss", jsonParser.getText());
176 break;
177 case "sub":
178 attributes.put("sub", jsonParser.getText());
179 break;
180 case "azp":
181 attributes.put("azp", jsonParser.getText());
182 break;
183 case "email":
184 attributes.put("email", jsonParser.getText());
185 break;
186 case "at_hash":
187 attributes.put("at_hash", jsonParser.getText());
188 break;
189 case "email_verified":
190 attributes.put("email_verified", jsonParser.getText());
191 break;
192 case "aud":
193 attributes.put("aud", jsonParser.getText());
194 break;
195 case "iat":
196 attributes.put("iat", jsonParser.getText());
197 break;
198 case "exp":
199 attributes.put("exp", jsonParser.getText());
200 break;
201 }
202 }
203 }
204 }
205 }
206
207 protected TokenResponse getTokenUrl(final String code) throws IOException {
208 return new AuthorizationCodeTokenRequest(httpTransport, jsonFactory, new GenericUrl(getOicTokenServerUrl()), code)
209 .setGrantType("authorization_code")
210 .setRedirectUri(getOicRedirectUrl())
211 .set("client_id", getOicClientId())
212 .set("client_secret", getOicClientSecret())
213 .execute();
214 }
215
216 protected String getOicClientSecret() {
217 return ComponentUtil.getSystemProperties().getProperty(OIC_CLIENT_SECRET, StringUtil.EMPTY);
218 }
219
220 protected String getOicTokenServerUrl() {
221 return ComponentUtil.getSystemProperties().getProperty(OIC_TOKEN_SERVER_URL, "https://accounts.google.com/o/oauth2/token");
222 }
223
224 protected String getOicRedirectUrl() {
225 return ComponentUtil.getSystemProperties().getProperty(OIC_REDIRECT_URL, "http://localhost:8080/sso/");
226 }
227
228 protected String getOicScope() {
229 return ComponentUtil.getSystemProperties().getProperty(OIC_SCOPE, StringUtil.EMPTY);
230 }
231
232 protected String getOicClientId() {
233 return ComponentUtil.getSystemProperties().getProperty(OIC_CLIENT_ID, StringUtil.EMPTY);
234 }
235
236 protected String getOicAuthServerUrl() {
237 return ComponentUtil.getSystemProperties().getProperty(OIC_AUTH_SERVER_URL, "https://accounts.google.com/o/oauth2/auth");
238 }
239
240 @Override
241 public void resolveCredential(final LoginCredentialResolver resolver) {
242 resolver.resolve(OpenIdConnectCredential.class, credential -> OptionalEntity.of(credential.getUser()));
243 }
244
245 @Override
246 public ActionResponse getResponse(final SsoResponseType responseType) {
247 return null;
248 }
249
250 @Override
251 public String logout(final FessUserBean user) {
252 return null;
253 }
254 }