OpenIDでのSSO

Cognitoを認可サーバとして、OpenIDでSSOを設定しようとしています。
以前の投稿(OpenID SSO)と同様で、認証が通った直後にNullPointerExceptionのエラーが発生し、error/systemerror/画面に遷移してしまいます。

スタックトレースを以下に記載します。

java.lang.NullPointerException
 	at org.codelibs.fess.mylasta.direction.FessProp.getCanonicalLdapName(FessProp.java:1900)
 	at org.codelibs.fess.helper.SystemHelper.createSearchRole(SystemHelper.java:469)
 	at org.codelibs.fess.helper.SystemHelper.getSearchRoleByUser(SystemHelper.java:457)
 	at org.codelibs.fess.app.web.base.login.OpenIdConnectCredential$OpenIdUser.getPermissions(OpenIdConnectCredential.java:107)
 	at org.codelibs.fess.mylasta.action.FessUserBean.getPermissions(FessUserBean.java:60)
 	at org.codelibs.fess.helper.ActivityHelper.lambda$login$1(ActivityHelper.java:61)
 	at org.dbflute.optional.BaseOptional.callbackMapping(BaseOptional.java:265)
//以下省略

問題は、OpenIdConnectCredential.java:107部分で、groupsがnullを返すことと思われます。
groupsがJWTから渡されない場合もエラーとならないように、デフォルト値が実装されているように見えるのですが、意図通り設定されているかまでは確認できていません。

お手数ですが、確認いただけないでしょうか?

問題は、OpenIdConnectCredential.java:107部分で、groupsがnullを返すことと思われます。

の話とスタックトレースの内容が一致していないので、よくわかりませんが、スタックトレースだけから判断すると、ユーザー名がnullになっているのが原因かと思います。なぜ、ユーザー名がnullなのかは、デバッグログとかにして判断する必要があると思います。

返信ありがとうございます。
確認不足で失礼しました。少し古いバージョン(13.16)で実行していたため、行番号がずれていたのに気付きませんでした。
最新版では107ではなく115行目の
permissionSet.add(systemHelper.getSearchRoleByUser(name));
の部分が対応すること確認しました。
ご指摘の通り、nameがnullだということになりますね。
デバッグログではjwtのemailに値があること確認しているので、正しく割り付けられていれば空になるとは思えません。
OpenIdUserが初期化されていない(getUserが呼び出されされる前にgetPermissionsが呼び出された)可能性は考えられますか?

そのインスタンスが生成されるのはattributesの取得後だと思うので、デバッグログでjwtClaim:〜の内容でemailが含まれていないとかな気がします。

返信ありがとうございます。
OpenIdConnectAuthenticator.javaの
138行目 logger.debug(“jwtClaim: {}”, jwtClaim);
154行目 logger.debug(“attribute: {}”, attributes);
に対応するデバッグログにはemailが含まれていることを確認しています。
追加でログ出力を仕込んで、どの部分で渡っていないか調べてみます。

そこに値が入っているのであれば、//以下省略 のスタックトレース部分が気にはなりますね。ActivityHelperはログイン時にログ出力しているだけなので、どこから呼ばれているものなのかが。

ActivityHelper.javaの以下の部分だと思われます。
この部分、最後にまとめてログに出力させている関係で、NullPointerExceptionが途中で発生するとその手前も含めてログが出力されていないのです。そのため、コード修正無しでの確認ができない状態です。デバッグ出力を追加して検証しようとしています。

    public void login(final OptionalThing<FessUserBean> user) {
        final Map<String, String> valueMap = new LinkedHashMap<>();
        valueMap.put("action", Action.LOGIN.name());
        valueMap.put("user", user.map(FessUserBean::getUserId).orElse("-"));
        valueMap.put("permissions",
                user.map(u -> stream(u.getPermissions()).get(stream -> stream.collect(Collectors.joining(permissionSeparator))))
                        .filter(StringUtil::isNotBlank).orElse("-"));
        log(valueMap);
    }

スタックトレースをすべて貼ると以下です。

