首页 > 编程语言 >JavaWeb学习笔记——第十二天

JavaWeb学习笔记——第十二天

时间:2024-04-02 12:55:21浏览次数:29  
标签:令牌 第十二天 JavaWeb JWT 笔记 Filter Result import 拦截

SpringBootWeb案例(三)

登录功能

  • LoginController:
import com.zgg1h.pojo.Emp;
import com.zgg1h.pojo.Result;
import com.zgg1h.service.EmpService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@Slf4j
@RestController
@RequestMapping("/login")
public class LoginController {
    @Autowired
    private EmpService empService;

    @PostMapping
    public Result login(@RequestBody Emp emp) {
        log.info("员工登录:{}", emp);
        Emp e = empService.login(emp);
        return e == null ? Result.error("用户名或密码错误") : Result.success();
    }
}
  • EmpService:
Emp login(Emp emp);
  • EmpServiceImpl:
@Override
public Emp login(Emp emp) {
    return empMapper.getByUsernameAndPassword(emp);
}
  • EmpMapper:
@Select("select  * from emp where username = #{username} and password = #{password}")
Emp getByUsernameAndPassword(Emp emp);

登录校验

在之前的程序中,用户不管有没有登陆,都可以直接进行业务操作,可以用以下技术来进行登录校验。

  • 登陆标记(会话技术,JWT令牌):当用户登陆成功后,给用户打上一个标记,之后的每一次请求中,都可以获取到该标记。
  • 统一拦截(过滤器Filter,拦截器Interceptor):在用户要进行业务操作时,先校验登陆标记,校验成功则进入正常业务执行,校验失败则给前端相应一个错误信息,前端页面再跳转到登录页面。

会话技术

  • 会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应。
  • 会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据。
  • 会话跟踪方案:
    • 客户端会话跟踪技术:Cookie
    • 服务端会话跟踪技术:Session
    • 令牌技术

会话跟踪方案对比

执行流程
  1. 浏览器第一次向服务器发送请求后,服务器会在返回响应时自动加上Cookie。
  2. 浏览器接收到这次响应后,会将Cookie自动保存在本地。
  3. 之后,浏览器的每一次请求都会自动带上Cookie,这就实现了会话跟踪。
优点
  • Cookie是HTTP协议中支持的技术。
缺点
  • 移动端APP无法使用Cookie。
  • 不安全,因为Cookie数据会保存在浏览器本地;且用户可以自己禁用Cookie。
  • Cookie不能跨域。(跨域区分三个维度:协议、IP/域名、端口,三者之一有不同即为跨域)
Session
执行流程
  1. 浏览器第一次向服务器发送请求后,服务器会在本地创建一个会话对象Session,每一个Session对象都有一个id。服务器在返回响应时会将Session id通过Cookie相应给浏览器。
  2. 之后的流程与Cookie一致。
优点
  • 存储在服务端,安全。
缺点
  • 服务器集群环境下无法直接使用Session。
  • Cookie的其他缺点依然存在。
令牌技术
执行流程
  1. 浏览器第一次向服务器发送请求后,服务器会创建一个令牌并下发给浏览器。
  2. 之后,浏览器的每一次请求都会带上令牌,服务器收到令牌后,会统一拦截校验并校验令牌的有效性,如果有效,则放行,反之则拦截。
优点
  • 支持PC端、移动端。
  • 解决集群环境下的认证问题。
  • 减轻服务器端存储压力。
缺点
  • 需要自己实现。

JWT令牌

  • JWT全称:JSON Web Token。
  • JWT定义了一种简洁的、自包含的格式,用于在通信双方以json数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的。
  • JWT令牌就是一段字符串,由标头Header、有效载荷(Payload)和签名(Signature)这三部分组成,用 . 拼接。在传输的时候,会将JWT的前两个部分分别进行Base64编码后用 . 进行连接,形成最终传输的字符串。
  • 组成:
    • 第一部分:Header(标头), 记录令牌类型、签名算法等。
    • 第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。
    • 第三部分:Signature(签名),防止Token被篡改、确保安全性。融入header、payload,并加入指定秘钥,通过指定签名算法计算而来。
  • Base64:是一种基于64个可打印字符(A-Z a-z 0-9 + /)来表示二进制数据的编码方式。(还有一个占位符=)

