SpringBootWeb案例_03

news2025/4/17 16:32:56

Web后端开发_06

SpringBootWeb案例_03

登录认证

智能学习辅助系统登录时需要身份验证

1.登录功能

先实现简单的登录功能,在进一步优化。

1.1需求

若账户或密码不存在/密码不正确,则登录失败。

账户密码正确,则登录成功

image-20231130160508878

1.2接口文档

1.2.1登录-基本信息

请求路径:/login

请求方式:POST

接口描述:该接口用于员工登录Tlias智能学习辅助系统,登录完毕后,系统下发JWT令牌。

1.2.2请求参数

参数格式:application/json

参数说明:

名称类型是否必须备注
usernamestring必须用户名
passwordstring必须密码

请求数据样例:

{
	"username": "jinyong",
    "password": "123456"
}
1.2.3响应数据

参数格式:application/json

参数说明:

名称类型是否必须默认值备注其他信息
codenumber必须响应码, 1 成功 ; 0 失败
msgstring非必须提示信息
datastring必须返回的数据 , jwt令牌

响应数据样例:

{
  "code": 1,
  "msg": "success",
  "data": "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoi6YeR5bq4IiwiaWQiOjEsInVzZXJuYW1lIjoiamlueW9uZyIsImV4cCI6MTY2MjIwNzA0OH0.KkUc_CXJZJ8Dd063eImx4H9Ojfrr6XMJ-yVzaWCVZCo"
}
1.2.4备注说明

用户登录成功后,系统会自动下发JWT令牌,然后在后续的每次请求中,都需要在请求头header中携带到服务端,请求头的名称为 token ,值为 登录时下发的JWT令牌。

如果检测到用户未登录,则会返回如下固定错误信息:

{
	"code": 0,
	"msg": "NOT_LOGIN",
	"data": null
}

1.3思路

image-20231130161143128

1.4功能实现

LoginController.java登录的控制层

@Slf4j
@RestController
public class LoginController {
    @Autowired
    private EmpService empService;

    @PostMapping("/login")
    public Result login(@RequestBody Emp emp) {
        log.info("员工登录:{}", emp);
        Emp e = empService.login(emp);
        return e != null ? Result.success() : Result.error("用户名或密码错误");
    }
}

EmpService.java员工管理的service的接口

public interface EmpService {
	Emp login(Emp emp);
}

EmpServiceImpl.java员工 接口的实现类

@Service
public class EmpServiceImpl implements EmpService {
    @Autowired
    private EmpMapper empMapper;
	@Override
    public Emp login(Emp emp) {
        return empMapper.getByUsernameAndPassword(emp);
    }
}

EmpMapper.java员工的Mapper层

@Mapper
public interface EmpMapper {
	/**
     * 根据用户名和密码查询员工
     *
     * @param emp
     * @return
     */
    @Select("select * from emp where username = #{username}  and password = #{password}")
    Emp getByUsernameAndPassword(Emp emp);
}

1.5API测试

登录失败:

image-20231130163729544

登录成功:

image-20231130163852941

1.6前端联调

登录失败示例:

image-20231130170425098

后端返回数据

image-20231130170602828

登陆成功示例:

image-20231130170312592

登陆成功

image-20231130170340485

后端返回数据

image-20231130170654140

问题

在未登录情况下,我们也可以直接访问部门管理、员工管理等功能。

image-20231130171717139

2.登录校验

思路

image-20231130172047260

登录标记

会话技术:用户登录成功之后,每一次请求中,都可以获取到该标记

统一拦截器

  • 过滤器:Filter
  • 拦截器:Interceptor

2.1会话技术

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

image-20231130173454366

2.2会话跟踪方案对比
2.2.1Cookie

image-20231130175721874

  • 优点:HTTP协议中支持的技术
  • 缺点:
    • 移动端APP无法使用Cookie
    • 不安全,用户可以自己禁用Cookie
    • Cookie不能跨域

跨域区分的三个维度:协议、IP/域名、端口

image-20231130180154543

示例

cookie