java.lang.NullPointerException
 	at org.codelibs.fess.mylasta.direction.FessProp.getCanonicalLdapName(FessProp.java:1900)
 	at org.codelibs.fess.helper.SystemHelper.createSearchRole(SystemHelper.java:469)
 	at org.codelibs.fess.helper.SystemHelper.getSearchRoleByUser(SystemHelper.java:457)
 	at org.codelibs.fess.app.web.base.login.OpenIdConnectCredential$OpenIdUser.getPermissions(OpenIdConnectCredential.java:107)
 	at org.codelibs.fess.mylasta.action.FessUserBean.getPermissions(FessUserBean.java:60)
 	at org.codelibs.fess.helper.ActivityHelper.lambda$login$1(ActivityHelper.java:61)
 	at org.dbflute.optional.BaseOptional.callbackMapping(BaseOptional.java:265)
 	at org.dbflute.optional.OptionalObject.map(OptionalObject.java:187)
 	at org.codelibs.fess.helper.ActivityHelper.login(ActivityHelper.java:61)
 	at org.codelibs.fess.app.web.sso.SsoAction.lambda$index$2(SsoAction.java:65)
 	at org.lastaflute.web.login.TypicalLoginAssist.loginRedirect(TypicalLoginAssist.java:253)
 	at org.codelibs.fess.app.web.sso.SsoAction.index(SsoAction.java:64)
 	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
 	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
 	at java.base/java.lang.reflect.Method.invoke(Unknown Source)
 	at org.lastaflute.web.ruts.GodHandableAction.invokeExecuteMethod(GodHandableAction.java:358)
 	at org.lastaflute.web.ruts.GodHandableAction.actuallyExecute(GodHandableAction.java:329)
 	at org.lastaflute.web.ruts.GodHandableAction.doExecute(GodHandableAction.java:152)
 	at org.lastaflute.web.ruts.GodHandableAction.lambda$transactionalExecute$0(GodHandableAction.java:143)
 	at org.lastaflute.db.jta.stage.JTATransactionStage.performTx(JTATransactionStage.java:102)
 	at org.lastaflute.db.jta.stage.JTATransactionStage.lambda$requiresNew$1(JTATransactionStage.java:59)
 	at org.lastaflute.di.tx.adapter.JTATransactionManagerAdapter.requiresNew(JTATransactionManagerAdapter.java:73)
 	at org.lastaflute.db.jta.stage.JTATransactionStage.requiresNew(JTATransactionStage.java:58)
 	at org.lastaflute.db.jta.stage.JTATransactionStage.selectable(JTATransactionStage.java:84)
 	at org.lastaflute.web.ruts.GodHandableAction.transactionalExecute(GodHandableAction.java:142)
 	at org.lastaflute.web.ruts.GodHandableAction.execute(GodHandableAction.java:117)
 	at org.lastaflute.web.ruts.ActionRequestProcessor.performAction(ActionRequestProcessor.java:253)
 	at org.lastaflute.web.ruts.ActionRequestProcessor.fire(ActionRequestProcessor.java:182)
 	at org.lastaflute.web.ruts.ActionRequestProcessor.process(ActionRequestProcessor.java:114)
 	at org.lastaflute.web.servlet.filter.RequestRoutingFilter.processAction(RequestRoutingFilter.java:289)
 	at org.lastaflute.web.servlet.filter.RequestRoutingFilter.routingToAction(RequestRoutingFilter.java:237)
 	at org.lastaflute.web.servlet.filter.RequestRoutingFilter.lambda$createActionFoundPathHandler$0(RequestRoutingFilter.java:201)
 	at org.lastaflute.web.path.ActionPathResolver.executeHandlerIfFound(ActionPathResolver.java:324)
 	at org.lastaflute.web.path.ActionPathResolver.mappingActionPath(ActionPathResolver.java:190)
 	at org.lastaflute.web.path.ActionPathResolver.handleActionPath(ActionPathResolver.java:114)
 	at org.lastaflute.web.servlet.filter.RequestRoutingFilter.doFilter(RequestRoutingFilter.java:132)
 	at org.lastaflute.web.servlet.filter.LastaToActionFilter.viaEmbeddedFilter(LastaToActionFilter.java:153)
 	at org.lastaflute.web.servlet.filter.LastaToActionFilter.viaInsideHookDeque(LastaToActionFilter.java:144)
 	at org.lastaflute.web.servlet.filter.LastaToActionFilter.viaInsideHook(LastaToActionFilter.java:128)
 	at org.lastaflute.web.servlet.filter.LastaToActionFilter.doFilter(LastaToActionFilter.java:120)
 	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
 	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
 	at org.lastaflute.web.servlet.filter.LastaShowbaseFilter.toNextChain(LastaShowbaseFilter.java:171)
 	at org.lastaflute.web.servlet.filter.LastaShowbaseFilter.lambda$viaEmbeddedFilter$3(LastaShowbaseFilter.java:150)
 	at org.lastaflute.web.servlet.filter.RequestLoggingFilter.actuallyFilter(RequestLoggingFilter.java:237)
 	at org.lastaflute.web.servlet.filter.RequestLoggingFilter.doFilter(RequestLoggingFilter.java:209)
 	at org.lastaflute.web.servlet.filter.LastaShowbaseFilter.viaEmbeddedFilter(LastaShowbaseFilter.java:148)
 	at org.lastaflute.web.servlet.filter.LastaShowbaseFilter.viaOutsideHookDeque(LastaShowbaseFilter.java:139)
 	at org.lastaflute.web.servlet.filter.LastaShowbaseFilter.viaOutsideHook(LastaShowbaseFilter.java:123)
 	at org.lastaflute.web.servlet.filter.LastaShowbaseFilter.doFilter(LastaShowbaseFilter.java:115)
 	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
 	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
 	at org.codelibs.fess.filter.WebApiFilter.doFilter(WebApiFilter.java:51)
 	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
 	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
 	at org.codelibs.fess.filter.CorsFilter.doFilter(CorsFilter.java:58)
 	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
 	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
 	at org.lastaflute.web.servlet.filter.LastaPrepareFilter.toNextFilter(LastaPrepareFilter.java:280)
 	at org.lastaflute.web.servlet.filter.LastaPrepareFilter.viaHotdeploy(LastaPrepareFilter.java:243)
 	at org.lastaflute.web.servlet.filter.LastaPrepareFilter.viaLastaDiContext(LastaPrepareFilter.java:230)
 	at org.lastaflute.web.servlet.filter.LastaPrepareFilter.doFilter(LastaPrepareFilter.java:203)
 	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
 	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
 	at org.codelibs.fess.filter.EncodingFilter.doFilter(EncodingFilter.java:118)
 	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
 	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
 	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
 	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
 	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:540)
 	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
 	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
 	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
 	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)
 	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382)
 	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
 	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:895)
 	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1732)
 	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
 	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
 	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
 	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
 	at java.base/java.lang.Thread.run(Unknown Source)

