SpringBoot小案例2-实现登录验证(会话、过滤器)

会话的跟踪技术

1.客户端会话跟踪

优点:http协议中支持的技术

缺点:

  • 移动端APP无法使用Cookie

  • Cookie存储在浏览器硬盘,用户可以禁用,不安全

  • Cookie无法跨域

流程:

  1. 用户登录后,服务器自动返回一个cookie响应给浏览器

  2. 浏览器接受到响应回来的数据之后,会自动的将cookie存储在浏览器本地

  3. 在后续的请求中,浏览器会自动的将cookie携带到服务器端

区分跨域的纬度:协议、IP/域名/端口号

实现代码

//设置cookie
@RequestMapping("/setcookie")
public Result setcookie(HttpServletResponse response){
    response.addCookie(new Cookie("username","ffy"));
    return Result.success();
}
​
//获取cookie
@RequestMapping("/getcookie")
public Result getcookie(HttpServletRequest request){
    Cookie[] cookies = request.getCookies();
    for (Cookie cookie : cookies) {
        log.info("getcookie: "+cookie.getName());
    }
    return Result.success();
}

2.服务端会话跟踪

优点:存储在服务器端,安全

缺点:

  • 在服务器集群环境下无法使用session

  • 对Cookie的依赖程度过高。Session 底层是基于Cookie实现的会话跟踪,如果Cookie不可用,则该方案,也就失效了。

流程:

  • 浏览器在第一次请求的时候,服务器会自动生成一个session ,每一个绘画对象session都有一个sessionID,服务器将sessionID响应给浏览器(以JSSESSIONID=xxxx的形式)

  • 浏览器会将SessionID存储到本地并在每一次访问时都携带

  • 当浏览器后续发出请求时,服务器会根据浏览器携带的SessionID查询其对应的会话

实现代码:

@RequestMapping("/getSession")
public Result getSession(HttpServletRequest request){
    //获取当前请求的session对象
    HttpSession session = request.getSession();
    log.info("This is getSession:"+session.hashCode());
​
    //往session中获取数据
    Object loginUser = session.getAttribute("psw");
    log.info("This is getSession:"+loginUser);
    return Result.success(loginUser);
}
​
@RequestMapping("/setSession")
public Result setSession(HttpSession session){
    log.info("从Session中读取到的数据:"+session.hashCode());
    //往session中存储数据
    session.setAttribute("psw","xiaobai");
    return Result.success();
}

3.令牌技术

优缺点

优点

  • 支持PC端、移动端

  • 解决集群环境下的认证问题

  • 减轻服务器的压力

缺点:需要手动配置

JWT

概念:

组成

  • 第一部分:header(头),用来记录令牌类型、签名算法等。

  • 第二部分:Payload(有效载荷),携带一些自定义的信息、默认信息等。

  • 第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、payload,并加入指定秘钥,通过指定签名算法计算而来。

使用场景

  • 登录认证

    • 浏览器发情请求来执行登录操作,这时会访问登录的接口,如果登录成功后,我们就会生成一个jwt令牌,将生成的jwt令牌返回给前端

    • 前端拿到jwt令牌之后,就会将jwt令牌存储起来。在后续的每一次请求中都会将jwt令牌携带到服务端

    • 服务端统一拦截请求后,先来判断一下这次请求有没有把令牌带过来,如果没有带过来,直接拒绝访问,如果带过来了,还要校验一下令牌是否有效。如果有效,就直接放行进行请求的处理

JWT的登录认证操作

  1. 生成令牌

    1. 导入Jwt依赖包

      <!-- JWT依赖-->
      <dependency>
          <groupId>io.jsonwebtoken</groupId>
          <artifactId>jjwt</artifactId>
          <version>0.9.1</version>
      </dependency>
      ​
    2. 使用工具类:Jwts生成JWT字符串

      @Test
      public void testJwt(){
      //设置有效载荷
      Map<String,Object> map = new HashMap<>();
      map.put("username","小明");
      map.put("password",123456);
      ​
      //生成jwt令牌
      String itcast = Jwts.builder().setClaims(map).
      signWith(SignatureAlgorithm.HS256, "itcast").//设置秘钥
      setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000)).//设置有效时间
      compact();
      ​
      System.out.println(itcast);
      }
      ​
  2. 校验令牌:在每一次请求当中,要接受令牌并对令牌进行校验

    1. 解析生成的令牌

      @Test
      public void parseJwt(){
      Claims claims = Jwts.parser().setSigningKey("itcast").//指定签名秘钥(要和生成米秘钥保持一致)
      parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJwYXNzd29yZCI6MTIzNDU2LCJleHAiOjE2ODU0MzIzNzgsInVzZXJuYW1lIjoi5bCP5piOIn0.2lqj3kKWGmRpSEbzijXXss7FPT49iLQ78OVUikNRNwc").getBody();
      System.out.println(claims);
      }
    2. 测试令牌过期时间