@Slf4j
@RestController
public class SessionController {
    //设置Cookie
    @GetMapping("/c1")
    public Result cookie1(HttpServletResponse response) {
        response.addCookie(new Cookie("login_username", "Bowen"));//设置Cookie/响应Cookie
        return Result.success();
    }

    //获取Cookie
    @GetMapping("/c2")
    public Result cookie2(HttpServletRequest request) {
        Cookie[] cookies = request.getCookies();//获取所有的Cookie
        for (Cookie cookie : cookies) {
            if (cookie.getName().equals("login_username")) {//输出name为login_username的cookie
                System.out.println("login_username:" + cookie.getValue());
            }
        }
        return Result.success();
    }
}

cookie1

image-20231130180838307

image-20231130181004780

cookie2

image-20231130181122057

2.2.2Session

image-20231130181633744

  • 优点:存储在服务端,安全
  • 缺点:
    • 服务集群环境下无法直接使用Session
    • Cookie的所有缺点

服务集群环境

示例

session

@Slf4j
@RestController
public class SessionController {
    @GetMapping("/s1")
    public Result session1(HttpSession session){
        log.info("HttpSession-s1: {}", session.hashCode());

        session.setAttribute("loginUser", "tom"); //往session中存储数据
        return Result.success();
    }

    @GetMapping("/s2")
    public Result session2(HttpServletRequest request){
        HttpSession session = request.getSession();
        log.info("HttpSession-s2: {}", session.hashCode());

        Object loginUser = session.getAttribute("loginUser"); //从session中获取数据
        log.info("loginUser: {}", loginUser);
        return Result.success(loginUser);
    }
}

session1

访问http://localhost:8080/s1,可以看到响应头多了一行数据

Set-Cookie: JSESSIONID=E6162850B6461136FB3B6E47141BE345; Path=/; HttpOnly

image-20231130182518144

并且Application中存储了该cookie

JSESSIONID=E6162850B6461136FB3B6E47141BE345代表服务器端session对象的ID

image-20231130182552262

session2

访问http://localhost:8080/s2可以看到请求头中的

Cookie: Idea-28469084=333be614-a4f0-4420-bb00-7ef0ad6f47ab; sidebarStatus=0; login_username=Bowen; JSESSIONID=E6162850B6461136FB3B6E47141BE345

image-20231130182643590

控制台输出的日志中,两次请求拿到的session是同一个,都是295124489

image-20231130183713773

2.2.3令牌技术(主流方案)

image-20231130183945036

  • 优点:

    • 支持PC端、移动端

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

    • 减轻服务器端存储压力

  • 缺点:需要自己实现

2.3JWT令牌

2.3.1JWT(将原始的JSON数据格式进行了安全的封装)

简介

  • 全称:JSON Web Token(https://jwt.io)
  • 定义了一种简洁的、自包含的格式,用于在通信双方以json数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的。
  • 组成:
    • 第一部分:Header(头),记录令牌类型、签名算法等。例如:{"alg":"HS256","type":"JWT"}
    • 第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。例如:{"id":"1","username":"Tom"}
    • 第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、payload,并加入指定秘钥,通过指定签名算法计算而来。

image-20231130204742116

Base64:是一种基于64个可打印字符(A-Z a-z 0-9 + /)来表示二进制数据的编码方式

2.3.2场景:登录认证
  1. 登录成功后,生成令牌
  2. 后续每个请求,都要携带JWT令牌,系统在每次请求处理前,先校验令牌,通过后,再处理
2.3.3JWT-生成

pom.xml引入依赖

<!--JWT令牌-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

生成令牌

@SpringBootTest
class TliasWebManagementApplicationTests {
    /**
     * 生成JWT令牌
     */
    @Test
    public void testGenJwt() {
        Map<String, Object> claims = new HashMap<>();
        claims.put("id", 1);
        claims.put("name", "Tom");
        String jwt = Jwts.builder()
                .signWith(SignatureAlgorithm.HS256, "Bowen")//签名算法
                .setClaims(claims)//自定义内容(荷载)
                .setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000))//设置有效期1h
                .compact();
        System.out.println(jwt);
    }
}

