一、引言
传统软件,架构单一,登录认证相对简单,基本都是通过Session来实现的,即通过对所有进入的URI进行解析,并取得当前Session中的User信息。而互联网软件,架构复杂,需部署多台机器,session并不唯一,写session方式会存在各种问题,因此我们使用写cookie的方式来进行认证。
在这次项目中,我们使用的是springMVC拦截器及登录注解认证,以token的方式进行登录认证。
二、登录认证过程
1、用户输入用户名、密码进行登录。
2、通过mvc访问后台login方法,比对数据库(或缓存)中的用户名及密码是否一致。
3、若一致,则根据用户信息生成一个唯一的token(可使用用户的信息进行几次MD5+UID)。
4、将生成的token放入用户的cookie中,返回给用户。
5、当用户访问相关功能时,判断该action是否加了登录认证注解,若加了该注解则需要通过拦截器进行token验证,若cookie中的token与实时生成的token一致,则将currentUser放入request中,否则不放。
6、在该action中获取currentUser的值,进行登录认证判断及相关参数的使用。
三、开发实例(核心代码)
1、login.jsp
<form id="login_form" action="../account/login" method="POST"> <ul class="form"> <li><input type="text" placeholder="账号" name="uid" value="$!uid"/></li> <li><input type="password" placeholder="密码" name="password" value="$!password"/></li> <li><a href="#" class="btn" onclick="document.getElementById(‘login_form‘).submit();return false">登录</a></li> </ul> <div class="login_failure">$!error</div></form>
2、AuthRequired(登录认证注解)
@Inherited@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface AuthRequired {}
3、AuthHandlerInterceptor(登录认证拦截器)
@Component("authHandlerInterceptor")public class AuthHandlerInterceptor implements HandlerInterceptor { @Resource private UserService userService; @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception { if (handler.getClass().isAssignableFrom(HandlerMethod.class)) { AuthRequired authRequired = ((HandlerMethod) handler).getMethodAnnotation(AuthRequired.class); if (authRequired == null) { return true; } //cookie里面获取token Cookie[] cookies = httpServletRequest.getCookies(); String token = ""; if (cookies != null) { for (Cookie cookie : cookies) { if (cookie.getName().equals("token")) { token = cookie.getValue(); } } } if (null != token && !"".equals(token) && token.contains("_")) { int id = Integer.parseInt(token.split("_")[0]); String code = ""; User user = userService.getUser(id); if (null != user && user.getLock() == 0) { code = userService.generateLoginToken(user); } if (code.equals(token)) { httpServletRequest.setAttribute("currentUser", user); } else { httpServletResponse.sendRedirect("/account/login"); return false; } } else { httpServletResponse.sendRedirect("/account/login"); return false; } } return true; } @Override public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { if (httpServletRequest.getAttribute("currentUser") != null && null != modelAndView) { modelAndView.addObject("current", httpServletRequest.getAttribute("currentUser")); } } @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { }}
4、Login action
@RequestMapping(value = "/login", method = RequestMethod.POST)public String login(String uid, String password, Model model, HttpServletResponse response) { try { if (StringUtils.isBlank(uid)) { throw new RuntimeException("账号必填"); } if (StringUtils.isBlank(password) || password.length() < 6) { throw new RuntimeException("密码必填并且至少6位"); } User userByUId = userService.getUserByUId(uid); if ( userByUId == null) { throw new RuntimeException("账号不存在"); } userByUId = null; User user = userService.login(uid, password); if (user == null) { throw new RuntimeException("账号或者密码错误"); } String token = userService.generateLoginToken(user); Cookie cookie = new Cookie("token", token); cookie.setPath("/"); response.addCookie(cookie); return "redirect:/user/main"; } catch (Exception e) { logger.error("error", e); model.addAttribute("error", e.getMessage()); model.addAttribute("uid", uid); model.addAttribute("password", password); return "/account/login"; }}
5、需要登录才能操作的action,有@AuthRequired注解说明需要登录拦截,只有成功才能进行相关操作。
@AuthRequired @RequestMapping(value = {"/main"}, method = {RequestMethod.GET}) public String goMainVm(@Value("#{request.getAttribute(‘currentUser‘)}") User currentUser,Model model, HttpServletRequest request) throws BizException { try { if (null == currentUser || null == currentUser.getPhone()) { throw new RuntimeException("请登录后操作"); } //登录后的操作**************** return "/user/main"; } catch (Exception e) { logger.error("error", e); model.addAttribute("error", e.getMessage()); return "/account/login"; } }
6、配置文件
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" /><bean class="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter" /><mvc:interceptors> <bean class="com.happywork.interceptor.AuthHandlerInterceptor"/></mvc:interceptors>
至此,登录认证完全搞定。
四、性能问题
有人会问,这么频繁的去读取数据库,是否会带来一定的性能问题,这里做个简要说明,这只是功能实现思路而已,当用户量上去后,直接从缓存中获取,无需频繁操作数据库,这样就解决了频繁的IO操作带来的瓶颈。