#582 OpenID Connect support
This commit is contained in:
parent
90e9b93787
commit
d44e76d0d1
16 changed files with 507 additions and 85 deletions
21
pom.xml
21
pom.xml
|
@ -1052,6 +1052,27 @@
|
|||
</dependency>
|
||||
|
||||
<!-- common library -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>19.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.oauth-client</groupId>
|
||||
<artifactId>google-oauth-client</artifactId>
|
||||
<version>1.22.0</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.http-client</groupId>
|
||||
<artifactId>google-http-client-jackson2</artifactId>
|
||||
<version>1.22.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codelibs</groupId>
|
||||
<artifactId>corelib</artifactId>
|
||||
|
|
|
@ -16,8 +16,18 @@
|
|||
package org.codelibs.fess.app.web.base.login;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class EmptyLoginCredential implements LoginCredential {
|
||||
import org.codelibs.fess.app.web.sso.SsoAction;
|
||||
import org.lastaflute.web.response.ActionResponse;
|
||||
|
||||
public class ActionLoginCredential implements LoginCredential {
|
||||
|
||||
private final Function<SsoAction, ActionResponse> action;
|
||||
|
||||
public ActionLoginCredential(final Function<SsoAction, ActionResponse> action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate() {
|
||||
|
@ -25,7 +35,7 @@ public class EmptyLoginCredential implements LoginCredential {
|
|||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "empty";
|
||||
return action.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -33,4 +43,7 @@ public class EmptyLoginCredential implements LoginCredential {
|
|||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
public ActionResponse execute(final SsoAction a) {
|
||||
return action.apply(a);
|
||||
}
|
||||
}
|
|
@ -175,11 +175,13 @@ public class FessLoginAssist extends TypicalLoginAssist<String, FessUserBean, Fe
|
|||
}
|
||||
}
|
||||
return doFindLoginUser(username, encryptPassword(password));
|
||||
} else if (credential instanceof SsoLoginCredential) {
|
||||
final String username = ((SsoLoginCredential) credential).getUsername();
|
||||
} else if (credential instanceof SpnegoLoginCredential) {
|
||||
final String username = credential.getId();
|
||||
if (!fessConfig.isAdminUser(username)) {
|
||||
return ComponentUtil.getLdapManager().login(username);
|
||||
}
|
||||
} else if (credential instanceof OpenIdConnectLoginCredential) {
|
||||
return OptionalEntity.of(((OpenIdConnectLoginCredential) credential).getUser());
|
||||
}
|
||||
return OptionalEntity.empty();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright 2012-2016 CodeLibs Project and the Others.
|
||||
*
|
||||
* 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 org.codelibs.fess.app.web.base.login;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.codelibs.fess.entity.FessUser;
|
||||
|
||||
public class OpenIdConnectLoginCredential implements LoginCredential {
|
||||
|
||||
private final Map<String, Object> attributes;
|
||||
|
||||
public OpenIdConnectLoginCredential(final Map<String, Object> attributes) {
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate() {
|
||||
assertLoginAccountRequired((String) attributes.get("email"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return (String) attributes.get("email");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getResource() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
public User getUser() {
|
||||
return new User(getId());
|
||||
}
|
||||
|
||||
public static class User implements FessUser {
|
||||
|
||||
private final String name;
|
||||
|
||||
protected User(final String name) {
|
||||
this.name = name;
|
||||
// TODO groups and roles
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getRoleNames() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getGroupNames() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getPermissions() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -17,12 +17,12 @@ package org.codelibs.fess.app.web.base.login;
|
|||
|
||||
import org.dbflute.util.DfCollectionUtil;
|
||||
|
||||
public class SsoLoginCredential implements LoginCredential {
|
||||
public class SpnegoLoginCredential implements LoginCredential {
|
||||
private final String username;
|
||||
|
||||
// private Principal principal;
|
||||
|
||||
public SsoLoginCredential(final String username) {
|
||||
public SpnegoLoginCredential(final String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
|
@ -15,18 +15,16 @@
|
|||
*/
|
||||
package org.codelibs.fess.app.web.sso;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.codelibs.fess.app.web.base.FessLoginAction;
|
||||
import org.codelibs.fess.app.web.base.login.EmptyLoginCredential;
|
||||
import org.codelibs.fess.app.web.base.login.ActionLoginCredential;
|
||||
import org.codelibs.fess.app.web.base.login.LoginCredential;
|
||||
import org.codelibs.fess.app.web.login.LoginAction;
|
||||
import org.codelibs.fess.sso.SsoAuthenticator;
|
||||
import org.codelibs.fess.sso.SsoManager;
|
||||
import org.codelibs.fess.util.ComponentUtil;
|
||||
import org.lastaflute.web.Execute;
|
||||
import org.lastaflute.web.login.exception.LoginFailureException;
|
||||
import org.lastaflute.web.response.ActionResponse;
|
||||
import org.lastaflute.web.servlet.filter.RequestLoggingFilter;
|
||||
import org.lastaflute.web.response.HtmlResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -42,19 +40,18 @@ public class SsoAction extends FessLoginAction {
|
|||
|
||||
@Execute
|
||||
public ActionResponse index() {
|
||||
final SsoAuthenticator authenticator = ComponentUtil.getSsoAuthenticator();
|
||||
LoginCredential loginCredential = authenticator.getLoginCredential();
|
||||
final SsoManager ssoManager = ComponentUtil.getSsoManager();
|
||||
final LoginCredential loginCredential = ssoManager.getLoginCredential();
|
||||
if (loginCredential == null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("No user in SSO request.");
|
||||
}
|
||||
if (fessConfig.isSsoEnabled()) {
|
||||
if (ssoManager.available()) {
|
||||
saveError(messages -> messages.addErrorsSsoLoginError(GLOBAL));
|
||||
}
|
||||
return redirect(LoginAction.class);
|
||||
} else if (loginCredential instanceof EmptyLoginCredential) {
|
||||
throw new RequestLoggingFilter.RequestClientErrorException("Your request is not authorized.", "401 Unauthorized",
|
||||
HttpServletResponse.SC_UNAUTHORIZED);
|
||||
} else if (loginCredential instanceof ActionLoginCredential) {
|
||||
return ((ActionLoginCredential) loginCredential).execute(this);
|
||||
}
|
||||
try {
|
||||
return fessLoginAssist.loginRedirect(loginCredential, op -> {}, () -> {
|
||||
|
@ -65,11 +62,14 @@ public class SsoAction extends FessLoginAction {
|
|||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("SSO login failure.", lfe);
|
||||
}
|
||||
if (fessConfig.isSsoEnabled()) {
|
||||
if (ssoManager.available()) {
|
||||
saveError(messages -> messages.addErrorsSsoLoginError(GLOBAL));
|
||||
}
|
||||
return redirect(LoginAction.class);
|
||||
}
|
||||
}
|
||||
|
||||
public ActionResponse redirect(final String url) {
|
||||
return HtmlResponse.fromRedirectPathAsIs(url);
|
||||
}
|
||||
}
|
|
@ -19,11 +19,11 @@ public class SsoLoginException extends FessSystemException {
|
|||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public SsoLoginException(String message) {
|
||||
public SsoLoginException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public SsoLoginException(String message, Exception e) {
|
||||
public SsoLoginException(final String message, final Exception e) {
|
||||
super(message, e);
|
||||
}
|
||||
|
||||
|
|
|
@ -110,7 +110,7 @@ public class RoleQueryHelper {
|
|||
requestManager.findUserBean(FessUserBean.class)
|
||||
.ifPresent(fessUserBean -> stream(fessUserBean.getPermissions()).of(stream -> stream.forEach(roleList::add)))
|
||||
.orElse(() -> roleList.addAll(fessConfig.getSearchGuestPermissionList()));
|
||||
} catch (RuntimeException e) {
|
||||
} catch (final RuntimeException e) {
|
||||
requestManager.findLoginManager(FessUserBean.class).ifPresent(manager -> manager.logout());
|
||||
throw e;
|
||||
}
|
||||
|
|
|
@ -19,8 +19,6 @@ import static org.codelibs.core.stream.StreamUtil.stream;
|
|||
|
||||
import java.util.Hashtable;
|
||||
|
||||
import javax.naming.Context;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.codelibs.core.lang.StringUtil;
|
||||
import org.codelibs.fess.entity.FessUser;
|
||||
|
|
|
@ -931,8 +931,8 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
|
|||
/** The key of the configuration. e.g. homeDirectory */
|
||||
String LDAP_ATTR_HOME_DIRECTORY = "ldap.attr.homeDirectory";
|
||||
|
||||
/** The key of the configuration. e.g. false */
|
||||
String SSO_ENABLED = "sso.enabled";
|
||||
/** The key of the configuration. e.g. none */
|
||||
String SSO_TYPE = "sso.type";
|
||||
|
||||
/** The key of the configuration. e.g. 0 */
|
||||
String SPNEGO_LOGGER_LEVEL = "spnego.logger.level";
|
||||
|
@ -970,6 +970,24 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
|
|||
/** The key of the configuration. e.g. false */
|
||||
String SPNEGO_ALLOW_DELEGATION = "spnego.allow.delegation";
|
||||
|
||||
/** The key of the configuration. e.g. __CLIENT_ID__ */
|
||||
String OIC_CLIENT_ID = "oic.client.id";
|
||||
|
||||
/** The key of the configuration. e.g. __CLIENT_SECRET__ */
|
||||
String OIC_CLIENT_SECRET = "oic.client.secret";
|
||||
|
||||
/** The key of the configuration. e.g. https://accounts.google.com/o/oauth2/auth */
|
||||
String OIC_AUTH_SERVER_URL = "oic.auth.server.url";
|
||||
|
||||
/** The key of the configuration. e.g. http://localhost:8080/sso/ */
|
||||
String OIC_REDIRECT_URL = "oic.redirect.url";
|
||||
|
||||
/** The key of the configuration. e.g. openid email */
|
||||
String OIC_SCOPE = "oic.scope";
|
||||
|
||||
/** The key of the configuration. e.g. https://accounts.google.com/o/oauth2/token */
|
||||
String OIC_TOKEN_SERVER_URL = "oic.token.server.url";
|
||||
|
||||
/**
|
||||
* Get the value of property as {@link String}.
|
||||
* @param propertyKey The key of the property. (NotNull)
|
||||
|
@ -3779,20 +3797,12 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
|
|||
String getLdapAttrHomeDirectory();
|
||||
|
||||
/**
|
||||
* Get the value for the key 'sso.enabled'. <br>
|
||||
* The value is, e.g. false <br>
|
||||
* Get the value for the key 'sso.type'. <br>
|
||||
* The value is, e.g. none <br>
|
||||
* comment: ------
|
||||
* @return The value of found property. (NotNull: if not found, exception but basically no way)
|
||||
*/
|
||||
String getSsoEnabled();
|
||||
|
||||
/**
|
||||
* Is the property for the key 'sso.enabled' true? <br>
|
||||
* The value is, e.g. false <br>
|
||||
* comment: ------
|
||||
* @return The determination, true or false. (if not found, exception but basically no way)
|
||||
*/
|
||||
boolean isSsoEnabled();
|
||||
String getSsoType();
|
||||
|
||||
/**
|
||||
* Get the value for the key 'spnego.logger.level'. <br>
|
||||
|
@ -3921,6 +3931,48 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
|
|||
*/
|
||||
boolean isSpnegoAllowDelegation();
|
||||
|
||||
/**
|
||||
* Get the value for the key 'oic.client.id'. <br>
|
||||
* The value is, e.g. __CLIENT_ID__ <br>
|
||||
* @return The value of found property. (NotNull: if not found, exception but basically no way)
|
||||
*/
|
||||
String getOicClientId();
|
||||
|
||||
/**
|
||||
* Get the value for the key 'oic.client.secret'. <br>
|
||||
* The value is, e.g. __CLIENT_SECRET__ <br>
|
||||
* @return The value of found property. (NotNull: if not found, exception but basically no way)
|
||||
*/
|
||||
String getOicClientSecret();
|
||||
|
||||
/**
|
||||
* Get the value for the key 'oic.auth.server.url'. <br>
|
||||
* The value is, e.g. https://accounts.google.com/o/oauth2/auth <br>
|
||||
* @return The value of found property. (NotNull: if not found, exception but basically no way)
|
||||
*/
|
||||
String getOicAuthServerUrl();
|
||||
|
||||
/**
|
||||
* Get the value for the key 'oic.redirect.url'. <br>
|
||||
* The value is, e.g. http://localhost:8080/sso/ <br>
|
||||
* @return The value of found property. (NotNull: if not found, exception but basically no way)
|
||||
*/
|
||||
String getOicRedirectUrl();
|
||||
|
||||
/**
|
||||
* Get the value for the key 'oic.scope'. <br>
|
||||
* The value is, e.g. openid email <br>
|
||||
* @return The value of found property. (NotNull: if not found, exception but basically no way)
|
||||
*/
|
||||
String getOicScope();
|
||||
|
||||
/**
|
||||
* Get the value for the key 'oic.token.server.url'. <br>
|
||||
* The value is, e.g. https://accounts.google.com/o/oauth2/token <br>
|
||||
* @return The value of found property. (NotNull: if not found, exception but basically no way)
|
||||
*/
|
||||
String getOicTokenServerUrl();
|
||||
|
||||
/**
|
||||
* The simple implementation for configuration.
|
||||
* @author FreeGen
|
||||
|
@ -5410,12 +5462,8 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
|
|||
return get(FessConfig.LDAP_ATTR_HOME_DIRECTORY);
|
||||
}
|
||||
|
||||
public String getSsoEnabled() {
|
||||
return get(FessConfig.SSO_ENABLED);
|
||||
}
|
||||
|
||||
public boolean isSsoEnabled() {
|
||||
return is(FessConfig.SSO_ENABLED);
|
||||
public String getSsoType() {
|
||||
return get(FessConfig.SSO_TYPE);
|
||||
}
|
||||
|
||||
public String getSpnegoLoggerLevel() {
|
||||
|
@ -5489,5 +5537,29 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
|
|||
public boolean isSpnegoAllowDelegation() {
|
||||
return is(FessConfig.SPNEGO_ALLOW_DELEGATION);
|
||||
}
|
||||
|
||||
public String getOicClientId() {
|
||||
return get(FessConfig.OIC_CLIENT_ID);
|
||||
}
|
||||
|
||||
public String getOicClientSecret() {
|
||||
return get(FessConfig.OIC_CLIENT_SECRET);
|
||||
}
|
||||
|
||||
public String getOicAuthServerUrl() {
|
||||
return get(FessConfig.OIC_AUTH_SERVER_URL);
|
||||
}
|
||||
|
||||
public String getOicRedirectUrl() {
|
||||
return get(FessConfig.OIC_REDIRECT_URL);
|
||||
}
|
||||
|
||||
public String getOicScope() {
|
||||
return get(FessConfig.OIC_SCOPE);
|
||||
}
|
||||
|
||||
public String getOicTokenServerUrl() {
|
||||
return get(FessConfig.OIC_TOKEN_SERVER_URL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
45
src/main/java/org/codelibs/fess/sso/SsoManager.java
Normal file
45
src/main/java/org/codelibs/fess/sso/SsoManager.java
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright 2012-2016 CodeLibs Project and the Others.
|
||||
*
|
||||
* 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 org.codelibs.fess.sso;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
import org.codelibs.fess.app.web.base.login.LoginCredential;
|
||||
import org.codelibs.fess.util.ComponentUtil;
|
||||
|
||||
public class SsoManager {
|
||||
|
||||
private SsoAuthenticator authenticator;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
final String ssoType = ComponentUtil.getFessConfig().getSsoType();
|
||||
if (!"none".equals(ssoType)) {
|
||||
authenticator = ComponentUtil.getComponent(ssoType + "Authenticator");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean available() {
|
||||
return authenticator != null;
|
||||
}
|
||||
|
||||
public LoginCredential getLoginCredential() {
|
||||
if (authenticator != null) {
|
||||
return authenticator.getLoginCredential();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
/*
|
||||
* Copyright 2012-2016 CodeLibs Project and the Others.
|
||||
*
|
||||
* 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 org.codelibs.fess.sso.oic;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.codelibs.core.lang.StringUtil;
|
||||
import org.codelibs.core.net.UuidUtil;
|
||||
import org.codelibs.fess.app.web.base.login.ActionLoginCredential;
|
||||
import org.codelibs.fess.app.web.base.login.LoginCredential;
|
||||
import org.codelibs.fess.app.web.base.login.OpenIdConnectLoginCredential;
|
||||
import org.codelibs.fess.crawler.Constants;
|
||||
import org.codelibs.fess.mylasta.direction.FessConfig;
|
||||
import org.codelibs.fess.sso.SsoAuthenticator;
|
||||
import org.codelibs.fess.util.ComponentUtil;
|
||||
import org.lastaflute.web.util.LaRequestUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.api.client.auth.oauth2.AuthorizationCodeRequestUrl;
|
||||
import com.google.api.client.auth.oauth2.AuthorizationCodeTokenRequest;
|
||||
import com.google.api.client.auth.oauth2.TokenResponse;
|
||||
import com.google.api.client.http.GenericUrl;
|
||||
import com.google.api.client.http.HttpTransport;
|
||||
import com.google.api.client.http.javanet.NetHttpTransport;
|
||||
import com.google.api.client.json.JsonFactory;
|
||||
import com.google.api.client.json.JsonParser;
|
||||
import com.google.api.client.json.JsonToken;
|
||||
import com.google.api.client.json.jackson2.JacksonFactory;
|
||||
import com.google.api.client.util.Base64;
|
||||
|
||||
public class OpenIdConnectAuthenticator implements SsoAuthenticator {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(OpenIdConnectAuthenticator.class);
|
||||
|
||||
private static final String OIC_STATE = "OIC_STATE";
|
||||
|
||||
private final HttpTransport httpTransport = new NetHttpTransport();
|
||||
|
||||
private final JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
|
||||
|
||||
@Override
|
||||
public LoginCredential getLoginCredential() {
|
||||
return LaRequestUtil.getOptionalRequest().map(request -> {
|
||||
final HttpSession session = request.getSession(false);
|
||||
if (session != null) {
|
||||
final String sesState = (String) session.getAttribute(OIC_STATE);
|
||||
session.removeAttribute(OIC_STATE);
|
||||
if (StringUtil.isNotBlank(sesState)) {
|
||||
final String code = request.getParameter("code");
|
||||
final String reqState = request.getParameter("state");
|
||||
if (sesState.equals(reqState) && StringUtil.isNotBlank(code)) {
|
||||
return processCallback(request, code);
|
||||
}
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("code:" + code + " state(request):" + reqState + " state(session):" + sesState);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return new ActionLoginCredential(action -> action.redirect(getAuthUrl(request)));
|
||||
}).orElse(null);
|
||||
}
|
||||
|
||||
protected String getAuthUrl(final HttpServletRequest request) {
|
||||
final FessConfig fessConfig = ComponentUtil.getFessConfig();
|
||||
final String state = UuidUtil.create();
|
||||
request.getSession().setAttribute(OIC_STATE, state);
|
||||
return new AuthorizationCodeRequestUrl(fessConfig.getOicAuthServerUrl(), fessConfig.getOicClientId())//
|
||||
.setScopes(Arrays.asList(fessConfig.getOicScope()))//
|
||||
.setResponseTypes(Arrays.asList("code"))//
|
||||
.setRedirectUri(fessConfig.getOicRedirectUrl())//
|
||||
.setState(state)//
|
||||
.build();
|
||||
}
|
||||
|
||||
protected LoginCredential processCallback(final HttpServletRequest request, final String code) {
|
||||
try {
|
||||
final TokenResponse tr = getTokenUrl(code);
|
||||
|
||||
final String[] jwt = ((String) tr.get("id_token")).split("\\.");
|
||||
final byte[] jwtHeader = Base64.decodeBase64(jwt[0]);
|
||||
final byte[] jwtClaim = Base64.decodeBase64(jwt[1]);
|
||||
final byte[] jwtSigniture = Base64.decodeBase64(jwt[2]);
|
||||
|
||||
// TODO validate signiture
|
||||
|
||||
final Map<String, Object> attributes = new HashMap<>();
|
||||
attributes.put("accesstoken", tr.getAccessToken());
|
||||
attributes.put("refreshtoken", tr.getRefreshToken() == null ? "null" : tr.getRefreshToken());
|
||||
attributes.put("tokentype", tr.getTokenType());
|
||||
attributes.put("expire", tr.getExpiresInSeconds());
|
||||
attributes.put("jwtheader", new String(jwtHeader, Constants.UTF_8_CHARSET));
|
||||
attributes.put("jwtclaim", new String(jwtClaim, Constants.UTF_8_CHARSET));
|
||||
attributes.put("jwtsign", new String(jwtSigniture, Constants.UTF_8_CHARSET));
|
||||
|
||||
parseJwtClaim(new String(jwtClaim, Constants.UTF_8_CHARSET), attributes);
|
||||
|
||||
return new OpenIdConnectLoginCredential(attributes);
|
||||
} catch (final IOException e) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Failed to process callbacked request.", e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void parseJwtClaim(final String jwtClaim, final Map<String, Object> attributes) throws IOException {
|
||||
final JsonParser jsonParser = jsonFactory.createJsonParser(jwtClaim);
|
||||
while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
|
||||
final String name = jsonParser.getCurrentName();
|
||||
if (name != null) {
|
||||
jsonParser.nextToken();
|
||||
|
||||
// TODO other parameters
|
||||
switch (name) {
|
||||
case "iss":
|
||||
attributes.put("iss", jsonParser.getText());
|
||||
break;
|
||||
case "sub":
|
||||
attributes.put("sub", jsonParser.getText());
|
||||
break;
|
||||
case "azp":
|
||||
attributes.put("azp", jsonParser.getText());
|
||||
break;
|
||||
case "email":
|
||||
attributes.put("email", jsonParser.getText());
|
||||
break;
|
||||
case "at_hash":
|
||||
attributes.put("at_hash", jsonParser.getText());
|
||||
break;
|
||||
case "email_verified":
|
||||
attributes.put("email_verified", jsonParser.getText());
|
||||
break;
|
||||
case "aud":
|
||||
attributes.put("aud", jsonParser.getText());
|
||||
break;
|
||||
case "iat":
|
||||
attributes.put("iat", jsonParser.getText());
|
||||
break;
|
||||
case "exp":
|
||||
attributes.put("exp", jsonParser.getText());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected TokenResponse getTokenUrl(final String code) throws IOException {
|
||||
final FessConfig fessConfig = ComponentUtil.getFessConfig();
|
||||
return new AuthorizationCodeTokenRequest(httpTransport, jsonFactory, new GenericUrl(fessConfig.getOicTokenServerUrl()), code)//
|
||||
.setGrantType("authorization_code")//
|
||||
.setRedirectUri(fessConfig.getOicRedirectUrl())//
|
||||
.set("client_id", fessConfig.getOicClientId())//
|
||||
.set("client_secret", fessConfig.getOicClientSecret())//
|
||||
.execute();
|
||||
}
|
||||
}
|
|
@ -24,9 +24,9 @@ import javax.servlet.ServletContext;
|
|||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.codelibs.core.io.ResourceUtil;
|
||||
import org.codelibs.fess.app.web.base.login.EmptyLoginCredential;
|
||||
import org.codelibs.fess.app.web.base.login.ActionLoginCredential;
|
||||
import org.codelibs.fess.app.web.base.login.LoginCredential;
|
||||
import org.codelibs.fess.app.web.base.login.SsoLoginCredential;
|
||||
import org.codelibs.fess.app.web.base.login.SpnegoLoginCredential;
|
||||
import org.codelibs.fess.exception.FessSystemException;
|
||||
import org.codelibs.fess.exception.SsoLoginException;
|
||||
import org.codelibs.fess.mylasta.direction.FessConfig;
|
||||
|
@ -37,6 +37,7 @@ import org.codelibs.spnego.SpnegoHttpFilter;
|
|||
import org.codelibs.spnego.SpnegoHttpFilter.Constants;
|
||||
import org.codelibs.spnego.SpnegoHttpServletResponse;
|
||||
import org.codelibs.spnego.SpnegoPrincipal;
|
||||
import org.lastaflute.web.servlet.filter.RequestLoggingFilter;
|
||||
import org.lastaflute.web.util.LaRequestUtil;
|
||||
import org.lastaflute.web.util.LaResponseUtil;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -49,14 +50,14 @@ public class SpnegoAuthenticator implements SsoAuthenticator {
|
|||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
if (ComponentUtil.getFessConfig().isSsoEnabled()) {
|
||||
if ("spnego".equals(ComponentUtil.getFessConfig().getSsoType())) {
|
||||
try {
|
||||
// set some System properties
|
||||
final SpnegoFilterConfig config = SpnegoFilterConfig.getInstance(new SpengoConfig());
|
||||
|
||||
// pre-authenticate
|
||||
authenticator = new org.codelibs.spnego.SpnegoAuthenticator(config);
|
||||
} catch (Exception e) {
|
||||
} catch (final Exception e) {
|
||||
throw new FessSystemException("Failed to initialize SPNEGO.", e);
|
||||
}
|
||||
}
|
||||
|
@ -67,43 +68,44 @@ public class SpnegoAuthenticator implements SsoAuthenticator {
|
|||
*/
|
||||
@Override
|
||||
public LoginCredential getLoginCredential() {
|
||||
if (!ComponentUtil.getFessConfig().isSsoEnabled()) {
|
||||
return null;
|
||||
}
|
||||
return LaRequestUtil
|
||||
.getOptionalRequest()
|
||||
.map(request -> {
|
||||
final HttpServletResponse response = LaResponseUtil.getResponse();
|
||||
final SpnegoHttpServletResponse spnegoResponse = new SpnegoHttpServletResponse(response);
|
||||
|
||||
return LaRequestUtil.getOptionalRequest().map(request -> {
|
||||
final HttpServletResponse response = LaResponseUtil.getResponse();
|
||||
final SpnegoHttpServletResponse spnegoResponse = new SpnegoHttpServletResponse(response);
|
||||
// client/caller principal
|
||||
final SpnegoPrincipal principal;
|
||||
try {
|
||||
principal = authenticator.authenticate(request, spnegoResponse);
|
||||
} catch (final Exception e) {
|
||||
final String msg = "HTTP Authorization Header=" + request.getHeader(Constants.AUTHZ_HEADER);
|
||||
logger.error(msg);
|
||||
throw new SsoLoginException(msg, e);
|
||||
}
|
||||
|
||||
// client/caller principal
|
||||
final SpnegoPrincipal principal;
|
||||
try {
|
||||
principal = authenticator.authenticate(request, spnegoResponse);
|
||||
} catch (Exception e) {
|
||||
final String msg = "HTTP Authorization Header=" + request.getHeader(Constants.AUTHZ_HEADER);
|
||||
logger.error(msg);
|
||||
throw new SsoLoginException(msg, e);
|
||||
}
|
||||
// context/auth loop not yet complete
|
||||
if (spnegoResponse.isStatusSet()) {
|
||||
return new ActionLoginCredential(action -> {
|
||||
throw new RequestLoggingFilter.RequestClientErrorException("Your request is not authorized.",
|
||||
"401 Unauthorized", HttpServletResponse.SC_UNAUTHORIZED);
|
||||
});
|
||||
}
|
||||
|
||||
// context/auth loop not yet complete
|
||||
if (spnegoResponse.isStatusSet()) {
|
||||
return new EmptyLoginCredential();
|
||||
}
|
||||
// assert
|
||||
if (null == principal) {
|
||||
final String msg = "Principal was null.";
|
||||
logger.error(msg);
|
||||
throw new SsoLoginException(msg);
|
||||
}
|
||||
|
||||
// assert
|
||||
if (null == principal) {
|
||||
String msg = "Principal was null.";
|
||||
logger.error(msg);
|
||||
throw new SsoLoginException(msg);
|
||||
}
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("principal=" + principal);
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("principal=" + principal);
|
||||
}
|
||||
|
||||
final String[] username = principal.getName().split("@", 2);
|
||||
return new SsoLoginCredential(username[0]);
|
||||
}).orElseGet(() -> null);
|
||||
final String[] username = principal.getName().split("@", 2);
|
||||
return new SpnegoLoginCredential(username[0]);
|
||||
}).orElseGet(() -> null);
|
||||
|
||||
}
|
||||
|
||||
|
@ -122,7 +124,7 @@ public class SpnegoAuthenticator implements SsoAuthenticator {
|
|||
}
|
||||
|
||||
@Override
|
||||
public String getInitParameter(String name) {
|
||||
public String getInitParameter(final String name) {
|
||||
if (SpnegoHttpFilter.Constants.LOGGER_LEVEL.equals(name)) {
|
||||
return fessConfig.getSpnegoLoggerLevel();
|
||||
} else if (SpnegoHttpFilter.Constants.LOGIN_CONF.equals(name)) {
|
||||
|
|
|
@ -59,7 +59,7 @@ import org.codelibs.fess.indexer.IndexUpdater;
|
|||
import org.codelibs.fess.job.JobExecutor;
|
||||
import org.codelibs.fess.ldap.LdapManager;
|
||||
import org.codelibs.fess.mylasta.direction.FessConfig;
|
||||
import org.codelibs.fess.sso.SsoAuthenticator;
|
||||
import org.codelibs.fess.sso.SsoManager;
|
||||
import org.lastaflute.core.message.MessageManager;
|
||||
import org.lastaflute.di.core.SingletonLaContainer;
|
||||
import org.lastaflute.di.core.factory.SingletonLaContainerFactory;
|
||||
|
@ -73,7 +73,7 @@ public final class ComponentUtil {
|
|||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ComponentUtil.class);
|
||||
|
||||
private static final String SSO_AUTHENTICATOR = "ssoAuthenticator";
|
||||
private static final String SSO_MANAGER = "ssoManager";
|
||||
|
||||
private static final String PERMISSION_HELPER = "permissionHelper";
|
||||
|
||||
|
@ -360,8 +360,8 @@ public final class ComponentUtil {
|
|||
return getComponent(PERMISSION_HELPER);
|
||||
}
|
||||
|
||||
public static SsoAuthenticator getSsoAuthenticator() {
|
||||
return getComponent(SSO_AUTHENTICATOR);
|
||||
public static SsoManager getSsoManager() {
|
||||
return getComponent(SSO_MANAGER);
|
||||
}
|
||||
|
||||
public static CrawlerClientFactory getCrawlerClientFactory() {
|
||||
|
|
|
@ -473,7 +473,7 @@ ldap.attr.homeDirectory=homeDirectory
|
|||
# ----------------------------------------------------------
|
||||
# SSO
|
||||
# ------
|
||||
sso.enabled=false
|
||||
sso.type=none
|
||||
spnego.logger.level=0
|
||||
spnego.krb5.conf=krb5.conf
|
||||
spnego.login.conf=auth_login.conf
|
||||
|
@ -487,4 +487,10 @@ spnego.prompt.ntlm=true
|
|||
spnego.allow.localhost=true
|
||||
spnego.allow.delegation=false
|
||||
|
||||
oic.client.id=__CLIENT_ID__
|
||||
oic.client.secret=__CLIENT_SECRET__
|
||||
oic.auth.server.url=https://accounts.google.com/o/oauth2/auth
|
||||
oic.redirect.url=http://localhost:8080/sso/
|
||||
oic.scope=openid email
|
||||
oic.token.server.url=https://accounts.google.com/o/oauth2/token
|
||||
|
||||
|
|
|
@ -2,7 +2,10 @@
|
|||
<!DOCTYPE components PUBLIC "-//DBFLUTE//DTD LastaDi 1.0//EN"
|
||||
"http://dbflute.org/meta/lastadi10.dtd">
|
||||
<components>
|
||||
<component name="ssoAuthenticator" class="org.codelibs.fess.sso.spnego.SpnegoAuthenticator">
|
||||
<component name="ssoManager" class="org.codelibs.fess.sso.SsoManager">
|
||||
</component>
|
||||
|
||||
<component name="spnegoAuthenticator" class="org.codelibs.fess.sso.spnego.SpnegoAuthenticator">
|
||||
</component>
|
||||
|
||||
</components>
|
||||
|
|
Loading…
Add table
Reference in a new issue