56

ほとんどのWebサイトでは、ユーザーがシステムにログインするためのユーザー名とパスワードを入力しようとすると、[ログインしたままにする]などのチェックボックスが表示されます。このチェックボックスをオンにすると、同じWebブラウザからすべてのセッションでログインしたままになります。Java EEで同じものを実装するにはどうすればよいですか?

JSFログインページでFORMベースのコンテナ管理認証を使用しています。

<security-constraint>
    <display-name>Student</display-name>
    <web-resource-collection>
        <web-resource-name>CentralFeed</web-resource-name>
        <description/>
        <url-pattern>/CentralFeed.jsf</url-pattern>
    </web-resource-collection>        
    <auth-constraint>
        <description/>
        <role-name>STUDENT</role-name>
        <role-name>ADMINISTRATOR</role-name>
    </auth-constraint>
</security-constraint>
 <login-config>
    <auth-method>FORM</auth-method>
    <realm-name>jdbc-realm-scholar</realm-name>
    <form-login-config>
        <form-login-page>/index.jsf</form-login-page>
        <form-error-page>/LoginError.jsf</form-error-page>
    </form-login-config>
</login-config>
<security-role>
    <description>Admin who has ultimate power over everything</description>
    <role-name>ADMINISTRATOR</role-name>
</security-role>    
<security-role>
    <description>Participants of the social networking Bridgeye.com</description>
    <role-name>STUDENT</role-name>
</security-role>
4

4 に答える 4

112

JavaEE8以降

Java EE 8以降を使用している場合は、。と一緒に@RememberMeカスタムを設定してください。HttpAuthenticationMechanismRememberMeIdentityStore

@ApplicationScoped
@AutoApplySession
@RememberMe
public class CustomAuthenticationMechanism implements HttpAuthenticationMechanism {

    @Inject
    private IdentityStore identityStore;

    @Override
    public AuthenticationStatus validateRequest(HttpServletRequest request, HttpServletResponse response, HttpMessageContext context) {
        Credential credential = context.getAuthParameters().getCredential();

        if (credential != null) {
            return context.notifyContainerAboutLogin(identityStore.validate(credential));
        }
        else {
            return context.doNothing();
        }
    }
}
public class CustomIdentityStore implements RememberMeIdentityStore {

    @Inject
    private UserService userService; // This is your own EJB.
    
    @Inject
    private LoginTokenService loginTokenService; // This is your own EJB.
    
    @Override
    public CredentialValidationResult validate(RememberMeCredential credential) {
        Optional<User> user = userService.findByLoginToken(credential.getToken());
        if (user.isPresent()) {
            return new CredentialValidationResult(new CallerPrincipal(user.getEmail()));
        }
        else {
            return CredentialValidationResult.INVALID_RESULT;
        }
    }

    @Override
    public String generateLoginToken(CallerPrincipal callerPrincipal, Set<String> groups) {
        return loginTokenService.generateLoginToken(callerPrincipal.getName());
    }

    @Override
    public void removeLoginToken(String token) {
        loginTokenService.removeLoginToken(token);
    }

}

実際の例は、Java EEKickoffApplicationにあります。


Java EE 6/7

HttpServletRequest#login()Java EE 6または7を使用している場合は、存続期間の長いCookieをホームグローして一意のクライアントを追跡し、ユーザーがログインしていないがCookieが存在する場合は、サーブレット3.0APIが提供するプログラムログインを使用します。

java.util.UUIDこれは、値がPKで、問題のユーザーのIDがFKである別のDBテーブルを作成する場合に最も簡単に実現できます。

次のログインフォームを想定します。

<form action="login" method="post">
    <input type="text" name="username" />
    <input type="password" name="password" />
    <input type="checkbox" name="remember" value="true" />
    <input type="submit" />
</form>

そして、にマッピングされているdoPost()メソッドの次の:Servlet/login

String username = request.getParameter("username");
String password = hash(request.getParameter("password"));
boolean remember = "true".equals(request.getParameter("remember"));
User user = userService.find(username, password);