JWT集成用户登录

JwtUtils

package com.itcast.utils;
​
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
​
import java.util.Date;
import java.util.Map;
​
@Component
@Slf4j
public class JwtUtils {
    //设置密钥
    private static String signKey = "itcast";
    //设置有效时间
    private static Long expire = System.currentTimeMillis()+1000*60*30;
​
    public static String setJwt(Map<String,Object> claims){
        //生成jwt令牌
        String jwt = Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS256, signKey).setExpiration(new Date(expire)).compact();
        return jwt;
    }
​
    //校验jwt
    public static Claims parseJwt(String jwt){
        Claims claims = Jwts.parser().setSigningKey(signKey).parseClaimsJws(jwt).getBody();
        return claims;
    }
}
​

EmpController登录生成JWT

    @PostMapping("/login")
    public Result Login(@RequestBody Emp emp){
        Boolean flag = empService.login(emp);
        if(flag==null){
            return Result.error("用户名或密码有误");
        }
        Map<String,Object> claim = new HashMap<>();
        claim.put("username",emp.getUsername());
        claim.put("id",emp.getId());
        String claims = JwtUtils.setJwt(claim);
        return Result.success(claims);
    }

过滤器-Filter

概念

  • 过滤器是JavaWeb的三大组件(Servlet/Filter/Listener)之一

  • 过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能,使用了过滤器之后,想要访问web服务器上的资源,必须先经过过滤器,过滤器处理完毕之后,才能访问对应的资源

  • 过滤器一般完成一些通用的操作,比如:登录校验、统一编码、敏感字符处理等

过滤器的基本使用操作

  • 第一步,定义过滤器

    • 定义一个类,实现Filter接口,并重写其所有方法

  • 第二步,配置过滤器

    • Filter类加@WebFilter注解,配置拦截资源的路径。引导类加@ServletComponentScan开启Servlet组件支持

  • 代码实现:

    1. 定义接口实现类ABCFilter

      package com.itcast.filter;
      ​
      ​
      import org.springframework.stereotype.Component;
      ​
      import javax.servlet.*;
      import javax.servlet.annotation.WebFilter;
      import java.io.IOException;
      ​
      @WebFilter(urlPatterns = "/*")
      public class ABCFiler implements Filter {
          @Override
          public void init(FilterConfig filterConfig) throws ServletException {
              System.out.println("init()......");
          }
      ​
          @Override
          public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
              System.out.println("doFilter()......");
          }
      ​
          @Override
          public void destroy() {
              System.out.println("destroy()......");
          }
      }
      ​
    2. 配置Application

      package com.itcast;
      ​
      import org.springframework.boot.ApplicationArguments;
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      import org.springframework.boot.web.servlet.ServletComponentScan;
      ​
      @SpringBootApplication
      @ServletComponentScan
      public class Web01Application {
          public static void main(String[] args) {
              SpringApplication.run(Web01Application.class);
          }
      }
      ​

