Cognos SDK サンプルのカスタム認証プロバイダでSSOと通常認証を共存させる

#概要
Cognos AnalyticsのSDKで、カスタム認証プロバイダーというCognos認証をJavaプログラムで行うサンプルがあります。※カスタム認証に関しては、以下参照。
Cognos カスタム認証のセットアップ手順
https://qiita.com/shinyama/items/1f9c7842a3966233a5d7

こちらのサンプルプログラムを使用すると、認証のためのユーザー情報をDb2などのデータベースに持たせる事ができて便利なのですが、シングルサインオン(SSO)と通常のユーザーID/パスワードでの認証を共存させようとすると、実はこのサンプルで色々と修正しないといけないところが出てきます。

まず前提として、サンプルのJDBCSampleを使用して、SSOと通常認証を共存させる場合、以下の制限があります。
**認証DBテーブルに登録しておく各ユーザーのパスワードは、ユーザーIDと同一でないといけない**

つまり、coguser1ユーザーのパスワードはcoguser1にしておかないといけません。
※SSOはやらず、通常のユーザーID・パスワード認証のみを行う場合は、こんな制限はありません。

SSO時にHeaderの変数からユーザーIDを取得する、JDBCSample.javaのgetTrustedEnvironmentVaribleValueが以下のようになっている事から、この意味がわかるかと思います。

```html
	protected static Credential getTrustedEnvironmentVaribleValue(final IBiBusHeader2 authRequest)
	{
		final Credential credential = new Credential();
		final String[] userNames;
		userNames = authRequest.getTrustedEnvVarValue("REMOTE_USER");

		if (userNames != null && userNames.length > 0)
		{
			credential.setUsername(userNames[0]);
			// To simplified the case, we set up the password the same as userName
			credential.setPassword(userNames[0]);  <--こちら
		}

		return credential;
	}
```

#ソースコードの修正箇所
こんな制限があるようでは、本番環境ではカスタム認証サンプルは使う事はできないですね、と言われてしまう事が有りますので、SSOと通常認証を共存させても、ユーザーIDとパスワードを異なる設定にできるようにするために、以下のコードの修正を行っていく必要があります。

**RestorableJDBCSample.javaの修正**

```html
	public IVisa logon(IBiBusHeader2 theAuthRequest)
			throws UserRecoverableException, SystemRecoverableException, UnrecoverableException {
		RestorableJDBCVisa visa = null;
		ConnectionManager mgr = ConnectionManager.get();

		// ★SSOかどうかを判定するフラグを作成し、SSOの場合 1 を入力
		int sso = 0;

		// 1 - Look for trusted credentials
		JDBCSample.Credential credential = null;

		// Scheduled report service credentials
		credential = JDBCSample.getTrustedCredentialValues(theAuthRequest);
		// if credential could not be retreived and sso is enabled
		if (credential.isEmpty() && mgr.singleSignOnEnabled()) {
			credential = JDBCSample.getTrustedEnvironmentVaribleValue(theAuthRequest);
		}

		// if credential was successfully retrieved set sso flag to 1
		if (!credential.isEmpty()) {
			sso = 1;
		}

		if (credential.isEmpty()) {
			credential = JDBCSample.getCredentialValues(theAuthRequest);
		}

		// If credentials are still empty, retrieve from form data (logon
		// prompt)
		if (credential.isEmpty()) {
			credential = JDBCSample.getFormFieldValues(theAuthRequest);
		}
		// ★ここまで

		if (credential.isEmpty() && mgr.singleSignOnEnabled()) {
			// null implies the provider has to start the dance so throw a
			// SysRecov.
			// the SysRecov needs to have the name of the variable we look for
			// in
			// the second parameter
			SystemRecoverableException e = new SystemRecoverableException("Challenge for UserID", "UserID");    // ★REMOTE_USERから変更が必要
			throw e;
		}

		if (credential.isEmpty()) {
			// Assume this is the initial logon and pass null for errorDetails
			generateAndThrowExceptionForLogonPrompt(null);
		}

		try {
			//
			// Create a Visa for the new user. Unlike the JDBCVisa, we will not
			// pas a reference to
			// the ConnectionManager. Instead, the Visa will use a static
			// accessor to get the singleton
			// reference to the ConnectionManager
			//
			// ★SSOの場合 initSSOを呼び出す処理を入れる
			visa = new RestorableJDBCVisa();
			if (sso == 1) {
				visa.initSSO(credential.getUsername(), credential.getPassword());
			} else {
				visa.init(credential.getUsername(), credential.getPassword());
			}
			// ★ここまで

		} catch (final UnrecoverableException ex) {
			final String errorDetails = RestorableJDBCSample.getErrorDetails(ex);

			// Something went wrong, probably because the user's credentials
			// are invalid.
			generateAndThrowExceptionForLogonPrompt(errorDetails);
		}
		return visa;
	}
```