执行流程

  1. 登录成功后,生成令牌。
  2. 后续每个请求,都要携带JWT令牌,系统在每次请求处理之前,先校验令牌,通过后,再处理请求,否则拒绝处理请求。

JWT令牌的使用

引入依赖
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
生成JWT
String jwt = Jwts.builder()
    .setClaims(Map对象) //自定义内容(载荷)
    .signWith(签名算法名, 密钥字符串) //签名算法
    .setExpiration(Date对象) //有效期
    .compact(); //压缩生成JWT
校验JWT
Claims claims = Jwts.parser()
    .setSigningKey(密钥字符串) //指定签名秘钥
    .parseClaimsJws(JWT令牌) //解析令牌
    .getBody() //获取Map对象封装的令牌内容

注意事项

  • JWT校验时使用的签名秘钥,必须和生成JWT令牌时使用的秘钥是配套的。
  • 如果JWT令牌解析校验时报错,则说明 JWT令牌被篡改或失效了,令牌非法。

登录功能-生成令牌

  • LoginController:
@PostMapping
public Result login(@RequestBody Emp emp) {
    log.info("员工登录:{}", emp);
    Emp e = empService.login(emp);
    if (e != null) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("id", e.getId());
        claims.put("username", e.getUsername());
        claims.put("name", e.getName());

        String jwt = JwtUtils.generateJwt(claims);
        return Result.success(jwt);
    }
    return Result.error("用户名或密码错误");
}
  • JwtUtils:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.Map;

public class JwtUtils {

    private static String signKey = "zgg1h"; //密钥
    private static Long expire = 43200000L; //有效期为十二小时

    /**
     * 生成JWT令牌
     * @param claims JWT第二部分负载 payload 中存储的内容
     * @return
     */
    public static String generateJwt(Map<String, Object> claims){
        String jwt = Jwts.builder()
                .addClaims(claims)
                .signWith(SignatureAlgorithm.HS256, signKey)
                .setExpiration(new Date(System.currentTimeMillis() + expire))
                .compact();
        return jwt;
    }

    /**
     * 解析JWT令牌
     * @param jwt JWT令牌
     * @return JWT第二部分负载 payload 中存储的内容
     */
    public static Claims parseJWT(String jwt){
        Claims claims = Jwts.parser()
                .setSigningKey(signKey)
                .parseClaimsJws(jwt)
                .getBody();
        return claims;
    }
}

过滤器Filter

  • Filter过滤器是 JavaWeb 三大组件(Servlet、Filter、Listener)之一。
  • 过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能。
  • 过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等。

使用步骤

  1. 定义Filter:定义一个类,实现 Filter 接口,并重写其所有方法。(init,doFilter,destroy,主要是doFilter)
  2. 配置Filter:Filter类上加 @WebFilter 注解,配置拦截资源的路径。由于Filter不是springboot的组件,所以还要在引导类上加上@ServletComponentScan以开启Servlet组件支持。
几点注意
  • Filter的三大方法:
方法 说明
init 初始化方法,Web服务器启动,创建Filter时调用,只调用一次
doFilter 拦截到请求时,调用该方法,可调用多次
destroy 销毁方法,服务器关闭时调用,只调用一次
  • Filter用到的注解:
注解 说明
@WebFilter(urlPatterns="拦截路径") 加在Filter类上,用于配置拦截资源的路径
@ServletComponentScan 加在springboot引导类上,用于开启Servlet组件支持

执行流程

  1. 拦截请求。
  2. 执行放行前逻辑。
  3. 执行放行操作。
  4. 访问对应的web资源。
  5. 执行放行后逻辑。