image-20231130211942884

生成的令牌

eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiVG9tIiwiaWQiOjEsImV4cCI6MTcwMTM1Mzk2NH0.Bn4qfOumxnzuwkFrBxGw3MQa4fdYf8rOCRRUbL02f1M

可以将令牌粘贴到官网进行解析:https://jwt.io/

image-20231130212655814

2.3.4JWT-校验
@SpringBootTest
class TliasWebManagementApplicationTests {
    /**
     * 解析JWT
     */
    @Test
    public void testPareJwt() {
        Claims claims = Jwts.parser()
                .setSigningKey("Bowen")
                .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiVG9tIiwiaWQiOjEsImV4cCI6MTcwMTM1MzA3OX0.8dK3aG5KhIjmPd9rxGZ_QW0BXMhWacycD9-jhqW4GKg")
                .getBody();
        System.out.println(claims);
    }
}

image-20231130213046402

注意事项

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

思路

  • 令牌生成:登录成功后,生成JWT令牌,并返回给前端
  • 令牌校验:在请求到达服务端后,对令牌进行统一拦截、校验
2.3.5.1接口文档:见1.2接口文档

步骤

  • 引入JWT令牌操作工具类
  • 登录完成后,调用工具类生成JWT令牌,并返回
2.3.5.2JwtUtils.java工具类
public class JwtUtils {

    private static String signKey = "itheima";
    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;
    }
}
2.3.5.3LoginController.java登录的控制层
@Slf4j
@RestController
public class LoginController {
    @Autowired
    private EmpService empService;

    @PostMapping("/login")
    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("name",e.getName());
            claims.put("username",e.getUsername());
            String jwt = JwtUtils.generateJwt(claims);//jwt包含了当前登录的员工信息
            return Result.success(jwt);
        }

        //登录失败,返回错误信息
        return Result.error("用户名或密码错误");
    }
}
2.3.5.4API测试

用户密码正确时

image-20231130220314925

用户或密码错误时

image-20231130220531340

2.3.5.5前后端联调

登录成功可以抓取去tlias_token的值eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoi6YeR5bq4IiwiaWQiOjEsInVzZXJuYW1lIjoiamlueW9uZyIsImV4cCI6MTcwMTM5NjQyOH0.N9-lpBDXMI_Bj_Bhy0Y6WBx1EcsTxBPA0IDnVqUhMm4

image-20231130221218297

进入员工管理页面,可以找到与tlias_token中一样的token

image-20231130221750000

2.4过滤器Filter

过滤器Filter

概述

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

image-20231130223040901

2.4.1快速入门
  1. 定义Filter:定义一个类,实现Filter接口,并重写其所有方法。
  2. 配置Filter:Filter类上加@WebFilter注解,配置拦截资源的路径。引导类上加@ServletComponentScan开启Servlet组件支持。

image-20231130223702016

DemoFilter.javaFilter类

@WebFilter(urlPatterns = "/*")//urlPatterns = "/*" 拦截所有请求
public class DemoFilter implements Filter {
    @Override//初始化方法只调用一次
    public void init(FilterConfig filterConfig) throws ServletException {
//        Filter.super.init(filterConfig);
        System.out.println("init 初始化方法执行了");
    }

    @Override//拦截到请求之后调用,调用多次
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("拦截到了请求");
        //放行
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override//销毁方法,只调用一次
    public void destroy() {
//        Filter.super.destroy();
        System.out.println("destroy 销毁方法执行了");
    }
}

TliasWebManagementApplication.java启动类

@ServletComponentScan//开启了对servlet组件的支持
@SpringBootApplication
public class TliasWebManagementApplication {

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

}

image-20231130225822087

image-20231130225914992

2.4.2详情(执行流程、拦截路径、过滤器链)
2.4.2.1执行流程

image-20231130230255100

  • 放行后访问对应资源,资源访问完成后,会回到Filter中
  • 如果回到Filter中,只执行放行后的逻辑
