当用户login到Web应用程序时如何实现“保持login状态”
在大多数网站上,当用户要提供用户名和密码login到系统时,有一个checkbox如“保持login状态”。 如果选中该框,则会使您从同一个Web浏览器的所有会话中login。 我如何在Java EE中实现相同的function?
我正在使用基于FORM的容器pipe理身份validation与JSFlogin页面。
<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>
使用一个长生存的cookie来跟踪唯一的客户端,并使用新的Servlet 3.0 API提供的程序性loginHttpServletRequest#login()
当用户没有login但存在cookie。
如果您pipe理由JDBCRealm
提供给Glassfish的数据库表中的用户,并且与您的User
实体来自的表相同,则这是最容易实现的。 然后,可以创build另一个表,其中java.util.UUID
值为PK,而用户的标识为FK。
假设以下login表单:
<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>
和下面在映射到/login
的Servlet
doPost()
方法中:
String username = request.getParameter("username"); String password = hash(request.getParameter("password")); boolean remember = "true".equals(request.getParameter("remember")); User user = userDAO.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(); rememberDAO.save(uuid, user); addCookie(response, COOKIE_NAME, uuid, COOKIE_AGE); } else { rememberDAO.delete(user); removeCookie(response, COOKIE_NAME); } }
( COOKIE_NAME
应该是唯一的cookie名称,例如"remember"
, COOKIE_AGE
应该是以秒为单位的年龄,例如30天为2592000
)
以下是如何映射到受限制页面的Filter
的doFilter()
方法:
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 = rememberDAO.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辅助方法(在Servlet API中缺less它们):
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
非常难以暴力破解,但您可以为用户提供一个选项,以将“记住”选项locking到用户的IP地址( request.getRemoteAddr()
),并将其存储在数据库中并进行比较。 这使得它更加健壮。
每当用户更改密码时,更换UUID
值也是一种很好的做法。
通常这是这样做的:
当你login一个用户时,你也会在客户端上设置一个cookie(并将cookie的值存储在数据库中),在一定时间后(通常为1-2周)过期。
当有新的请求进来时,检查是否存在某个cookie,如果是这样,请查看数据库以查看它是否与某个帐户匹配。 如果匹配,则会“松散地”login该帐户。 当我说松散我的意思是你只让该会议读取一些信息,而不是写信息。 您将需要请求密码以允许写入选项。
这就是全部。 诀窍是确保“松散”login不会对客户造成很大的伤害。 这将有助于保护用户免受那些抓住他记得我cookie的人,并尝试以他身份login。
您不能通过HttpServletRequest.login(用户名,密码)完全login用户,因为您不应该在数据库中保留用户名和纯文本密码。 此外,您不能使用保存在数据库中的密码哈希执行此login。 但是,您需要使用Cookie / DB令牌来标识用户,但使用基于Glassfish服务器API的自定义login模块(Java类)input密码时不需要input密码。
请参阅以下链接了解更多详情:
http://www.lucubratory.eu/custom-jaas-realm-for-glassfish-3/
Java EE 6/7应用程序中的定制安全机制