Filter拦截路径的方式

  1. 拦截具体路径

    • /login

    • 只访问/login的路径才会被拦截

  2. 目录拦截

    • /emps/*

    • 访问/emps下的所有资源都会被拦截

  3. 拦截所有

    • /*

    • 访问所有资源都会被拦截

Filter过滤器链的执行顺序及流程

顺序

  • 优先级是按照过滤器类名(字符串)的自然排序

流程

只存在一个Filter的情况

  1. 浏览器向服务器发出请求

  2. 放行前逻辑

  3. 放行

  4. 资源

  5. 放行后逻辑

  6. 服务器给浏览器返回响应

存在多个Filter的情况

  1. 浏览器向服务器发出请求

  2. Filter1放行前逻辑

  3. Filter1放行

  4. Filter2放行前逻辑

  5. Filter2放行

  6. Filter3放行前逻辑

  7. Filter3放行

  8. Web资源

  9. Filter3放行后逻辑

  10. Filter2放行后逻辑

  11. Filter1放行后逻辑

  12. 服务器给浏览器返回响应

登录校验-Filter

实现具体流程

  1. 获取请求url

  2. 判断请求url中是否包含login,如果包含,说明是登录操作,放行

  3. 获取请求头中的令牌(token)

  4. 判断令牌是否存在,如果不存在返回错误结果(未登录)

  5. 解析token,如果解析失败,返回错误结果(未登录)

  6. 放行

代码实现

1.获取请求url

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request =  (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
String url = request.getRequestURL().toString();
}

2.判断请求url中是否包含login,如果包含,说明是登录操作,放行

//判断请求路径是否是登录路径
if(url.contains("/login")){
filterChain.doFilter(request, response);
return;
}

3.获取请求头中的令牌(token)

//获取请求头中的令牌信息
String token = request.getHeader("token");
log.info("从请求头中获取的令牌:{}",token);

4.判断令牌是否存在,如果不存在返回错误结果(未登录)

//判断令牌是否存在,如果不存在则返回错误结果
if(!StringUtils.hasLength(token)){
log.info("Token不存在");
Result responseResult = Result.error("NOT_LOGIN");
//把Result对象转换为JSON格式字符串
String json = JSONObject.toJSONString(responseResult);
response.setContentType("application/json;charset=utf-8");
//将json字符串返回给页面
response.getWriter().write(json);
return;
}

5.解析token,如果解析失败,返回错误结果(未登录)

//解析token,如果解析失败,则返回错误结果
try {
JwtUtils.parseJwt(token);
}catch (Exception e){
log.info("令牌解析失败");
Result responseResult = Result.error("NOT_LOGIN");
String json = JSONObject.toJSONString(responseResult);
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(json);
return;
}

6.放行

 //放行
 filterChain.doFilter(request,response);

完整代码:

LoginCheckFilter

package com.itcast.filter;
​
​
import com.alibaba.fastjson.JSONObject;
import com.itcast.pojo.Result;
import com.itcast.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
​
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
@WebFilter(urlPatterns = "/*")
public class LoginCheckFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("init()......");
    }
​
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request =  (HttpServletRequest)servletRequest;
        HttpServletResponse response = (HttpServletResponse)servletResponse;
​
        String url = request.getRequestURL().toString();
        log.info("请求的路径"+url);
​
        //判断请求路径是否是登录路径
        if(url.contains("/login")){
            filterChain.doFilter(request, response);
            return;
        }
​
        //获取请求头中的令牌信息
        String token = request.getHeader("token");
        log.info("从请求头中获取的令牌:{}",token);
​
        //判断令牌是否存在,如果不存在则返回错误结果
        if(!StringUtils.hasLength(token)){
            log.info("Token不存在");
            Result responseResult = Result.error("NOT_LOGIN");
            //把Result对象转换为JSON格式字符串
            String json = JSONObject.toJSONString(responseResult);
            response.setContentType("application/json;charset=utf-8");
            //将json字符串返回给页面
            response.getWriter().write(json);
            return;
        }
​
        //解析token,如果解析失败,则返回错误结果
        try {
            JwtUtils.parseJwt(token);
        }catch (Exception e){
            log.info("令牌解析失败");
            Result responseResult = Result.error("NOT_LOGIN");
            String json = JSONObject.toJSONString(responseResult);
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().write(json);
            return;
        }
​
        //放行
        filterChain.doFilter(request,response);
    }
​
    @Override
    public void destroy() {
        System.out.println("destroy()......");
    }
}
​

JwtUtils

package com.itcast.utils;
​
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
​
import java.util.Date;
import java.util.Map;
​
@Component
@Slf4j
public class JwtUtils {
    //设置密钥
    private static String signKey = "itcast";
    //设置有效时间
    private static Long expire = 1000*60*30L;
​
    public static String setJwt(Map<String,Object> claims){
        //生成jwt令牌
        String jwt = Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS256, signKey).setExpiration(new Date(System.currentTimeMillis()+expire)).compact();
        return jwt;
    }
​
    //校验jwt
    public static Claims parseJwt(String jwt){
        Claims claims = Jwts.parser().setSigningKey(signKey).parseClaimsJws(jwt).getBody();
        return claims;
    }
}
​
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

简兮_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值