0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Userクラス(org.springframework.security.core.userdetailsパッケージ)のequalsメソッドは何と何を比較しているのかという話

Last updated at Posted at 2025-07-16

事象

認証に関わるサービスクラス(ドメイン層)のテストコードを実行した時、期待値(user)とメソッドが返した値(actual)が異なるのにも関わらず、isEqualTo()がtrueになってしまった。

user = com.example.webapp.test_data.login.User [/*(中略)*/Granted Authorities=[USER]]

actual = com.example.webapp.entity.LoginUser [/*(中略)*/Granted Authorities=[ADMIN, USER]]

	@Test
	void test_loadUserByUsername_ADMIN() {
	//(中略)
		assertThat(actual).isEqualTo(user); //true
	}

userもactualも共にUserクラス(org.springframework.security.core.userdetails.User)(を継承して作成した独自のクラス)のインスタンスであり、username, password, authoritiesをインスタンス変数として持つ

public User(StringSE username,
 StringSE password,
 CollectionSE<? extends GrantedAuthority> authorities)

原因

isEqualTo()は比較にUserクラスのequalsメソッド(java.util.Objectsのオーバーライド)を用いており、equals()が比較するのはインスタンス同士ではなくusernameだけである。よって、authoritiesが違ってもtrueになってしまい、結果的にUser.isEqualTo()もtrueになってしまう。

以下は、ドキュメント(spring.pleiades.io)から引用したUser.equals()の説明

指定されたオブジェクトが同じ username 値を持つ User インスタンスである場合true を返します

以下はその実装

	/**
	 * Returns {@code true} if the supplied object is a {@code User} instance with the
	 * same {@code username} value.
	 * <p>
	 * In other words, the objects are equal if they have the same username, representing
	 * the same principal.
	 */
	@Override
	public boolean equals(Object obj) {
		if (obj instanceof User user) {
			return this.username.equals(user.getUsername());
		}
		return false;
	}

デバッガーで変数を追ってみても、確かにusername同士を比較していることがわかる。

スクリーンショット 2025-07-15 20.57.54.png

対応

authorities同士で比較すると、ちゃんとfalseになる。

	@Test
	void test_loadUserByUsername_ADMIN() {
	//(中略)
		//assertThat(actual).isEqualTo(user); true
		assertThat(actual.getAuthorities()).isEqualTo(user.getAuthorities()); //false
	}

以下は興味のある方のみ

isEqualTo()のメソッド呼び出しを辿ってみる

AbstractAssert.isEqualTo()(org.assertj.core.apiパッケージ)

  /** {@inheritDoc} */
  @Override
  public SELF isEqualTo(Object expected) {
    //(中略)
    objects.assertEqual(info, actual, expected);
    return myself;
  }

Objects.assertEqual()(org.assertj.core.internalパッケージ)

public void assertEqual(AssertionInfo info, Object actual, Object expected) {
    if (!areEqual(actual, expected))
      throw failures.failure(info, shouldBeEqual(actual, expected, comparisonStrategy, info.representation()));
  }

Objects.areEqual()(org.assertj.core.internalパッケージ)

comparisonStrategyというのは、Objectsクラスがフィールドとして持つStandardComparisonStratedy(ComparisonStrategyインターフェースの実装)のインスタンス

  private boolean areEqual(Object actual, Object other) {
    return comparisonStrategy.areEqual(actual, other);
  }

ComparisonStrategy.areEqual()(org.assertj.core.internalパッケージ)

抽象メソッド

	public interface ComparisonStrategy {
  /**
   * Returns true if actual and other are equal according to the implemented comparison strategy.
   * 
   * @param actual the object to compare to other
   * @param other the object to compare to actual
   * @return true if actual and other are equal according to the underlying comparison strategy.
   */
  boolean areEqual(Object actual, Object other);

StandardComparisonStratedy.areEqual()(org.assertj.core.internalパッケージ)

上にも書いたが、ComparisonStrategyインターフェースの実装

 /**
   * Returns {@code true} if the arguments are deeply equal to each other, {@code false} otherwise.
   * <p>
   * It mimics the behavior of {@link java.util.Objects#deepEquals(Object, Object)}, but without performing a reference
   * check between the two arguments. According to {@code deepEquals} javadoc, the reference check should be delegated
   * to the {@link Object#equals equals} method of the first argument, but this is not happening. Bug JDK-8196069 also
   * mentions this gap.
   *
   * @param actual the object to compare to {@code other}
   * @param other the object to compare to {@code actual}
   * @return {@code true} if the arguments are deeply equal to each other, {@code false} otherwise
   *
   * @see <a href="https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8196069">JDK-8196069</a>
   *
   */
  @Override
  public boolean areEqual(Object actual, Object other) {
    //(中略。かなり長い分岐処理。actualとotherが配列である場合や、基本型である場合の比較をしている。) 
    return actual.equals(other);
  }

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?