目录:
1.实现概述
2.注册功能实现
3.登录功能实现
4.状态判断
1.实现概述:
总体思想:注册、登陆之后,服务器会生成一个短期有效的ticket,并将这个ticket存入浏览器的cookie中,之后再使用Interception根据cookie中的ticket渲染网页。
configuration.ToutiaoWebConfiguration:用于将interceptor(拦截器)与网页进行关联。
controller.LoginController:用于注册、登录操作的前端交互。
model.LoginTicket和dao.LoginTicketDAO:用于服务器给登录用户下发的ticket,即用户已登录的凭证。
model.HostHolder:所有属性定义成静态ThreadLocal的,可以实现多线程(多个用户同时访问网站)。
interception.PassportInterception:interception拦截器,为一种面向切面的思想,本类用于加载网页前判断是否已登录。
interception.LoginRequiredInterception:设置权限网页,如果权限不够,自动跳回首页。
Service.UserService:用于实现注册、登录的逻辑判断。
2.注册功能实现:
在UserService实现逻辑判断:
public Map<String ,Object> Register(String username, String password){
Map<String ,Object> map=new HashMap<>();
if(StringUtils.isBlank(username)){
map.put("msgname","用户名不能为空");
return map;
}
if(StringUtils.isBlank(username)){
map.put("msgpassword","密码不能为空");
return map;
}
User user=userDAO.selectByName(username);
if(user!=null){
map.put("msgname","用户名已经被注册");
return map;
}
user=new User();
user.setName(username);
user.setSalt(UUID.randomUUID().toString().substring(0,5));//为了进一步加密密码而生成的随机数
user.setHeadUrl(String.format("http://images.nowcoder.com/head/%dt.png", new Random().nextInt(1000)));
user.setPassword(ToutiaoUtil.MD5(password+user.getSalt()));//使用MD5加密password+salt更加安全
userDAO.addUser(user);
//注册结束直接登录
String ticket=addLoginTicket(user.getId());
map.put("ticket",ticket);
return map;
}
private String addLoginTicket(int userId){
LoginTicket ticket=new LoginTicket();
ticket.setUserId(userId);
Date date=new Date();
date.setTime(date.getTime()+1000*3600*24);
ticket.setExpired(date);//设置ticket的过期时间
ticket.setStatus(0);//用于标记当前ticket是否有效,0为有效,1为失效
ticket.setTicket(UUID.randomUUID().toString().replaceAll("-",""));
loginTicketDAO.addTicket(ticket);//存储它的ticket以便于之后一段时间的自动登录的验证
return ticket.getTicket();
}
3.登录功能实现:
基本与注册功能相似。
public Map<String ,Object> login(String username, String password){
Map<String ,Object> map=new HashMap<>();
if(StringUtils.isBlank(username)){
map.put("msgname","用户名不能为空");
return map;
}
if(StringUtils.isBlank(username)){
map.put("msgpassword","密码不能为空");
return map;
}
User user=userDAO.selectByName(username);
if(user==null){
map.put("msgname","用户名不存在");
return map;
}
if(!ToutiaoUtil.MD5(password+user.getSalt()).equals(user.getPassword())){//验证密码
map.put("msgpassword","密码错误");
return map;
}
//登录验证完,自动登录,即下发ticket
String ticket=addLoginTicket(user.getId());
map.put("ticket",ticket);
return map;
}
4.状态判断:
注册登录的逻辑判断很好实现,但是我们如何在打开或者刷新一个页面的时候,让浏览器记住我们是否登录,或者我们是谁?这些问题呢。因此我们需要两部分来解决这些问题:①HostHandler类来记录当前登陆的用户是谁。②Interception(拦截器),在每个网页加载之前,读取cookie,来判断我们是谁。③LoginController中将ticket写入cookie。
①HostHandle实现类很简单,需要注意的是需要把它的成员变量user(即“我是谁”)设置成ThreadLocal的来应对多线程问题;把其方法设置成static的,方便调用。
package com.nowcoder.model;
import org.springframework.stereotype.Component;
@Component
public class HostHolder {
private static ThreadLocal<User> users=new ThreadLocal<>();//解决“我是谁”的问题,ThreadLocal解决多线程问题
public static User getUser(){//定义成static方便调用
return users.get();
}
public static void setUser(User user){
users.set(user);
}
public static void clear(){
users.remove();
}
}
②Interception(拦截器)
PassportInterception可以实现在网页加载前后进行一系列的判断和操作。需要实现HandlerInterception接口,这个接口有三个方法。
//HandlerInterceptor.java
public interface HandlerInterceptor {
boolean preHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;
//该方法将在请求处理之前进行调用,只有该方法返回true,才会继续执行后续的Interceptor和Controller,当返回值为true 时就会继续调用下一个Interceptor的preHandle 方法,如果已经是最后一个Interceptor的时候就会是调用当前请求的Controller方法;
void postHandle(HttpServletRequest var1, HttpServletResponse var2, Object var3, ModelAndView var4) throws Exception;
//该方法将在请求处理之后,DispatcherServlet进行视图返回渲染之前进行调用,可以在这个方法中对Controller 处理之后的ModelAndView 对象进行操作。
void afterCompletion(HttpServletRequest var1, HttpServletResponse var2, Object var3, Exception var4) throws Exception;
//该方法也是需要当前对应的Interceptor的preHandle方法的返回值为true时才会执行,该方法将在整个请求结束之后,也就是在DispatcherServlet 渲染了对应的视图之后执行。用于进行资源清理。
}
package com.nowcoder.interceptor;
//PassportInterceptor.java
import com.nowcoder.dao.LoginTicketDAO;
import com.nowcoder.dao.UserDAO;
import com.nowcoder.model.HostHolder;
import com.nowcoder.model.LoginTicket;
import com.nowcoder.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
@Component
public class PassportInterceptor implements HandlerInterceptor {
@Autowired
private LoginTicketDAO loginTicketDAO;
@Autowired
private UserDAO userDAO;
@Autowired
private HostHolder hostHolder;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
String ticket=null;
if(httpServletRequest.getCookies()!=null){//读取httpServletRequest的cookie
for(Cookie cookie: httpServletRequest.getCookies()){//遍历所有cookie
if( cookie.getName().equals("ticket")){//如果有cookie等于ticket
ticket=cookie.getValue();//取该cookie的value
break;
}
}
}
if(ticket!=null){
LoginTicket loginTicket=loginTicketDAO.selectByTicket(ticket);//在数据库中查找该ticket
if(loginTicket==null||loginTicket.getExpired().before(new Date())||loginTicket.getStatus()!=0){//判断loginTicket是否为空,是否过期,是否失效
return true;
//直接进入posthandle,继续判断是否有hosthandle
}
User user=userDAO.selectById(loginTicket.getUserId());//根据Loginticket中的userID属性查找对应的user
hostHolder.setUser(user);//将该user存入hostHolder中
}
return true;
//直接进入posthandle,继续判断是否有hosthandle
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
if(modelAndView!=null&&hostHolder.getUser()!=null){//判断hostHolder是否为空,即判断是否有当前登录用户
//modelAndView可以实现后端与前端的交互,addObject的元素可以直接被前端模板调用
//html文件中可以直接使用这个$user
modelAndView.addObject("user",hostHolder.getUser());
}
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
hostHolder.clear();//网页关闭,清理hostHolder占用的内存
}
}
interception写到这里,它的功能已经实现但是还需要把它和本网站关联,使用configuration.ToutiaoWebConfiguration实现
package com.nowcoder.configuration;
import com.nowcoder.interceptor.LoginRequiredInterceptor;
import com.nowcoder.interceptor.PassportInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Component//用@Component对那些比较中立的类进行凝视,让spring可以自动装载这个类
public class ToutiaoWebConfiguration extends WebMvcConfigurerAdapter {
@Autowired
PassportInterceptor passportInterceptor;
@Autowired
LoginRequiredInterceptor loginRequiredInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(passportInterceptor);//将passportInterceptor注册,才能回调
registry.addInterceptor(loginRequiredInterceptor).addPathPatterns("/setting/");//不是全局的,只是setting页面
super.addInterceptors(registry);
}
}
③LoginController
将ticket写入cookie,以注册为例,登录同理。
@RequestMapping(path = {"/reg/"}, method = {RequestMethod.GET, RequestMethod.POST})
//设置网页路径为/reg/
@ResponseBody
//@responseBody注解的作用是将controller的方法返回的对象通过适当的转换器转换为指定的格式之后,
//写入到response对象的body区,通常用来返回JSON数据或者是XML
public String reg(Model model , @RequestParam("username") String username,
@RequestParam("password") String password,
@RequestParam(value = "rember",defaultValue = "0") int remeberme,
HttpServletResponse response) {
//此处没有写前端,所以用户名,密码需要在url中填写
try {
Map<String,Object> map=userService.Register(username,password);
//调用注册功能,得到注册返回的map
if (map.containsKey("ticket")){
//将ticket写入cookie
Cookie cookie=new Cookie("ticket",map.get("ticket").toString());
cookie.setPath("/");//设置cookie为全站有效的
//设置remember me,即延长cookie的保存时间,,否则默认浏览器关闭删除cookie
if(remeberme>0){
cookie.setMaxAge(3600*24*5);
}
response.addCookie(cookie);//注意设置完cookie的所有属性才能add到response,否则不会提交
return ToutiaoUtil.getJSONString(0,"注册成功");//返回json格式的信息字符串
}
else{
return ToutiaoUtil.getJSONString(1,map);//返回json格式的信息字符串
}
}catch (Exception e){
logger.error("注册异常"+e.getMessage());
return ToutiaoUtil.getJSONString(1,"注册异常");//返回json格式的信息字符串
}
}