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.servlet.http.HttpServletRequest;
24 import javax.servlet.http.HttpSession;
25
26 import org.codelibs.core.lang.StringUtil;
27 import org.codelibs.core.net.UuidUtil;
28 import org.codelibs.fess.app.web.base.login.ActionResponseCredential;
29 import org.codelibs.fess.app.web.base.login.OpenIdConnectCredential;
30 import org.codelibs.fess.crawler.Constants;
31 import org.codelibs.fess.mylasta.direction.FessConfig;
32 import org.codelibs.fess.sso.SsoAuthenticator;
33 import org.codelibs.fess.util.ComponentUtil;
34 import org.lastaflute.web.login.credential.LoginCredential;
35 import org.lastaflute.web.response.HtmlResponse;
36 import org.lastaflute.web.util.LaRequestUtil;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 import com.google.api.client.auth.oauth2.AuthorizationCodeRequestUrl;
41 import com.google.api.client.auth.oauth2.AuthorizationCodeTokenRequest;
42 import com.google.api.client.auth.oauth2.TokenResponse;
43 import com.google.api.client.http.GenericUrl;
44 import com.google.api.client.http.HttpTransport;
45 import com.google.api.client.http.javanet.NetHttpTransport;
46 import com.google.api.client.json.JsonFactory;
47 import com.google.api.client.json.JsonParser;
48 import com.google.api.client.json.JsonToken;
49 import com.google.api.client.json.jackson2.JacksonFactory;
50 import com.google.api.client.util.Base64;
51
52 public class OpenIdConnectAuthenticator implements SsoAuthenticator {
53
54 private static final Logger logger = LoggerFactory.getLogger(OpenIdConnectAuthenticator.class);
55
56 private static final String OIC_STATE = "OIC_STATE";
57
58 private final HttpTransport httpTransport = new NetHttpTransport();
59
60 private final JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
61
62 @Override
63 public LoginCredential getLoginCredential() {
64 return LaRequestUtil.getOptionalRequest().map(request -> {
65 final HttpSession session = request.getSession(false);
66 if (session != null) {
67 final String sesState = (String) session.getAttribute(OIC_STATE);
68 if (StringUtil.isNotBlank(sesState)) {
69 session.removeAttribute(OIC_STATE);
70 final String code = request.getParameter("code");
71 final String reqState = request.getParameter("state");
72 if (sesState.equals(reqState) && StringUtil.isNotBlank(code)) {
73 return processCallback(request, code);
74 }
75 if (logger.isDebugEnabled()) {
76 logger.debug("code:" + code + " state(request):" + reqState + " state(session):" + sesState);
77 }
78 return null;
79 }
80 }
81
82 return new ActionResponseCredential(() -> HtmlResponse.fromRedirectPathAsIs(getAuthUrl(request)));
83 }).orElse(null);
84 }
85
86 protected String getAuthUrl(final HttpServletRequest request) {
87 final FessConfig fessConfig = ComponentUtil.getFessConfig();
88 final String state = UuidUtil.create();
89 request.getSession().setAttribute(OIC_STATE, state);
90 return new AuthorizationCodeRequestUrl(fessConfig.getOicAuthServerUrl(), fessConfig.getOicClientId())
91 .setScopes(Arrays.asList(fessConfig.getOicScope()))
92 .setResponseTypes(Arrays.asList("code"))
93 .setRedirectUri(fessConfig.getOicRedirectUrl())
94 .setState(state)
95 .build();
96 }
97
98 protected LoginCredential processCallback(final HttpServletRequest request, final String code) {
99 try {
100 final TokenResponse tr = getTokenUrl(code);
101
102 final String[] jwt = ((String) tr.get("id_token")).split("\\.");
103 final String jwtHeader = new String(Base64.decodeBase64(jwt[0]), Constants.UTF_8_CHARSET);
104 final String jwtClaim = new String(Base64.decodeBase64(jwt[1]), Constants.UTF_8_CHARSET);
105 final String jwtSigniture = new String(Base64.decodeBase64(jwt[2]), Constants.UTF_8_CHARSET);
106
107 if (logger.isDebugEnabled()) {
108 logger.debug("jwtHeader: " + jwtHeader);
109 logger.debug("jwtClaim: " + jwtClaim);
110 logger.debug("jwtSigniture: " + jwtSigniture);
111 }
112
113
114
115 final Map<String, Object> attributes = new HashMap<>();
116 attributes.put("accesstoken", tr.getAccessToken());
117 attributes.put("refreshtoken", tr.getRefreshToken() == null ? "null" : tr.getRefreshToken());
118 attributes.put("tokentype", tr.getTokenType());
119 attributes.put("expire", tr.getExpiresInSeconds());
120 attributes.put("jwtheader", jwtHeader);
121 attributes.put("jwtclaim", jwtClaim);
122 attributes.put("jwtsign", jwtSigniture);
123
124 parseJwtClaim(jwtClaim, attributes);
125
126 return new OpenIdConnectCredential(attributes);
127 } catch (final IOException e) {
128 if (logger.isDebugEnabled()) {
129 logger.debug("Failed to process callbacked request.", e);
130 }
131 }
132 return null;
133 }
134
135 protected void parseJwtClaim(final String jwtClaim, final Map<String, Object> attributes) throws IOException {
136 final JsonParser jsonParser = jsonFactory.createJsonParser(jwtClaim);
137 while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
138 final String name = jsonParser.getCurrentName();
139 if (name != null) {
140 jsonParser.nextToken();
141
142
143 switch (name) {
144 case "iss":
145 attributes.put("iss", jsonParser.getText());
146 break;
147 case "sub":
148 attributes.put("sub", jsonParser.getText());
149 break;
150 case "azp":
151 attributes.put("azp", jsonParser.getText());
152 break;
153 case "email":
154 attributes.put("email", jsonParser.getText());
155 break;
156 case "at_hash":
157 attributes.put("at_hash", jsonParser.getText());
158 break;
159 case "email_verified":
160 attributes.put("email_verified", jsonParser.getText());
161 break;
162 case "aud":
163 attributes.put("aud", jsonParser.getText());
164 break;
165 case "iat":
166 attributes.put("iat", jsonParser.getText());
167 break;
168 case "exp":
169 attributes.put("exp", jsonParser.getText());
170 break;
171 }
172 }
173 }
174 }
175
176 protected TokenResponse getTokenUrl(final String code) throws IOException {
177 final FessConfig fessConfig = ComponentUtil.getFessConfig();
178 return new AuthorizationCodeTokenRequest(httpTransport, jsonFactory, new GenericUrl(fessConfig.getOicTokenServerUrl()), code)
179 .setGrantType("authorization_code")
180 .setRedirectUri(fessConfig.getOicRedirectUrl())
181 .set("client_id", fessConfig.getOicClientId())
182 .set("client_secret", fessConfig.getOicClientSecret())
183 .execute();
184 }
185 }