一、引言
传统软件,架构单一,登录认证相对简单,基本都是通过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操作带来的瓶颈。