2.4.2.2拦截路径

执行流程搞清楚之后,接下来再来介绍一下过滤器的拦截路径,Filter可以根据需求,配置不同的拦截资源路径:

拦截路径urlPatterns值含义
拦截具体路径/login只有访问 /login 路径时,才会被拦截
目录拦截/emps/*访问/emps下的所有资源,都会被拦截
拦截所有/*访问所有资源,都会被拦截

示例

拦截了/depts下的一级路径,(调试的时候,在放行处打断点)

@WebFilter(urlPatterns = "/depts/*")//urlPatterns = "/*" 拦截所有请求
public class DemoFilter 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("拦截到了请求...放行前的逻辑");
        //放行
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("拦截到了请求...放行后的逻辑");
    }

    @Override//销毁方法,只调用一次
    public void destroy() {
        System.out.println("destroy 销毁方法执行了");
    }
}
2.4.2.3过滤器链

介绍:一个web应用中,可以配置多个过滤器,这多个过滤器就形成了一个过滤器链

顺序:注解配置的Filter,优先级是按照过滤器类名(字符串)的自然顺序排列

image-20231130233937073

2.4.2.4示例
@WebFilter("/*")
public class AbcFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("Abc拦截到了请求...放行前的逻辑");
        //放行
        filterChain.doFilter(servletRequest, servletResponse);

        System.out.println("Abc拦截到了请求...放行后的逻辑");
    }
}

API测试

image-20231130233344550

控制台输出日志

image-20231130233436111

2.4.3小结
  1. 执行流程
    • 请求==》放行前逻辑==》放行==》资源==》放行后逻辑
  2. 拦截路径
    • /login
    • /depts/*
    • /*
  3. 过滤器链
    • 一个web应用中,配置了多个过滤器,就形成了一个过滤器链
2.4.4登录校验-Filter
2.4.4.1需求分析

需要使用过滤器Filter来完成案例当中的登录校验功能。

image-20230107095010089

先来回顾下前面分析过的登录校验的基本流程:

  • 要进入到后台管理系统,必须先完成登录操作,此时就需要访问登录接口login。

  • 登录成功之后,会在服务端生成一个JWT令牌,并且把JWT令牌返回给前端,前端会将JWT令牌存储下来。

  • 在后续的每一次请求当中,都会将JWT令牌携带到服务端,请求到达服务端之后,要想去访问对应的业务功能,此时必须先要校验令牌的有效性。

  • 对于校验令牌的这一块操作,使用登录校验的过滤器,在过滤器当中来校验令牌的有效性。如果令牌是无效的,就响应一个错误的信息,也不会再去放行访问对应的资源了。如果令牌存在,并且它是有效的,此时就会放行去访问对应的web资源,执行相应的业务操作。

大概清楚了在Filter过滤器的实现步骤了,那在正式开发登录校验过滤器之前,思考两个问题:

  1. 所有的请求,拦截到了之后,都需要校验令牌吗?

    • 答案:登录请求例外
  2. 拦截到请求后,什么情况下才可以放行,执行业务操作?

    • 答案:有令牌,且令牌校验通过(合法);否则都返回未登录错误结果
2.4.4.2具体流程

Filter过滤器的流程步骤:

image-20230112122130564

基于上面的业务流程分析出具体的操作步骤:

  1. 获取请求url
  2. 判断请求url中是否包含login,如果包含,说明是登录操作,放行
  3. 获取请求头中的令牌(token)
  4. 判断令牌是否存在,如果不存在,返回错误结果(未登录)
  5. 解析token,如果解析失败,返回错误结果(未登录)
  6. 放行
2.4.4.3功能实现
引入阿里巴巴fastJSON依赖
<!--fastJSON-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.83</version>
</dependency>
LoginCheckFilter.java登录过滤器
/**
 * @ClassName LoginCheckFilter
 * @Description 登录过滤器
 * @Author Bowen
 * @Date 2023/11/30 23:53
 * @Version 1.0
 **/
@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);//选中代码块后ctrl+alt+t
        } catch (Exception e) {//解析失败
            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);
    }

}

