原理篇
前言:
HTTP是无状态的协议。浏览器先后两次发起请求,服务器不会知道你是同一个人。
但是除去静态的资源库,几乎所有的web应用都需要记录身份。
Cookie:
cookie是存在于浏览器中的一种<K,V>数据结构。cookie有两个关键参数,Age/Path(Domain+Path)。这两个参数决定了存活时间以及访问什么URL会携带cookie
cookie实现验证的过程如下:
- 初次登录,request携带username/password
- 服务器验证成功,response.addCookie(new Cookie(key,value));
- 后续访问,拦截器request.getCookies()。获取健为key的value值~~后续操作
第二步中,如果说验证了用户名密码之后,直接new Cookie("user",username)。拦截器直接获取key="user"的cookie,这其实已经做到了登录验证,不过这个风险太大了。基本没有用
如果保险一点,比如我生成一个UUID,new Cookie("uuid",uuid)。同时将这个UUID存入数据库,table(user,uuid),在拦截器中可以通过select user from talbe where uuid=#{uuid}的方式
Session:
cookie中说的保险一点的方式,其实就是Session的思想。
session实现验证的过程如下:
- 初次登录,request携带username/password
- 服务器验证成功,request.getSession() session.addAttribute("user",name)
- 后续访问,拦截器request.getSession() session.getAttribute("user")~~~后续操作
这里没有出现cookie,不过实际仍然用到了cookie。cookie中存放了会话相关的sessionId,下次请求时request.getSession()直接会根据cookie中的sessionId获取
但这个和上面Cookie方式中存入数据库也有区别,这个session信息是存放在内存中的(其实,cookie也可以设计成这种方式),而不是数据库。
Token:
为什么要采用session? 简言之,为了证明你是你。
session的做法是,你初次访问的时候给你取个名字,你自己记住这个名字,服务器也会记住你的名字。后续访问的时候只要服务器中有你的名字就行。
token的做法是,你初次访问的时候根据你的指纹给你取个复杂的名字,并把这个名字给你,但是服务器不会记住你的名字。后续访问的时候再次根据你的指纹获取你的复杂的名字。如果你带来的名字和新生成的名字一样,那验证通过。——即加密
token实现验证的过程如下:
- 初次登录,request携带username/password
- 服务器验证成功,token=tokenMethod(username) response.addCookie("token",token).addCookie("user",username)
- 后续访问,拦截器获取cookies realToken=tokenMethod(username) 对比token和realToken~~后续操作
Demo篇
Cookie
采用cookie+数据库实现登录认证,每一个登录的用户都会将登录信息存入数据库
验证用户/密码等略
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(new HandlerInterceptor(){
@Override
public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
//根据request获取cookie与当前会话中的ticket比对
String t= CookieUtils.getCookie("t",request);
if(t!=null){
Ticket ticket=ticketService.getTicket(t); //数据库中有则代表已经登录
if(ticket!=null){
if(ticket.getExpiredAt().compareTo(new Date())>0){ //登录过期判断
User u=userService.getUser(ticket.getUserId());
ConcurrentUtils.setHost(u);
}
}
}
return true;
}
}).addPathPatterns("/**");
Session
session有一个问题。如果说我们的应用做了多实例+负载均衡,用户登录访问了A实例,那在A实例中确实有用户的session,可B实例是没有的。下次用户访问到了B实例,携带了sessionId可是B实例并不认识这个Id
@RestController
public class LoginController {
Map<String,String> userlist=new HashMap<>();
{
for(int i=0;i<5;i++)
userlist.put("user"+i,"user"+i);
};
@RequestMapping("login")
public String login(@RequestParam("name")String name, @RequestParam("pwd")String pwd, HttpServletRequest request){
if(userlist.containsKey(name))
if(userlist.get(name).equals(pwd)){
HttpSession session=request.getSession();
session.setAttribute("whoLogin",name);
return "Welcome You: "+name;
}
else return "Wrong Pwd";
return "Invalid account";
}
@RequestMapping("check")
public String check(){
return "If you can see me, you already logged in.";
}
}
//拦截器设置
registry.addInterceptor(new HandlerInterceptor(){
@Override
public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
//根据request获取cookie与当前会话中的ticket比对
HttpSession session=request.getSession();
String who=(String)session.getAttribute("whoLogin");
if(who==null||who.equals(""))
response.sendError(6666);
return true;
}
}).addPathPatterns("/login/check");
Token
由于使用token验证的服务器端并没有保存sessionid等信息,所以即便是在多实例中也不会出现这边登录了那边‘未登录’的情况。不过这样,每次的请求都会做一个加密,会消耗服务器资源
@RestController
@RequestMapping("/token/")
public class TokenController {
Map<String,String> userList=new HashMap<>();
{
for(int i=0;i<5;i++)
userList.put("user"+i,"user"+i);
}
@RequestMapping("login")
public String login(HttpServletRequest request, HttpServletResponse response) throws IOException {
String name=request.getParameter("name");
String pwd=request.getParameter("pwd");
if(name==null||pwd==null)
response.sendError(6666,"Please enter username OR password.");
if(userList.containsKey(name)){
if(userList.get(name).equals(pwd)){
String token= DigestUtils.md5DigestAsHex(name.getBytes());
Cookie cookie=new Cookie("token",token);
response.addCookie(cookie);
cookie=new Cookie("username",name);
response.addCookie(cookie);
return "Welcome You, "+name;
}else
return "Wrong Password";
}
return "Invalid Account";
}
@RequestMapping("check")
public String check(){
return "If you can see me, you already logged in.";
}
}
//拦截器配置
registry.addInterceptor(new HandlerInterceptor(){
@Override
public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
String name=null,token=null;
for(Cookie cookie : request.getCookies()){
if(cookie.getName().equals("username"))
name=cookie.getValue();
if(cookie.getName().equals("token"))
token=cookie.getValue();
}
if(name==null||token==null){
response.sendError(6666,"Please login first");
return false;
}
String realToken= DigestUtils.md5DigestAsHex(name.getBytes());
if(!token.equals(realToken)){
response.sendError(6666,"Invalid Token");
return false;
}
return true;
}
}).addPathPatterns("/token/check");
参考:
牛客网项目:https://git.nowcoder.com/11000160/BookManager 用的是cookie+数据库验证方式
原理分析:https://www.cnblogs.com/moyand/p/9047978.html session/token Demo是根据这个文章的思想写出的
Demo没啥技术含量,如果有新手记不住API可以参考