if (user != null) {
    request.login(user.getUsername(), user.getPassword()); // Password should already be the hashed variant.
    request.getSession().setAttribute("user", user);

    if (remember) {
        String uuid = UUID.randomUUID().toString();
        rememberMeService.save(uuid, user);
        addCookie(response, COOKIE_NAME, uuid, COOKIE_AGE);
    } else {
        rememberMeService.delete(user);
        removeCookie(response, COOKIE_NAME);
    }
}

COOKIE_NAMEたとえば、一意のCookie名で"remember"あるCOOKIE_AGE必要があり、秒単位の年齢、たとえば259200030日間である必要があります)

制限されたページにマップされるaのdoFilter()メソッドは次のようになります。Filter

HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
User user = request.getSession().getAttribute("user");

if (user == null) {
    String uuid = getCookieValue(request, COOKIE_NAME);

    if (uuid != null) {
        user = rememberMeService.find(uuid);

        if (user != null) {
            request.login(user.getUsername(), user.getPassword());
            request.getSession().setAttribute("user", user); // Login.
            addCookie(response, COOKIE_NAME, uuid, COOKIE_AGE); // Extends age.
        } else {
            removeCookie(response, COOKIE_NAME);
        }
    }
}

if (user == null) {
    response.sendRedirect("login");
} else {
    chain.doFilter(req, res);
}

これらのCookieヘルパーメソッドとの組み合わせ(サーブレットAPIにないのは残念です):

public static String getCookieValue(HttpServletRequest request, String name) {
    Cookie[] cookies = request.getCookies();
    if (cookies != null) {
        for (Cookie cookie : cookies) {
            if (name.equals(cookie.getName())) {
                return cookie.getValue();
            }
        }
    }
    return null;
}

public static void addCookie(HttpServletResponse response, String name, String value, int maxAge) {
    Cookie cookie = new Cookie(name, value);
    cookie.setPath("/");
    cookie.setMaxAge(maxAge);
    response.addCookie(cookie);
}

public static void removeCookie(HttpServletResponse response, String name) {
    addCookie(response, name, null, 0);
}

総当たり攻撃は非常に困難ですがUUID、「記憶」オプションをユーザーのIPアドレス(request.getRemoteAddr())にロックし、データベースに保存/比較するオプションをユーザーに提供することもできます。これにより、少し堅牢になります。また、データベースに「有効期限」を保存しておくと便利です。

UUIDまた、ユーザーがパスワードを変更するたびに値を置き換えることもお勧めします。


JavaEE5以下

アップグレードしてください。

于 2011-02-22T20:50:19.167 に答える
22

通常、これは次のように行われます。

ユーザーにログインすると、クライアントにCookieを設定し(そしてCookieの値をデータベースに保存し)、一定の時間(通常は1〜2週間)後に期限切れになります。

新しいリクエストが届いたら、特定のCookieが存在することを確認し、存在する場合はデータベースを調べて、特定のアカウントと一致するかどうかを確認します。一致する場合は、そのアカウントに「大まかに」ログインします。私が大まかに言うとき、私はあなたがそのセッションにいくつかの情報を読ませ、情報を書かないようにすることを意味します。書き込みオプションを許可するには、パスワードを要求する必要があります。

これがすべてです。秘訣は、「緩い」ログインがクライアントに大きな害を及ぼさないようにすることです。これにより、ユーザーを覚えているCookieを取得して、ユーザーとしてログインしようとするユーザーからユーザーをある程度保護できます。

于 2011-02-22T20:30:00.350 に答える
4

ユーザー名とプレーンテキストのパスワードの両方をデータベースに保持するべきではないため、HttpServletRequest.login(username、password)を介してユーザーに完全にログインすることはできません。また、データベースに保存されているパスワードハッシュを使用してこのログインを実行することはできません。ただし、Cookie / DBトークンでユーザーを識別する必要がありますが、GlassfishサーバーAPIに基づくカスタムログインモジュール(Javaクラス)を使用して、パスワードを入力せずにユーザーをログインします。

詳細については、次のリンクを参照してください。

http://www.lucubratory.eu/custom-jaas-realm-for-glassfish-3/

JavaEE6/7アプリケーションのカスタムセキュリティメカニズム

于 2014-11-28T20:34:23.197 に答える
0