ありがとうございます。普通のログイン処理の中で起きているようなので、jwtClaimにemailが含まれていれば、ユーザー名がnullになることはなさそうに見えます。

時間が空いてしまいすみません。
検証したところ原因を発見しました。
OpenIdConnectAuthenticator.javaのparseJwtClaim関数において、jwtClaimの中に配列型の要素が含まれていたときに、正しくパースしないことが原因でした。トークンがずれて、emailがnullとなってしまっていました。
正しく処理するように実装しましたが、GitHubにプルリクを出せばよいでしょうか?

また、ログインはできるようになったのですが、ログアウトした後に再度/ssoにアクセスするとエラーになります。
ログを見たところリダイレクトURLのstateパラメータが消えていませんでした。OpenIdConnectAuthenticator.javaのlogout関数はnullで何も処理していないようですが、最低でもセッションを破棄しなければならないと思います。

直前の投稿の後半部分(ログアウトのところ)は、理解が不足していました。
リダイレクトURLのstateパラメータはCSRF攻撃対策のものなので、関係なかったです。

状況としては、FESS内でログイン処理を実行するとエラーが発生し、ログに出力されているリダイレクトURLを直接ブラウザで叩くとログインが成功するという挙動です。
もう少し原因を探ってみます。

連投で申し訳ありません。再ログインができない問題の原因を見つけました。
OpenIdConnectAuthenticator.javaのgetLoginCredential関数内で1箇所nullでreturnしている部分をコメントアウトすることで解決しました。
検証したところ、ログイン処理中にcode,stateがnullでsesStateのみ値を持つ状況が発生していました。
この場合、最後のreturnの処理で再認証をかければよいはずですが、早期returnでnullを返してしまっていることが問題のようです。

public LoginCredential getLoginCredential() {
	return LaRequestUtil.getOptionalRequest().map(request -> {
		if (logger.isDebugEnabled()) {
			logger.debug("Logging in with OpenID Connect Authenticator");
		}
		final HttpSession session = request.getSession(false);
		if (session != null) {
			final String sesState = (String) session.getAttribute(OIC_STATE);
			if (StringUtil.isNotBlank(sesState)) {
				session.removeAttribute(OIC_STATE);
				final String code = request.getParameter("code");
				final String reqState = request.getParameter("state");
				if (logger.isDebugEnabled()) {
					logger.debug("code: {}, state(request): {}, state(session): {}", code, reqState, sesState);
				}
				if (sesState.equals(reqState) && StringUtil.isNotBlank(code)) {
					return processCallback(request, code);
				}
				//return null;  //修正箇所
			}
		}

		return new ActionResponseCredential(() -> HtmlResponse.fromRedirectPathAsIs(getAuthUrl(request)));
	}).orElse(null);
}

プルリクを頂ければ、確認してから取り込みます。