注释掉DemoFilter.javaAbcFilter中的过滤器

2.4.4.4API测试

启动springboot,进行测试

将登录拿到的jwt令牌复制给查询部门的测试接口

令牌eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoi6YeR5bq4IiwiaWQiOjEsInVzZXJuYW1lIjoiamlueW9uZyIsImV4cCI6MTcwMTQwNTY1M30.rT2wkW2OoY_UZz75A36ElRVvmQh5ivv214RzXZp3yHk

image-20231201004134781

添加请求头的token后,发送请求,获取到了数据

image-20231201004401524

若请求参数没有token,则返回JSON数据

image-20231201004608657

查看控制台日志

image-20231201005147906

进行前后端联调

登录后拿到 员工的urlhttp://localhost:90/#/system/emp,退出登录,复制该url到地址栏,直接转跳到登录页面。前后端联调成功~~~

image-20231201005543762

将拿到的url复制到地址栏

image-20231201005840088

直接跳转到登录页面,说明拦截器配置成功

image-20231201005744868

2.5拦截器Interceptor

2.5.1简介

概述

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

image-20231201100545157

2.5.2快速入门
  1. 定义拦截器,实现HandlerInterceptor接口,并重写其所有方法
  2. 注册拦截器

image-20231201101127141

image-20231201101243805

2.5.2.1示例
定义拦截器

com.bowen包下创建interceptor.LoginCheckInterceptor.java

@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
    @Override//目标资源方法运行前,返回true:放行,返回false,不放行
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle....");
        return true;
    }

    @Override//目标资源方法运行后
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle....");
    }

    @Override//视图渲染完毕后运行,最后运行
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion....");
    }
}
配置拦截器

com.bowen包下创建config.WebConfig.java配置类

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginCheckInterceptor loginCheckInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
       registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**");
    }
}

注释掉之前的拦截器,启动该项目

API测试

使用登录接口测试

preHandle方法中返回值为true时放行。

image-20231201105312139

控制台日志

image-20231201105457316

preHandle方法中返回值为false时禁止放行。

image-20231201105640094

控制台日志

image-20231201105803855

2.5.3详解(拦截路径、执行流程)
2.5.3.1拦截路径
  • 拦截器可以根据需求,配置不同的拦截路径

image-20231201110845868

拦截路径含义举例
/*一级路径能匹配/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

addPathPatterns("/**").excludePathPatterns("/login")

excludePathPatterns("/login")排除登录请求

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginCheckInterceptor loginCheckInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
       registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**").excludePathPatterns("/login");
    }
}

API测试-登录接口

登录返回数据令牌成功,并拿到令牌:eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoi6YeR5bq4IiwiaWQiOjEsInVzZXJuYW1lIjoiamlueW9uZyIsImV4cCI6MTcwMTQ1MzM0OX0.EPBM2H_BPFi71Sl4cUs3rzYGuA9xCdsFTPUOxfgNgkY

image-20231201135848087

查看控制台日志,发现直接登录了没有被拦截

image-20231201135958388

API测试-查询部门接口

需要登录后拿到的令牌作为token

image-20231201140152152

查看控制台日志,发现查询部门时拦截后放行,拿到数据,再执行postHandle、afterCompletion方法

image-20231201140416031

API测试-删除部门接口

image-20231201140748389

查看控制台日志,发现删除部门时拦截后放行,更新数据,再执行postHandle、afterCompletion方法

image-20231201141027022

示例2

addPathPatterns("/*").excludePathPatterns("/login")

addPathPatterns("/*")拦截一级路径

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginCheckInterceptor loginCheckInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
       registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/*").excludePathPatterns("/login");
    }
}

API测试-查询部门

查询部门是一级路径,应该被拦截

image-20231201141633981

查看日志,查询部门请求被拦截

image-20231201141826713

API测试-删除部门

删除部门有二级路径,应该没有拦截

image-20231201142013428

查看控制台生成的日志

image-20231201142153004

2.5.3.2执行流程

image-20231201142842440

FilterInterceptor

接口规范不同:过滤器需要实现Fileter接口,而拦截器需要实现HandlerInterceptor接口。

拦截范围不同:过滤器Fileter会拦截所有的资源,而Interceptor只会拦截Spring环境中的资源。

2.5.3.3示例

打开DemoFilter.java中的过滤器,打开WebConfig.java中的拦截器,重新启动服务进行测试

API测试-查询部门

image-20231201143932423

查看控制台日志

image-20231201144344811

2.5.4登录校验-Interceptor
2.5.4.1流程图

流程图与Filter过滤器的流程完全一致

image-20230112122130564

基于上面的业务流程分析出具体的操作步骤:

  1. 获取请求url
  2. 判断请求url中是否包含login,如果包含,说明是登录操作,放行
  3. 获取请求头中的令牌(token)
  4. 判断令牌是否存在,如果不存在,返回错误结果(未登录)
  5. 解析token,如果解析失败,返回错误结果(未登录)
  6. 放行
2.5.4.2功能实现

WebConfig.java配置类,在config包下,其中的.excludePathPatterns("/login")可省略,因为在LoginCheckInterceptor.java//> 2. 判断请求url中是否包含login,如果包含,说明是登录操作,放行,所以造成了重复判断,可删除该拦截器。

/**
 * @ClassName WebConfig
 * @Description 配置类
 * @Author Bowen
 * @Date 2023/12/1 10:20
 * @Version 1.0
 **/
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginCheckInterceptor loginCheckInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
       registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**").excludePathPatterns("/login");
    }
}