拦截路径

  • Filter 可以根据需求,配置不同的拦截资源路径:
拦截路径 urlPatterns值 含义
拦截具体路径 /login 只有访问/login路径时,才会被拦截
目录拦截 /emps/* 访问/emps下的所有资源,都会被拦截
拦截所有 /* 访问所有资源,都会被拦截

过滤器链

  • 介绍:一个web应用中,可以配置多个过滤器,这多个过滤器就形成了一个过滤器链,放行后操作执行顺序与放行前操作相反。
  • 顺序:注解配置的Filter,优先级是按照过滤器类名(字符串)的自然排序。

登录校验-Filter

  • 流程:
登录校验流程
  • LoginCheckFilter:
import com.alibaba.fastjson.JSONObject;
import com.zgg1h.pojo.Result;
import com.zgg1h.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 doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        //1.获取请求url。
        String url = req.getRequestURL().toString();
        log.info("请求的url: {}",url);

        //2.判断请求url中是否包含login,如果包含,说明是登录操作,放行。
        if(url.contains("login")){
            log.info("登录操作, 放行...");
            chain.doFilter(request,response);
            return;
        }

        //3.获取请求头中的令牌(token)。
        String jwt = req.getHeader("token");

        //4.判断令牌是否存在,如果不存在,返回错误结果(未登录)。
        if(!StringUtils.hasLength(jwt)){
            log.info("请求头token为空,返回未登录的信息");
            Result error = Result.error("NOT_LOGIN");
            //手动转换 对象--json --------> 阿里巴巴fastJSON
            String notLogin = JSONObject.toJSONString(error);
            resp.getWriter().write(notLogin);
            return;
        }

        //5.解析token,如果解析失败,返回错误结果(未登录)。
        try {
            JwtUtils.parseJWT(jwt);
        } catch (Exception e) {//jwt解析失败
            e.printStackTrace();
            log.info("解析令牌失败, 返回未登录错误信息");
            Result error = Result.error("NOT_LOGIN");
            //手动转换 对象--json --------> 阿里巴巴fastJSON
            String notLogin = JSONObject.toJSONString(error);
            resp.getWriter().write(notLogin);
            return;
        }

        //6.放行。
        log.info("令牌合法, 放行");
        chain.doFilter(request, response);

    }
}
  • SpringbootExampleApplication:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@ServletComponentScan
@SpringBootApplication
public class SpringbootExampleApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootExampleApplication.class, args);
    }

}

拦截器Interceptor

  • 拦截器Interceptor是一种Spring框架中提供的动态拦截方法调用的机制,类似于过滤器,用来动态拦截控制器方法的执行。
  • 作用:拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码。

使用步骤

  1. 定义拦截器,实现HandlerInterceptor接口,加上@Component注解,并按照需求重写其方法。
  2. 注册拦截器:定义一个配置类实现WebMvcConfigurer接口,加上@Configuration注解,并重写addInterceptors方法。
注意事项
  • HandlerInterceptor的三大方法:
方法 说明
preHandle 目标资源方法执行前执行,返回true代表放行,返回false代表不放行
postHandle 目标资源方法执行后执行
afterCompletion 视图渲染完毕后执行,最后执行

拦截路径

  • 拦截器可以选择拦截或不拦截某些路径:
InterceptorRegistry类的方法 说明
addPathPatterns("拦截路径") 需要拦截哪些资源
excludePathPatterns("不拦截路径") 不需要拦截哪些资源
  • 拦截器可以根据需求,配置不同的拦截路径:
拦截路径 含义 举例
/* 一级路径 能匹配/depts,/emps,/login,不能匹配 /depts/1
/** 任意级路径 能匹配/depts,/depts/1,/depts/1/2
/depts/* /depts下的一级路径 能匹配/depts/1,不能匹配/depts/1/2,/depts
/depts/** /depts下的任意级路径 能匹配/depts,/depts/1,/depts/1/2,不能匹配/emps/1

执行流程

  1. 由于Interceptor是spring框架提供的,Tomcat无法识别,所以外部请求先进入DispatcherServlet,再由DispatcherServlet转给Interceptor。
  2. 请求进入Interceptor后,先执行preHandle逻辑,并根据执行结果决定是否放行。
  3. 如果放行,则访问对应的web资源。
  4. 然后执行postHandle逻辑。
  5. 最后执行afterCompletion逻辑。
  6. 如果同时使用Filter和Interceptor,则外部请求在进入DispatcherServlet前先被Filter拦截,并执行放行前逻辑,若放行,则外部请求进入DispatcherServlet,执行第1步,在第5步执行完后又回到Filter执行放行后逻辑。

Filter和Interceptor的区别

区别 说明
接口规范不同 过滤器需要实现Filter接口,而拦截器需要实现HandlerInterceptor接口
拦截范围不同 过滤器Filter会拦截所有的资源,而Interceptor只会拦截Spring环境中的资源

登录校验- Interceptor

  • 流程:
登录校验流程
  • LoginCheckInterceptor:
import com.alibaba.fastjson.JSONObject;
import com.zgg1h.pojo.Result;
import com.zgg1h.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //1.获取请求url。
        String url = request.getRequestURL().toString();
        log.info("请求的url: {}",url);

        //2.判断请求url中是否包含login,如果包含,说明是登录操作,放行。
        if(url.contains("login")){
            log.info("登录操作, 放行...");
            return true;
        }

        //3.获取请求头中的令牌(token)。
        String jwt = request.getHeader("token");

        //4.判断令牌是否存在,如果不存在,返回错误结果(未登录)。
        if(!StringUtils.hasLength(jwt)){
            log.info("请求头token为空,返回未登录的信息");
            Result error = Result.error("NOT_LOGIN");
            //手动转换 对象--json --------> 阿里巴巴fastJSON
            String notLogin = JSONObject.toJSONString(error);
            response.getWriter().write(notLogin);
            return false;
        }

        //5.解析token,如果解析失败,返回错误结果(未登录)。
        try {
            JwtUtils.parseJWT(jwt);
        } catch (Exception e) {//jwt解析失败
            e.printStackTrace();
            log.info("解析令牌失败, 返回未登录错误信息");
            Result error = Result.error("NOT_LOGIN");
            //手动转换 对象--json --------> 阿里巴巴fastJSON
            String notLogin = JSONObject.toJSONString(error);
            response.getWriter().write(notLogin);
            return false;
        }

        //6.放行。
        log.info("令牌合法, 放行");
        return true;

    }
}
  • WebConfig:
import com.zgg1h.interceptor.LoginCheckInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private LoginCheckInterceptor loginCheckInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**").excludePathPatterns("/login");
    }
}

异常处理

当出现异常时,默认返回的结果不符合规范,前端无法解析。

解决方案

方案 说明 评价
方案一 在Controller的方法中进行try…catch处理 代码臃肿,不推荐
方案二 使用全局异常处理器 简单、优雅,推荐

全局异常处理器

定义一个异常处理类,加上@RestControllerAdvice注解。在内部定义处理异常的方法,并加上@ExceptionHandler(异常类型)注解。

import com.zgg1h.pojo.Result;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public Result ex(Exception ex){
        ex.printStackTrace();
        return Result.error(" 对不起,操作失败,请联系管理员 ");
    }
}
  • @RestControllerAdvice = @ControllerAdvice + @ResponseBody,所以方法ex可以把对象封装成json格式返回。

标签:令牌,第十二天,JavaWeb,JWT,笔记,Filter,Result,import,拦截
From: https://www.cnblogs.com/zgg1h/p/18110342

相关文章

  • 计算机笔记(2)续20个
    21. 按位相或和相与,没有进位。比如11或10,结果就是11(运算过程是,个位0或1,结果是1;十位1或1,结果是1)01与10,结果是00(运算过程是,个位1与0,结果是0;十位0与1,结果是0)简单来说:与:0&0=0            0&1=0                    1&0=01&1=122. RAM......
  • 【文化课学习笔记】【数学】复数
    【数学】复数定义规定\(i^2=-1\),并称\(i\)为虚数单位。则\(i^3=-i,i^4=(i^2)^2=1,i^5=i^4\cdoti=i\),所以\(i^k\)具有周期性,周期为\(4\)。复数:\[z=a+bi(a,b\in\mathrmR)\]其中\(a\)为实部,\(b\)为虚部。注意:\(a\)和\(b\)都是实数。所有复数......
  • Vue学习笔记70--全局前置-路由守卫 + 后置路由守卫 + 独享守卫 + 组件内守卫
    路由守卫简介作用:用于对路由进行权限控制分类:全局守卫(前置路由守卫+后置路由守卫)、独享守卫、组件内守卫全局--前置路由守卫+ 后置守卫 示例1importVuefrom'vue'2importVueRouterfrom'vue-router'3importHomefrom'../views/Home.vue'4imp......
  • ARM架构银河麒麟使用笔记-下载docker软件包及所有依赖包并在离线环境下安装
    ARM架构银河麒麟使用笔记-下载docker软件包及所有依赖包并在离线环境下安装arm银河麒麟aptdocker目的是在arm架构的银河麒麟操作系统V10中安装docker。一、给虚拟机创建快照1.创建qemu-imgsnapshot-cEmptyKylinrootfs.qcow22.查看qemu-imgsnapshot-lrootfs......
  • 基于springboot实现学生读书笔记共享平台系统项目【项目源码+论文说明】
    基于springboot实现学生读书笔记共享平台系统演示摘要本论文主要论述了如何使用JAVA语言开发一个读书笔记共享平台,本系统将严格按照软件开发流程进行各个阶段的工作,采用B/S架构,面向对象编程思想进行项目开发。在引言中,作者将论述读书笔记共享平台的当前背景以及系统开发......
  • mysql -约束合集笔记
    SQL创建数据库createdatabaseschoolUSEschool#(数据库名)创建数据库表:createtablestudents(useridINTNOTNULLPRIMARYkey,lastnamevarchar(255),firstnamevarchar(255))#创建student数据库表且设置userid为主键)SQL约束:查看某个表已有的约束:#inform......
  • 【InternLM实战营---第二节课笔记】
    一、本期课程内容概述本节课的主讲老师是角色扮演SIG小组长任宇鹏。教学内容主要包括以下四个部分:部署InternLM2-Chat-1.8B模型进行智能对话部署实战营优秀作品八戒-Chat-1.8B模型通过InternLM2-Chat-7B运行Lagent智能体Demo实践部署浦语·灵笔2模型二、学习......
  • MySQL基础笔记-第九课
    DDL语句操作笔记在数据库管理中,DDL(DataDefinitionLanguage,数据定义语言)是用于定义和修改数据库结构的语言。本节课我们将深入了解DDL语句中关于表结构修改的几种常见操作,包括:添加字段、修改字段、删除字段和修改表名。添加字段向表中添加字段的操作使用ALTERTABLE语句......
  • Markdown学习笔记
    一.标题语法:#(一级标题)##(二级标题)###(三级标题)......(共6级标题)代码:#一级标题##二级标题tips:#与文本之间存在一个空格,无空格则为普通文本(eg:#一级标题)快捷键:Ctrl+数字1~6可以快速将选中的文本调成对应级别的标题Ctrl+0可以快速将选中的文本调成普通文......
  • MySQL提升笔记(4)InnoDB存储结构(1)
    innoDB存储引擎中,常见的页类型有:✅数据页(B-treeNode)✅undo页(undoLogPage)✅系统页(SystemPage)✅事务数据页(TransactionSystemPage)✅插入缓冲位图页(InsertBufferBitmap)✅插入缓冲空闲列表页(InsertBufferFreeList)✅未压缩的二进制大对象页(Uncompres......