사용자가 웹 응용 프로그램에 로그인 할 때 "로그인 상태 유지"를 구현하는 방법
대부분의 웹 사이트에서 사용자가 시스템에 로그인하기 위해 사용자 이름과 암호를 제공하려고 할 때 "로그인 상태 유지"와 같은 확인란이 있습니다. 확인란을 선택하면 동일한 웹 브라우저에서 모든 세션에 로그인 한 상태로 유지됩니다. 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
예를 들어 초 나이,해야 2592000
30 일)
제한된 페이지에 매핑되는 의 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
'programing' 카테고리의 다른 글
SBT보고 "GZIPInputStream에서 InputStream 래핑 오류 : java.io.EOFException"을 수정하는 방법은 무엇입니까? (0) | 2021.01.18 |
---|---|
HAX 장치를 열지 못했습니다! (0) | 2021.01.18 |
CSV 파일에 새 열을 추가하는 방법은 무엇입니까? (0) | 2021.01.17 |
잠금 및 잠금 해제를위한 Eventviewer eventid (0) | 2021.01.17 |
클립 보드에서 R로 데이터를 복사하여 붙여 넣는 방법은 무엇입니까? (0) | 2021.01.17 |