LoginCheckInterceptor.java实现登录校验的拦截器

/**
 * @ClassName LoginCheckInterceptor
 * @Description 拦截器`Interceptor`演示
 * @Author Bowen
 * @Date 2023/12/1 10:13
 * @Version 1.0
 **/
@Slf4j
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
    @Override//目标资源方法运行前,返回true:放行,返回false,不放行
    public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
        System.out.println("preHandle....");

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

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

        //> 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 false;
        }

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

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

    }

    @Override//目标资源方法运行后
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle....");
    }

    @Override//视图渲染完毕后运行,最后运行
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion....");
    }
}

注释掉filter包下的所有过滤器注解

启动服务进行测试

2.5.4.3API测试
API测试-登录

image-20231201150047334

API测试-查询部门(未登录)

image-20231201150207302

API测试-查询部门(已登录)

image-20231201150401104

查看日志

image-20231201150628649

前后端联调

启动nginx,不再具体演示,具体操作可参考2.4.4.4API测试

image-20231201150747547

3.异常处理

3.1问题引出

打开浏览器,访问系统中的新增部门操作,系统中已经有了 “就业部” 这个部门,再来增加一个就业部,看看会发生什么现象。

image-20231201155913598

点击确定之后,窗口关闭了,页面没有任何反应,就业部也没有添加上。 而此时会发现,网络请求报错了。

image-20231201160045153

响应回来的数据是一个JSON格式的数据。但这种JSON格式的数据还是我们开发规范当中所提到的统一响应结果Result吗?显然并不是。由于返回的数据不符合开发规范,所以前端并不能解析出响应的JSON数据。

状态码为500,表示服务器端异常,打开idea,查看服务器端出了什么问题。在项目案例中没有进行异常处理

image-20231201160331122

上述错误信息的含义是,dept部门表的name字段的值 就业部 重复了,因为在数据库表dept中已经有了就业部,之前设计这张表时,为name字段建议了唯一约束,所以该字段的值是不能重复的。

而当再添加就业部,这个部门时,就违反了唯一约束,此时就会报错。

3.2全局异常处理器

image-20231201160748800

@RestControllerAdvice = @ControllerAdvice + @ResponseBody

新建一个包exception,创建GlobalExceptionHandler.java全局异常处理器

/**
 * @ClassName GlobalExceptionHandler
 * @Description 全局异常处理器
 * @Author Bowen
 * @Date 2023/12/1 16:10
 * @Version 1.0
 **/
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)//捕获所有异常
    public Result ex(Exception ex) {
        ex.printStackTrace();
        return Result.error("对不起操作失败请联系管理员~~~");
    }
}

