我在上一篇文章中介绍了Application
类,您可以在其中设置后端服务。 我添加的一个示例服务是UserService
。 该服务将加载包含用户名和密码集的Java用户属性文件; 稍后将用于对用户进行身份验证以登录到Web应用程序。 现在,我将展示如何使用标准Servlet API以及此后端服务完成登录部分。
从高层次上讲,我们希望将某些Web资源(这意味着Servlet提供的某些URL,例如“ / sysprops”或“ / user”)限制为仅在我们的用户属性文件中已知的客户端用户。 用户可以使用匹配的密码来标识自己。 通常使用用户登录表单来完成此操作,对其进行身份验证,然后将登录令牌插入Http Session范围空间。 然后可以使用该登录令牌来验证是否允许用户访问受限资源。 我们只对单一授权感兴趣(未定义任何角色,任何登录的用户都可以访问任何受保护的URL。)
在SysPropsServlet
提供的我以前的SysPropsServlet
,您已经看到了一个映射到“ / sysprops” URL的示例,该示例仅生成系统信息HTML表。 这些是敏感信息,因此我们要保护此URL。 我们将需要创建一个实现javax.servlet.Filter
接口的类,然后使用此过滤器添加“ / sysprops” URL,以便它可以在实际的Servlet之前对请求进行预处理。 此过滤器使我们可以检查HTTP请求对象并在需要时中止请求,从而限制了访问。
package zemian.servlet3example.web;import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import zemian.service.logging.Logger;@WebFilter(urlPatterns={"/sys-props", "/user"})
public class LoginRequiredFilter implements Filter {private static final Logger LOGGER = new Logger(LoginRequiredFilter.class);public static final String LOGIN_REDIRECT = "LOGIN_REDIRECT";@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {if (request instanceof HttpServletRequest) {HttpServletRequest req = (HttpServletRequest) request;LOGGER.trace("Checking LoginSession token for uri=%s", req.getRequestURI());LoginSession loginSession = LoginServlet.getOptionalLoginSession(req);if (loginSession == null) {LOGGER.debug("No LoginSession token found; forwarding request to login page.");// We need to save the old URI so we can auto redirect after login.req.setAttribute(LOGIN_REDIRECT, req.getRequestURI());req.getRequestDispatcher("/login").forward(request, response);return;} else {LOGGER.debug("Request allowed using LoginSession token=%s", loginSession.getId());}}chain.doFilter(request, response);}@Overridepublic void destroy() {}}
请注意,您可以配置此过滤器以匹配要保护的多个URL。 您甚至可以使用通配符模式,例如“ / *”,它将保护您应用程序中的每个URL! 过滤器只是在Http Session空间中LoginSession
我们稍后将创建的LoginSession
对象。 如果找到它,则它使请求通过,否则它将重定向到LoginServlet
页面,该页面由LoginServlet
类提供服务(请注意RETURN语句将提前退出filter方法,而无需调用filter链!)。
LoginServlet
类是一种表单处理Servlet,它将提示用户输入用户名和密码。 如果成功,那么我们将LoginSession
令牌对象插入到HttpSession空间中,这就是上面的过滤器正在寻找的内容。 这是处理Servlet代码。
package zemian.servlet3example.web;import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import zemian.service.logging.Logger;
import zemian.servlet3example.service.Application;
import zemian.servlet3example.service.UserService;@WebServlet("/login")
public class LoginServlet extends HtmlWriterServlet {private static final Logger LOGGER = new Logger(LoginServlet.class);@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {HtmlWriter html = createHtmlWriter(req, resp);String message;// Check to see if we are doing logout or not.LoginSession loginSession = getOptionalLoginSession(req);if (loginSession != null && req.getParameter("logout") != null) {logout(req);message = "Your have successfully logged out.";} else { message = (String)req.getAttribute("message");if (message == null)message = "";} // Show a login formString redirectUri = (String)req.getAttribute(LoginRequiredFilter.LOGIN_REDIRECT);String redirectHtmlTag = "";if (redirectUri != null) {redirectHtmlTag = "<input type='hidden' name='redirectUri' value='" + redirectUri + "'/>";}html.header().h(1, "Please Login").p(message).println("<form method='post' action='login'>").println(redirectHtmlTag).println("<p/>Username: <input type='text' name='username'/>").println("<p/>Password: <input type='password' name='password'/>").println("<p/><input type='submit' value='Submit'/>").println("</form>").footer();}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {LOGGER.debug("Processing login form.");if (login(req)) {// Login succeed, we should auto redirect user if exists.String redirectUri = req.getParameter("redirectUri");if (redirectUri != null) {LOGGER.debug("Redirect after login to: %s", redirectUri);resp.sendRedirect(redirectUri);return;}}// Show the form again in case login failed or user didn't provide a redirectdoGet(req, resp);} protected LoginSession createLoginSession(HttpServletRequest req, String username) {LoginSession result = new LoginSession(username);req.getSession(true).setAttribute(LoginSession.LOGIN_SESSION_KEY, result);return result;}protected void removeLoginSession(HttpServletRequest req) {HttpSession session = req.getSession(false);if (session != null) {session.removeAttribute(LoginSession.LOGIN_SESSION_KEY);}}private boolean login(HttpServletRequest req) throws IOException {String username = req.getParameter("username");String password = req.getParameter("password");UserService userService = Application.getInstance().getUserService();if (userService.validate(username, password)) {LOGGER.info("User %s logged in successfully.", username);// Create Session Data here after successful authenticated.LoginSession loginsession = getOptionalLoginSession(req);if (loginsession == null) {createLoginSession(req, username);req.setAttribute("message", "You have successfully logged in.");} else {req.setAttribute("message", "You already have logged in."); }} else {LOGGER.info("User %s failed to login.", username);req.setAttribute("message", "Invalid login.");}return true;}/** Return LoginSession if found in HttpSession scope, else return NULL value. */public static LoginSession getOptionalLoginSession(HttpServletRequest req) {LoginSession result = null;HttpSession session = req.getSession(false);if (session != null)result = (LoginSession)session.getAttribute(LoginSession.LOGIN_SESSION_KEY);return result;}
}
在LoginServlet
类中,我们使用UserService
服务来验证用户名和密码。 我们显示带有GET请求的登录表单,然后使用POST操作处理登录。 检查用户名和密码后,我们将创建LoginSession
对象。 这只是表示会话令牌的简单POJO。 您可以保留所需的任何用户信息。 我不会在这里列出,但是您可以在GitHub上浏览它。 请注意,但是您应该使其可序列化,因为HttpSession中存储的任何数据都可能会被应用程序服务器序列化/反序列化。
还要注意,我也已经将Logout功能实现到LoginServlet
类中。 您只需传递“注销”查询参数,它将被检测到并从会话中删除登录令牌。 这样做时,请确保使HttpSession本身无效,只是为了保证安全。 我还公开了一个静态帮助器getOptionalLoginSession
, getOptionalLoginSession
在少数几个类之间使用,以检查用户是否已登录。
这几个类很简单,但是在如何管理Session数据上展示了Servlet过滤器和Servlet的用法。 这种编程模式允许用户通过应用程序拥有自己的浏览会话和隐私。
如果要在GlassFish服务器中运行我的servlet3-example ,则可以使用here中列出的任何用户登录。
翻译自: https://www.javacodegeeks.com/2015/01/ee-servlet-3-developing-user-login-with-session-and-filter.html