BalusC(Java EE 6/7の一部)による回答は有用なヒントを提供しますが、標準的な方法で保護されているページにログインフィルターをマップできないため、最新のコンテナーでは機能しません(コメント)。

何らかの理由でSpringSecurity(互換性のない方法でサーブレットセキュリティを再実装する)を使用できない場合は、そのままにして<auth-method>FORM、すべてのロジックをアクティブなログインページに配置することをお勧めします。

コードは次のとおりです(完全なプロジェクトはここにあります:https ://github.com/basinilya/rememberme )

web.xml:

    <form-login-config>
        <form-login-page>/login.jsp</form-login-page>
        <form-error-page>/login.jsp?error=1</form-error-page>
    </form-login-config>

login.jsp:

if ("1".equals(request.getParameter("error"))) {
    request.setAttribute("login_error", true);
} else {
    // The initial render of the login page
    String uuid;
    String username;

    // Form fields have priority over the persistent cookie

    username = request.getParameter("j_username");
    if (!isBlank(username)) {
        String password = request.getParameter("j_password");

        // set the cookie even though login may fail
        // Will delete it later
        if ("on".equals(request.getParameter("remember_me"))) {
            uuid = UUID.randomUUID().toString();
            addCookie(response, COOKIE_NAME, uuid, COOKIE_AGE); // Extends age.
            Map.Entry<String,String> creds =
                    new AbstractMap.SimpleEntry<String,String>(username,password);
            rememberMeServiceSave(request, uuid, creds);
        }
        if (jSecurityCheck(request, response, username, password)) {
            return;
        }
        request.setAttribute("login_error", true);
    }

    uuid = getCookieValue(request, COOKIE_NAME);
    if (uuid != null) {
        Map.Entry<String,String> creds = rememberMeServiceFind(request, uuid);
        if (creds != null) {
            username = creds.getKey();
            String password = creds.getValue();
            if (jSecurityCheck(request, response, username, password)) {
                return; // going to redirect here again if login error
            }
            request.setAttribute("login_error", true);
        }
    }
}

// login failed
removeCookie(response, COOKIE_NAME);
// continue rendering the login page...

ここにいくつかの説明があります:

呼び出す代わりにrequest.login()、HTTPリスナーへの新しいTCP接続を確立し、ログインフォームを/j_security_checkアドレスに送信します。これにより、コンテナは最初に要求されたWebページにリダイレクトし、POSTデータ(存在する場合)を復元できます。セッション属性からこの情報を取得しようとするRequestDispatcher.FORWARD_SERVLET_PATHと、コンテナ固有になります。

コンテナはフィルタに到達する前にログインページに転送/リダイレクトするため、自動ログインにはサーブレットフィルタを使用しません。

動的ログインページは、以下を含むすべてのジョブを実行します。

  • ログインフォームを実際にレンダリングする
  • 記入済みのフォームを受け入れる
  • /j_security_checkボンネットの下で呼び出す
  • ログインエラーの表示
  • 自動ログイン
  • 最初に要求されたページにリダイレクトする

「ログインしたまま」機能を実装するために、送信されたログインフォームからの資格情報をサーブレットコンテキスト属性に保存します(今のところ)。上記のSO回答とは異なり、パスワードはハッシュされません。これは、特定のセットアップのみがパスワードを受け入れるためです(jdbcレルムを持つGlassfish)。永続的なCookieは資格情報に関連付けられています。

フローは次のとおりです。

  • ログインフォームに転送/リダイレクトされます
  • 私たちが役立っている場合は<form-error-page>、フォームとエラーメッセージをレンダリングします
  • それ以外の場合、いくつかのクレデンシャルが送信された場合は、それらを保存し、呼び出し/j_security_checkて結果にリダイレクトします(これは再び私たちである可能性があります)
  • それ以外の場合、Cookieが見つかった場合は、関連する資格情報を取得して続行します/j_security_check
  • 上記のいずれでもない場合は、エラーメッセージなしでログインフォームをレンダリングします

のコードは、現在のCookieと、実際のフォームからの、または永続的なCookieに関連付けられた資格情報/j_security_checkを使用してPOSTリクエストを送信します。JSESSIONID

于 2019-03-15T10:29:13.813 に答える