添加断点后进行调试,

image-20231201161720173

重新添加重复的部门

image-20231201161757624

点击确定后,进入控制层的断点,恢复程序进入下一个断点

image-20231201161859680

可以看到抛出异常

image-20231201162015424

前端返回异常弹窗

image-20231201162147325

3.3总结

全局异常处理器

  • @RestControllerAdvice
  • @ExceptionHandler

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1274910.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android Studio - 显示配置

1.修改 Code / Split / Design&#xff08;代码视图 & 效果视图&#xff09; 路径&#xff1a;Settings→Editor→Design Tools。 2.增加打开文件的数量及展示方式 路径&#xff1a;Settings→Editor→General→Editor Tabs。

BUUCTF [UTCTF2020]docx 1

BUUCTF:https://buuoj.cn/challenges 题目描述&#xff1a; 得到的 flag 请包上 flag{} 提交。 密文&#xff1a; 下载附件&#xff0c;得到一个.docx文件。 解题思路&#xff1a; 1、打开文件&#xff0c;内容如下&#xff0c;没有flag。 使用010 Editor打开.docx文件&…

AdWords 广告字符的限制是多少?

谷歌已经发展到不仅仅是一个简单的网络搜索。谷歌已成为任何组织所希望的最好的广告网络之一&#xff0c;不断有全球观众来到它研究项目和便利设施、数据、新闻、解决方案等等。 手机的变化带来了数字广告形式的初步转变&#xff0c;随后学习算法的发展和接受也给Google AdWor…

Qt基础 QListWidget dragDrop拖拽操作

目录 1.重写 2.本身自带拖拽功能 关于QLisWidget的item拖拽操作,也可以某个控件拖拽思路 1.重写 重写相当于要重写这些事件函数 void mousePressEvent(QMouseEvent *event); //鼠标按下 void mouseMoveEvent(QMouseEvent *event); //鼠标移动 void dragEnterE…

消字笔的正确使用方法?这样做轻松消除图片杂物!

拍摄的照片或者网上保存的图片有时候会有一点小瑕疵&#xff0c;比如拍摄照片的背景有他人出镜、衣服上有污渍点&#xff0c;保存的网图有水印或其他不想留存的元素。那想要收获一张完美照片&#xff0c;就需要使用消除工具进行处理&#xff0c;有需求就会有产品&#xff0c;具…

数据结构-03-栈

1-栈的结构和特点 先进后出&#xff0c;后进先出 是栈的特点&#xff1b; 从图中&#xff0c;我们看到A入栈先放入底部&#xff0c;然后依次B和C&#xff1b;出栈的顺序依次是C-B-A&#xff1b;这种结构只能在一端操作。所以当某个数据集合只涉及在一端插入和删除数据&#xf…

面试问的最多的时候

1&#xff1a;kafuka消息队列不丢数据 2&#xff1a;MVC的流转 3&#xff1a;线程池 4&#xff1a;liunx 5&#xff1a;k8s 6&#xff1a;负载过高 7&#xff1a;索引 8&#xff1a;事务 9&#xff1a;监听 10&#xff1a;动态路由 11&#xff1a;业务模块 12&#x…

【1】基于多设计模式下的同步异步日志系统

1. 项目介绍 本项⽬主要实现⼀个⽇志系统&#xff0c; 其主要⽀持以下功能: • ⽀持多级别⽇志消息 • ⽀持同步⽇志和异步⽇志 • ⽀持可靠写⼊⽇志到控制台、⽂件以及滚动⽂件中 • ⽀持多线程程序并发写⽇志 • ⽀持扩展不同的⽇志落地⽬标地 2. 开发环境 • CentOS 7 • vs…

【python海洋专题四十九】500hpa位势高度异常场

