programing

사용자가 웹 응용 프로그램에 로그인 할 때 "로그인 상태 유지"를 구현하는 방법

goodcopy 2021. 1. 17. 11:58
반응형

사용자가 웹 응용 프로그램에 로그인 할 때 "로그인 상태 유지"를 구현하는 방법


대부분의 웹 사이트에서 사용자가 시스템에 로그인하기 위해 사용자 이름과 암호를 제공하려고 할 때 "로그인 상태 유지"와 같은 확인란이 있습니다. 확인란을 선택하면 동일한 웹 브라우저에서 모든 세션에 로그인 한 상태로 유지됩니다. 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>

Java EE 8 이상

자바 EE 8 이상에 있다면, 넣어 @RememberMe사용자 정의에 HttpAuthenticationMechanism와 함께 RememberMeIdentityStore.

@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 EE Kickoff Application 에서 실제 사례찾을 수 있습니다 .


Java EE 6/7

Java EE 6 또는 7을 사용하는 경우 고유 클라이언트를 추적하기 위해 수명이 긴 쿠키를 홈 그로우 HttpServletRequest#login()하고 사용자가 로그인하지 않았지만 쿠키가있을 때 프로그래밍 방식 로그인을 제공하는 Servlet 3.0 API를 사용합니다 .

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, 고유 한 쿠키 이름 예해야 "remember"하고는 COOKIE_AGE예를 들어 초 나이,해야 259200030 일)

제한된 페이지에 매핑되는 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);
}

이러한 쿠키 도우미 메서드와 함께 사용하면 (서블릿 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사용자가 암호를 변경할 때마다 값 을 바꾸는 것도 좋은 방법 입니다.


Java EE 5 이하

업그레이드하세요.


일반적으로 이것은 다음과 같이 수행됩니다.

사용자로 로그인 할 때 특정 시간 (보통 1-2 주) 후에 만료되는 클라이언트에 쿠키를 설정하고 데이터베이스에 쿠키 값을 저장합니다.

When a new request comes in you check that the certain cookie exists and if so look into the database to see if it matches a certain account. If it matches you will then "loosely" log in that account. When i say loosely i mean you only let that session read some info and not write information. You will need to request the password in order to allow the write options.

This is all that is. The trick is to make sure that a "loosely" login is not able to do a lot of harm to the client. This will somewhat protect the user from someone who grabs his remember me cookie and tries to log in as him.


You cannot login a user completely via HttpServletRequest.login(username, password) since you shouldn't keep both username and plain text password in the database. Also you cannot perform this login with a password hash which is saved in the database. However, you need to identify a user with a cookie/DB token but log him/her in without entering password using custom login module (Java class) based on Glassfish server API.

See the following links for more details:

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

Custom Security mechanism in Java EE 6/7 application


Although the answer by BalusC (the part for Java EE 6/7) gives useful hints, I doesn't work in modern containers, because you can't map a login filter to pages that are protected in a standard way (as confirmed in the comments).

If for some reason you can't use Spring Security (which re-implements the Servlet Security in an incompatible way), then it's better to stay with <auth-method>FORM and put all the logic into an active login page.

Here's the code (the full project is here: 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...

Here's some explanation:

Instead of calling request.login() we establish a new TCP connection to our HTTP listener and post the login form to the /j_security_check address. This allows the container to redirect us to the initially requested web page and restore the POST data (if any). Trying to obtain this info from a session attribute or RequestDispatcher.FORWARD_SERVLET_PATH would be container-specific.

We don't use a servlet filter for automatic login, because containers forward/redirect to the login page BEFORE the filter is reached.

The dynamic login page does all the job, including:

  • actually rendering the login form
  • accepting the filled form
  • calling /j_security_check under the hood
  • displaying login errors
  • automatic login
  • redirecting back to the initially requested page

To implement the "Stay Logged In" feature we save the credentials from the submitted login form in the servlet context attribute (for now). Unlike in the SO answer above, the password is not hashed, because only certain setups accept that (Glassfish with a jdbc realm). The persistent cookie is associated with the credentials.

The flow is the following:

  • Get forwarded/redirected to the login form
  • If we're served as the <form-error-page> then render the form and the error message
  • Otherwise, if some credentials are submitted, then store them and call /j_security_check and redirect to the outcome (which might be us again)
  • Otherwise, if the cookie is found, then retrieve the associated credentials and continue with /j_security_check
  • If none of the above, then render the login form without the error message

The code for /j_security_check sends a POST request using the current JSESSIONID cookie and the credentials either from the real form or associated with the persistent cookie.

ReferenceURL : https://stackoverflow.com/questions/5082846/how-to-implement-stay-logged-in-when-user-login-in-to-the-web-application

반응형