Web后端开发_06
SpringBootWeb案例_03
登录认证
智能学习辅助系统登录时需要身份验证
1.登录功能
先实现简单的登录功能,在进一步优化。
1.1需求
若账户或密码不存在/密码不正确,则登录失败。
账户密码正确,则登录成功
1.2接口文档
1.2.1登录-基本信息
请求路径:/login
请求方式:POST
接口描述:该接口用于员工登录Tlias智能学习辅助系统,登录完毕后,系统下发JWT令牌。
1.2.2请求参数
参数格式:application/json
参数说明:
名称 | 类型 | 是否必须 | 备注 |
---|---|---|---|
username | string | 必须 | 用户名 |
password | string | 必须 | 密码 |
请求数据样例:
json
{
"username": "jinyong",
"password": "123456"
}
1.2.3响应数据
参数格式:application/json
参数说明:
名称 | 类型 | 是否必须 | 默认值 | 备注 | 其他信息 |
---|---|---|---|---|---|
code | number | 必须 | 响应码, 1 成功 ; 0 失败 | ||
msg | string | 非必须 | 提示信息 | ||
data | string | 必须 | 返回的数据 , jwt令牌 |
响应数据样例:
json
{
"code": 1,
"msg": "success",
"data": "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoi6YeR5bq4IiwiaWQiOjEsInVzZXJuYW1lIjoiamlueW9uZyIsImV4cCI6MTY2MjIwNzA0OH0.KkUc_CXJZJ8Dd063eImx4H9Ojfrr6XMJ-yVzaWCVZCo"
}
1.2.4备注说明
用户登录成功后,系统会自动下发JWT令牌,然后在后续的每次请求中,都需要在请求头header中携带到服务端,请求头的名称为 token ,值为 登录时下发的JWT令牌。
如果检测到用户未登录,则会返回如下固定错误信息:
json{ "code": 0, "msg": "NOT_LOGIN", "data": null }
1.3思路
1.4功能实现
LoginController.java
登录的控制层
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的接口
java
public interface EmpService {
Emp login(Emp emp);
}
EmpServiceImpl.java
员工 接口的实现类
java
@Service
public class EmpServiceImpl implements EmpService {
@Autowired
private EmpMapper empMapper;
@Override
public Emp login(Emp emp) {
return empMapper.getByUsernameAndPassword(emp);
}
}
EmpMapper.java
员工的Mapper层
java
@Mapper
public interface EmpMapper {
/**
* 根据用户名和密码查询员工
*
* @param emp
* @return
*/
@Select("select * from emp where username = #{username} and password = #{password}")
Emp getByUsernameAndPassword(Emp emp);
}
1.5API测试
登录失败:
登录成功:
1.6前端联调
登录失败示例:
后端返回数据
登陆成功示例:
登陆成功
后端返回数据
问题
在未登录情况下,我们也可以直接访问部门管理、员工管理等功能。
2.登录校验
思路
登录标记
会话技术:用户登录成功之后,每一次请求中,都可以获取到该标记
统一拦截器
- 过滤器:
Filter
- 拦截器:
Interceptor
2.1会话技术
- 会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应。
- 会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据
- 会话跟踪方案:
- 客户端会话跟踪技术:Cookie
- 服务端会话跟踪技术:Session
- 令牌技术
2.2会话跟踪方案对比
2.2.1Cookie
- 优点:HTTP协议中支持的技术
- 缺点:
- 移动端APP无法使用Cookie
- 不安全,用户可以自己禁用Cookie
- Cookie不能跨域
跨域区分的三个维度:协议、IP/域名、端口
示例
cookie
java
@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
cookie2
2.2.2Session
- 优点:存储在服务端,安全
- 缺点:
- 服务集群环境下无法直接使用Session
- Cookie的所有缺点
服务集群环境
示例
session
java
@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
并且Application中存储了该cookie
JSESSIONID=E6162850B6461136FB3B6E47141BE345
代表服务器端session对象的ID
session2
访问http://localhost:8080/s2
可以看到请求头中的
Cookie: Idea-28469084=333be614-a4f0-4420-bb00-7ef0ad6f47ab; sidebarStatus=0; login_username=Bowen; JSESSIONID=E6162850B6461136FB3B6E47141BE345
控制台输出的日志中,两次请求拿到的session是同一个,都是295124489
2.2.3令牌技术(主流方案)
优点:
支持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,并加入指定秘钥,通过指定签名算法计算而来。
Base64:是一种基于64个可打印字符(
A-Z a-z 0-9 + /
)来表示二进制数据的编码方式
2.3.2场景:登录认证
- 登录成功后,生成令牌
- 后续每个请求,都要携带JWT令牌,系统在每次请求处理前,先校验令牌,通过后,再处理
2.3.3JWT-生成
pom.xml
引入依赖
xml
<!--JWT令牌-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
生成令牌
java
@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);
}
}
生成的令牌
shell
eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiVG9tIiwiaWQiOjEsImV4cCI6MTcwMTM1Mzk2NH0.Bn4qfOumxnzuwkFrBxGw3MQa4fdYf8rOCRRUbL02f1M
可以将令牌粘贴到官网进行解析:https://jwt.io/
2.3.4JWT-校验
java
@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);
}
}
注意事项
- JWT校验时使用的签名秘钥,必须和生成JWT令牌时使用的秘钥是配套的
- 如果JWT令牌解校验时报错 ,则说明JWT令牌被篡改 或失效 了,令牌非法
2.3.5案例
思路
- 令牌生成:登录成功后,生成JWT令牌,并返回给前端
- 令牌校验:在请求到达服务端后,对令牌进行统一拦截、校验
2.3.5.1接口文档:见1.2接口文档
步骤
- 引入JWT令牌操作工具类
- 登录完成后,调用工具类生成JWT令牌,并返回
2.3.5.2JwtUtils.java
工具类
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
登录的控制层
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测试
用户密码正确时
用户或密码错误时
2.3.5.5前后端联调
登录成功可以抓取去tlias_token
的值eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoi6YeR5bq4IiwiaWQiOjEsInVzZXJuYW1lIjoiamlueW9uZyIsImV4cCI6MTcwMTM5NjQyOH0.N9-lpBDXMI_Bj_Bhy0Y6WBx1EcsTxBPA0IDnVqUhMm4
进入员工管理页面,可以找到与tlias_token
中一样的token
2.4过滤器Filter
过滤器Filter
概述
- 概念:
Filter过滤器
是JavaWeb三大组件(Servlet、Filter
、Listener)之一。- 过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能。
- 过滤器一般完成一些通用 的操作,比如:登录校验 、统一编码处理 、敏感字符处理等。
2.4.1快速入门
- 定义
Filter
:定义一个类,实现Filter
接口,并重写其所有方法。 - 配置
Filter
:Filter类上加@WebFilter
注解,配置拦截资源的路径。引导类上加@ServletComponentScan
开启Servlet
组件支持。
DemoFilter.java
Filter类
java
@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
启动类
java
@ServletComponentScan//开启了对servlet组件的支持
@SpringBootApplication
public class TliasWebManagementApplication {
public static void main(String[] args) {
SpringApplication.run(TliasWebManagementApplication.class, args);
}
}
2.4.2详情(执行流程、拦截路径、过滤器链)
2.4.2.1执行流程
放行后访问对应资源,资源访问完成后,会回到Filter中
如果回到Filter中,只执行放行后的逻辑
2.4.2.2拦截路径
执行流程搞清楚之后,接下来再来介绍一下过滤器的拦截路径,Filter可以根据需求,配置不同的拦截资源路径:
拦截路径 | urlPatterns值 | 含义 |
---|---|---|
拦截具体路径 | /login | 只有访问 /login 路径时,才会被拦截 |
目录拦截 | /emps/* | 访问/emps下的所有资源,都会被拦截 |
拦截所有 | /* | 访问所有资源,都会被拦截 |
示例
拦截了/depts
下的一级路径,(调试的时候,在放行处打断点)
java
@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,优先级是按照过滤器类名(字符串)的自然顺序排列
2.4.2.4示例
java
@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测试
控制台输出日志
2.4.3小结
- 执行流程
- 请求==》放行前逻辑==》放行==》资源==》放行后逻辑
- 拦截路径
- /login
- /depts/*
- /*
- 过滤器链
- 一个web应用中,配置了多个过滤器,就形成了一个过滤器链
2.4.4登录校验-Filter
2.4.4.1需求分析
需要使用过滤器Filter来完成案例当中的登录校验功能。
先来回顾下前面分析过的登录校验的基本流程:
-
要进入到后台管理系统,必须先完成登录操作,此时就需要访问登录接口login。
-
登录成功之后,会在服务端生成一个JWT令牌,并且把JWT令牌返回给前端,前端会将JWT令牌存储下来。
-
在后续的每一次请求当中,都会将JWT令牌携带到服务端,请求到达服务端之后,要想去访问对应的业务功能,此时必须先要校验令牌的有效性。
-
对于校验令牌的这一块操作,使用登录校验的过滤器,在过滤器当中来校验令牌的有效性。如果令牌是无效的,就响应一个错误的信息,也不会再去放行访问对应的资源了。如果令牌存在,并且它是有效的,此时就会放行去访问对应的web资源,执行相应的业务操作。
大概清楚了在Filter过滤器的实现步骤了,那在正式开发登录校验过滤器之前,思考两个问题:
所有的请求,拦截到了之后,都需要校验令牌吗?
- 答案:登录请求例外
拦截到请求后,什么情况下才可以放行,执行业务操作?
- 答案:有令牌,且令牌校验通过(合法);否则都返回未登录错误结果
2.4.4.2具体流程
Filter过滤器的流程步骤:
基于上面的业务流程分析出具体的操作步骤:
- 获取请求url
- 判断请求url中是否包含login,如果包含,说明是登录操作,放行
- 获取请求头中的令牌(token)
- 判断令牌是否存在,如果不存在,返回错误结果(未登录)
- 解析token,如果解析失败,返回错误结果(未登录)
- 放行
2.4.4.3功能实现
引入阿里巴巴fastJSON依赖
xml
<!--fastJSON-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
LoginCheckFilter.java
登录过滤器
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.java
和AbcFilter
中的过滤器
2.4.4.4API测试
启动springboot,进行测试
将登录拿到的jwt令牌复制给查询部门的测试接口
令牌eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoi6YeR5bq4IiwiaWQiOjEsInVzZXJuYW1lIjoiamlueW9uZyIsImV4cCI6MTcwMTQwNTY1M30.rT2wkW2OoY_UZz75A36ElRVvmQh5ivv214RzXZp3yHk
添加请求头的token后,发送请求,获取到了数据
若请求参数没有token,则返回JSON数据
查看控制台日志
进行前后端联调
登录后拿到 员工的urlhttp://localhost:90/#/system/emp
,退出登录,复制该url到地址栏,直接转跳到登录页面。前后端联调成功~~~
将拿到的url复制到地址栏
直接跳转到登录页面,说明拦截器配置成功
2.5拦截器Interceptor
2.5.1简介
概述
- 概念:是一种动态拦截方法调用的机制,类似于过滤器。Spring框架中提供的,用于动态拦截控制器方法的执行。
- 作用:拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码。
2.5.2快速入门
- 定义拦截器,实现
HandlerInterceptor
接口,并重写其所有方法 - 注册拦截器
2.5.2.1示例
定义拦截器
在com.bowen
包下创建interceptor.LoginCheckInterceptor.java
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
配置类
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginCheckInterceptor loginCheckInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**");
}
}
注释掉之前的拦截器,启动该项目
API测试
使用登录接口测试
preHandle
方法中返回值为true
时放行。
控制台日志
preHandle
方法中返回值为false
时禁止放行。
控制台日志
2.5.3详解(拦截路径、执行流程)
2.5.3.1拦截路径
- 拦截器可以根据需求,配置不同的拦截路径
拦截路径 | 含义 | 举例 |
---|---|---|
/* | 一级路径 | 能匹配/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")
排除登录请求
java
@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
查看控制台日志,发现直接登录了没有被拦截
API测试-查询部门接口
需要登录后拿到的令牌作为token
查看控制台日志,发现查询部门时拦截后放行,拿到数据,再执行postHandle、afterCompletion方法
API测试-删除部门接口
查看控制台日志,发现删除部门时拦截后放行,更新数据,再执行postHandle、afterCompletion方法
示例2
addPathPatterns("/*").excludePathPatterns("/login")
addPathPatterns("/*")
拦截一级路径
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginCheckInterceptor loginCheckInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/*").excludePathPatterns("/login");
}
}
API测试-查询部门
查询部门是一级路径,应该被拦截
查看日志,查询部门请求被拦截
API测试-删除部门
删除部门有二级路径,应该没有拦截
查看控制台生成的日志
2.5.3.2执行流程
Filter
与Interceptor
接口规范 不同:过滤器需要实现
Fileter
接口,而拦截器需要实现HandlerInterceptor
接口。拦截范围 不同:过滤器
Fileter
会拦截所有的资源,而Interceptor
只会拦截Spring环境中的资源。
2.5.3.3示例
打开DemoFilter.java
中的过滤器,打开WebConfig.java
中的拦截器,重新启动服务进行测试
API测试-查询部门
查看控制台日志
2.5.4登录校验-Interceptor
2.5.4.1流程图
流程图与Filter过滤器的流程完全一致
基于上面的业务流程分析出具体的操作步骤:
- 获取请求url
- 判断请求url中是否包含login,如果包含,说明是登录操作,放行
- 获取请求头中的令牌(token)
- 判断令牌是否存在,如果不存在,返回错误结果(未登录)
- 解析token,如果解析失败,返回错误结果(未登录)
- 放行
2.5.4.2功能实现
WebConfig.java
配置类,在config包下,其中的.excludePathPatterns("/login")
可省略,因为在LoginCheckInterceptor.java
的//> 2. 判断请求url中是否包含login,如果包含,说明是登录操作,放行
,所以造成了重复判断,可删除该拦截器。
java
/**
* @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
实现登录校验的拦截器
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测试-登录
API测试-查询部门(未登录)
API测试-查询部门(已登录)
查看日志
前后端联调
启动nginx,不再具体演示,具体操作可参考2.4.4.4API测试
3.异常处理
3.1问题引出
打开浏览器,访问系统中的新增部门操作,系统中已经有了 "就业部" 这个部门,再来增加一个就业部,看看会发生什么现象。
点击确定之后,窗口关闭了,页面没有任何反应,就业部也没有添加上。 而此时会发现,网络请求报错了。
响应回来的数据是一个JSON格式的数据。但这种JSON格式的数据还是我们开发规范当中所提到的统一响应结果Result吗?显然并不是。由于返回的数据不符合开发规范,所以前端并不能解析出响应的JSON数据。
状态码为500,表示服务器端异常,打开idea,查看服务器端出了什么问题。在项目案例中没有进行异常处理。
上述错误信息的含义是,dept部门表的name字段的值 就业部 重复了,因为在数据库表dept中已经有了就业部,之前设计这张表时,为name字段建议了唯一约束,所以该字段的值是不能重复的。
而当再添加就业部,这个部门时,就违反了唯一约束,此时就会报错。
3.2全局异常处理器
java
@RestControllerAdvice = @ControllerAdvice + @ResponseBody
新建一个包exception
,创建GlobalExceptionHandler.java
全局异常处理器
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("对不起操作失败请联系管理员~~~");
}
}
添加断点后进行调试,
重新添加重复的部门
点击确定后,进入控制层的断点,恢复程序进入下一个断点
可以看到抛出异常
前端返回异常弹窗
3.3总结
全局异常处理器
@RestControllerAdvice
@ExceptionHandler