【python海洋专题四十九】500hpa位势高度异常场 # -*- coding: utf-8 -*- # ---导入数据读取和处理的模块------- import astimport pandas as pd from netCDF4 import Dataset from pathlib import Path import xarray as xr from datetime import datetime import n…

Tomcat 漏洞修复

1、去掉请求响应中Server信息 修复方法&#xff1a; 在Tomcat的配置文件的Connector中增加 server" " &#xff0c;server 的值可以改成你任意想返回的值。

SQL Sever 基础知识 - 限制行数

SQL Sever 基础知识 - 三、限制行数 三、限制行数第1节 OFFSET FETCH - 限制查询返回的行数1.1 OFFSET 和 FETCH 子句1.2 SQL Server OFFSET 和 FETCH 示例 第2节 SELECT TOP - 限制查询结果集中返回的行数或行的百分比2.1 SELECT TOP 子句2.2 PERCENT2.3 WITH TIES2.4 SELECT …

小满CRM与金蝶云星空系统数据集成方案分享

在当今数字化时代&#xff0c;企业面临着海量的数据和系统之间的复杂互联需求。为了提升业务效率和数据管理能力&#xff0c;企业需要借助先进的数据集成平台来实现不同系统之间的无缝连接。本文将分享小满CRM与金蝶云星空系统数据集成方案&#xff0c;为企业提供一个高效、稳定…

“超越摩尔定律”,存内计算走在爆发的边缘

过去几十年来&#xff0c;在摩尔定律的推动下&#xff0c;处理器的性能有了显著提高。然而&#xff0c;传统的计算架构将数据的处理和存储分离开来&#xff0c;随着以数据为中心的计算&#xff08;如机器学习&#xff09;的发展&#xff0c;在这两个物理分离的单元之间传输数据…

Selenium定位元素的方法css和xpath的区别

selenium是一种自动化测试工具&#xff0c;它可以通过不同的定位方式来识别网页上的元素&#xff0c;如id、name、class、tag、link text、partial link text、css和xpath。 如果对软件测试、接口、自动化、性能测试、测试开发、面试经验交流。感兴趣可以加裙485187702&#x…

前后端联调

JavaWenb开发 pom.xml 文件 配置 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://ma…

【DBeaver】驱动添加-Hive和星环

驱动 Hive驱动 hive驱动可以直接去官网下载官网地址&#xff0c;填一下个人信息。 如果想直接下载可以去我上次的资源下地址&#xff0c;需要用zip解压。 星环驱动 星环驱动是我第一次接触&#xff0c;是国产的基于开源Hive驱动自研的产品&#xff0c;我看到官网上有很多类…

java正则表达式字母开头后面跟12位数字

字母开头后面跟12位数字 ^[A-Za-z]\d{12}$ 验证&#xff1a; 验证工具地址&#xff1a; Java正则表达式测试

LangChain(0.0.339)官方文档三:Prompts上(自定义提示模板、使用实时特征或少量示例创建提示模板)

文章目录 一、 Prompt templates1.1 langchain_core.prompts1.2 PromptTemplate1.2.1 简介1.2.2 ICEL 1.3 ChatPromptTemplate1.3.1 使用role创建1.3.2 使用MessagePromptTemplate创建1.3.3 自定义MessagePromptTemplate1.3.3.1 自定义消息角色名1.3.3.2 自定义消息 1.3.4 LCEL…

xcode swiftui项目添加依赖

打开项目targets——Build Phases 点击“” 属于Apple SDKs的依赖可以直接添加 其他依赖需要在 Add Other中添加&#xff0c;在右上角用名字搜索或者URL地址(如GitHub上插件的地址)搜索,然后添加&#xff0c;也可添加本地文件

SpringBoot集成邮箱验证码功能(注册/改密)

准备工作 开启SMTP服务 前往你的邮箱网站&#xff0c;以网易邮箱为例&#xff0c;打开网易邮箱地址&#xff0c;登录你的邮箱&#xff0c;进入邮箱管理后台界面。点击“设置”》》“POP3/SMTP/IMAP”后&#xff0c;点击开启SMTP服务即可。 技术实现 Spring Boot 发送邮件验证…