**RestorableJDBCVisa.javaの修正①**

```html
	// ★SSOの時createAccountSSOを呼び出すinitSSO処理をまるごと追加
	public void initSSO(String username, String password) throws UnrecoverableException {
		try {
			ConnectionManager connectionManager = ConnectionManager.get();
			// Create account object for the user.

			account = QueryUtil.createAccountSSO(connectionManager, username, password);

			QueryUtil.updateMembership(connectionManager, this);
			this.username = username;
			this.password = password;

			// This is very important. You must flag this Visa as changed
			// whenever it's state (i.e. credentials)
			// is modified
			hasChanged = true;
		} catch (final SQLException e) {
			throw new UnrecoverableException("Connection Error",
					"Database connection failure. Reason: " + ConnectionManager.getSqlExceptionDetails(e));
		}
	}
	// ★ここまで
```

**RestorableJDBCVisa.javaの修正②**

```html
	// ★ここの箇所でもcreateAccountSSOに変更
	private boolean validateConnection(final String theUsername, final String thePassword) {
		try {
			QueryUtil.createAccountSSO(ConnectionManager.get(), theUsername, thePassword);
			username = theUsername;
			password = thePassword;
		} catch (UnrecoverableException ex) {
			return false;
		}

		return true;
	}
	// ★ここまで
```

**RestorableJDBCVisa.javaの修正③**

```html
	// ★ここをinitSSOに変更。これをやっておかないと、レポートのバッググラウンド実行がエラーになる。
	public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
		try {
			String username = in.readUTF();
			String password = in.readUTF();
			initSSO(username, password);   //★ここ
		} catch (UnrecoverableException e) {
			throw new IOException(e);
		}
	}
	// ★ここまで
```

**JDBCSample.javaの修正**

```html
	protected static Credential getTrustedEnvironmentVaribleValue(final IBiBusHeader2 authRequest)
	{
		final Credential credential = new Credential();
		final String[] userNames;
		userNames = authRequest.getTrustedEnvVarValue("UserID");  // ★REMOTE_USERから変更が必要

		if (userNames != null && userNames.length > 0)
		{
			credential.setUsername(userNames[0]);
			// To simplified the case, we set up the password the same as userName
			credential.setPassword(userNames[0]);
		}

		return credential;
	}
```

**QueryUtil.javaの修正**

```html
        //★USERNAMEのみがマッチした場合に結果を返すcreateAccountSSO処理を丸ごと追加
	public static Account createAccountSSO(final ConnectionManager connectionManager, final String userName, final String password)
			throws UnrecoverableException
	{

		final Object[][] data =
				QueryUtil.query(connectionManager.getConnection(),
						"SELECT USERID FROM USERS WHERE USERNAME = ?", userName);
		final String userID = QueryUtil.getSingleStringResult(data);

		if (null != userID)
		{
			final String userSearchPath = "u:" + userID;
			return connectionManager.getAccountCache().findAccount(userSearchPath);
		}

		throw new UnrecoverableException("Invalid Credentials", "Could not authenticate with the provide credentials");
	}
        //★ここまで
```

以上です。
是非ご活用下さい！
もしやってみて、うまく行かなかった点あれば、是非フィードバック下さい！


#こちらも併せてご参照下さい

**Cognos SDK でカスタム認証プロバイダーを開発する時に参考になるJDBCSampleの説明**
https://qiita.com/shinyama/items/33b6349916380b8fe960

**Cognos SDK サンプルのカスタム認証プロバイダでDB接続数増大を防ぐ修正**
https://qiita.com/shinyama/items/22e858818b56294710f6