登入:
创建一个实体类来封装登入的信息
package org.example.threelayerdecouplingdomeapplication2.pojo;
public class LoginInfo {
private Integer id;
private String username;
private String name;
private String token;
public LoginInfo() {
}
public LoginInfo(Integer id, String username, String name, String token) {
this.id = id;
this.username = username;
this.name = name;
this.token = token;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}
接受前端返回登入的账号密码,直接用Emp封装
package org.example.threelayerdecouplingdomeapplication2.controller;
import org.example.threelayerdecouplingdomeapplication2.pojo.Emp;
import org.example.threelayerdecouplingdomeapplication2.pojo.LoginInfo;
import org.example.threelayerdecouplingdomeapplication2.pojo.Result;
import org.example.threelayerdecouplingdomeapplication2.service.impl.EmpServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/login")
public class LoginController {
@Autowired
EmpServiceImpl empServiceImpl;
@PostMapping
public Result login(@RequestBody Emp emp) {
LoginInfo login = empServiceImpl.login(emp);
if(login != null) {
return Result.ok(login);
}
else
return Result.error("用户名或密码错误");
}
}
servers层进行数据处理
public LoginInfo login(Emp emp) {
Emp emp1 = empMapper.selectByUsernameAndPassword(emp);
if(emp1 != null)
{
return new LoginInfo(emp1.getId(),emp1.getUsername(),emp1.getName(),"");
}else
{
return null;
}
}
使用用户名和密码进行进行查询操作
Emp selectByUsernameAndPassword(Emp emp);
<select id="selectByUsernameAndPassword"
resultType="org.example.threelayerdecouplingdomeapplication2.pojo.Emp">
select id ,username,name from emp where username=#{username} and password=#{password};
</select>
校验:
http协议是无状态的协议,请求的时候无法携带之前请求的结果,就需要对用户是否登入进行校验
两种方式进行校验
1·登录标记:用户登录成功之后,在后续的每一次请求中,都可以获取到该标记。【会话技术】
2·统一拦截:过滤器Filter、拦截器Interceptor
会话技术
会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响
会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据。
会话跟踪方案:
·客户端会话跟踪技术:Cookie
·服务端会话跟踪技术:Session
·令牌技术
Cookie
Cookie 是由服务器生成并发送到客户端浏览器的一小段文本数据,存储在用户本地计算机上。当浏览器再次访问同一服务器时,会将相应的 Cookie 发送回服务器,从而实现会话状态的跟踪。
1. 交互流程
- 客户端首次访问服务器,服务器生成 Cookie 并通过响应头
Set-Cookie
返回 - 浏览器将 Cookie 存储在本地
- 后续请求中,浏览器自动在请求头
Cookie
中携带对应 Cookie 发送给服务器 - 服务器解析 Cookie 数据,处理用户请求
2. Cookie 组成要素
- 名称(Name):Cookie 的标识
- 值(Value):存储的具体数据
- 过期时间(Expires/Max-Age):Cookie 的有效期
- 路径(Path):指定 Cookie 生效的 URL 路径
- 域名(Domain):指定 Cookie 生效的域名
- Secure:标记是否仅通过 HTTPS 传输
- HttpOnly:标记是否禁止 JavaScript 访问
Session
1. 核心作用
- 状态保持:在无状态的 HTTP 协议中,通过 Session 为每个用户维护独立的会话状态
- 数据存储:将用户信息(如登录状态、购物车)存储在服务器端
- 安全增强:相比 Cookie,Session 数据存储在服务器,避免敏感信息泄露
2. 与 Cookie 的关系
- Session ID:服务器生成的唯一标识符,通过 Cookie(默认名为
JSESSIONID
)发送到客户端 - 依赖关系:Session 依赖 Cookie 传递 Session ID,若无 Cookie 支持(如禁用 Cookie),需通过 URL 重写等方式传递 Session ID
令牌
JWT令牌
全称:JSON Web Token (https: //jwt.io/)
定义了一种简洁的、自包含的格式,用于在通信双方以json数据格式安全的传输信息。组成:
第一部分: Header(头),记录令牌类型、签名算法等。例如: { "alg" : "HS256" , "type " : "JWT"}
第二部分: Payload(有效载荷),携带一些自定义信息、默认信息等。例如: {"id": "1" , "username" : "Tom"}]
第三部分: Signature(签名),防止Token被篡改、确保安全性。将header、 payload融入,并加入指定秘钥,通过指定签名算法计算而来。
生成/解析
引入jjwt的依赖。
调用官方提供的工具类Jwts来生成或解析jwt令牌。
生成的工具类
package org.example.threelayerdecouplingdomeapplication2.utils;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.Map;
/**
* JWT工具类,用于生成和解析JSON Web Token
* 基于io.jsonwebtoken库实现,提供了Token的创建与验证功能
*
* JWT token格式: header.payload.signature
* - header: 包含签名算法和token类型
* - payload: 包含声明(claims),如用户ID、角色等
* - signature: 使用密钥对header和payload进行签名
*/
public class JwtUtils {
/**
* 生成JWT Token
* @param claims 存储在JWT payload中的声明信息,通常包含用户ID、角色等
* @return 生成的JWT字符串
*
* 示例:
* Map<String, Object> claims = new HashMap<>();
* claims.put("userId", 123);
* claims.put("role", "admin");
* String token = JwtUtils.generateToken(claims);
*/
public static String generateToken(Map<String, Object> claims) {
return Jwts.builder()
// 使用HS256算法和指定密钥进行签名
.signWith(SignatureAlgorithm.HS256, "weiyicheng")
// 将用户自定义的声明信息添加到payload中
.addClaims(claims)
// 设置Token的过期时间为7天
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 7))
// 生成最终的JWT字符串
.compact();
}
/**
* 解析JWT Token,获取其中的声明信息
* @param token 待解析的JWT字符串
* @return 包含声明信息的Map
* @throws Exception 当Token无效、过期或签名验证失败时抛出异常
*
* 示例:
* Map<String, Object> claims = JwtUtils.parseToken(token);
* Integer userId = (Integer) claims.get("userId");
*/
public static Map<String, Object> parseToken(String token) throws Exception {
return Jwts.parser()
// 设置用于验证签名的密钥,必须与生成Token时使用的密钥相同
.setSigningKey("weiyicheng")
// 解析JWT并验证签名和过期时间
.parseClaimsJws(token)
// 获取JWT中的payload部分(即声明信息)
.getBody();
}
}
过滤器Filter
概念:Filter过滤器,是JavaWeb三大组件(ServletFilter、Lisner)之一。
过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能。
过滤器一般完成一些通用的操作,比如∶登录校验、统一编码处理、敏感字符处理等。
快速入门:
1. 定义Filter:定义一个类,实现 Filter接口,并实现其所有方法。
2.配置Filter: Filter类上加oMebFilter注解,配置拦截路径。引导类上加@ServletComponentScan开启Servlet组件
package org.example.threelayerdecouplingdomeapplication2.filter;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import java.io.IOException;
/**
* DemoFilter 是一个Servlet过滤器,用于拦截所有HTTP请求
* 过滤器是Java Web应用中实现AOP(面向切面编程)的重要组件
* 可用于实现权限检查、字符编码转换、日志记录等横切关注点
*/
@WebFilter(urlPatterns = "/*") // 指定该过滤器拦截所有URL请求
public class DemoFilter implements Filter {
/**
* 过滤器初始化方法,在过滤器实例创建后调用
* 通常用于加载配置信息、初始化资源等操作
* @param filterConfig 过滤器配置对象,包含初始化参数和Servlet上下文
* @throws ServletException 初始化过程中发生的Servlet异常
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 调用父类初始化方法(默认实现为空)
Filter.super.init(filterConfig);
}
/**
* 过滤器核心方法,每次请求匹配URL模式时都会调用
* 可对请求和响应进行预处理和后处理
* @param servletRequest 客户端请求对象
* @param servletResponse 服务器响应对象
* @param filterChain 过滤器链,用于传递请求到下一个过滤器或Servlet
* @throws IOException I/O异常
* @throws ServletException Servlet处理异常
*/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 1. 前置处理代码可以放在这里(例如设置字符编码)
// 2. 调用filterChain.doFilter传递请求到下一个组件
// 如果是最后一个过滤器,则传递到目标Servlet
filterChain.doFilter(servletRequest, servletResponse);
// 3. 后置处理代码可以放在这里(例如记录响应信息)
}
/**
* 过滤器销毁方法,在过滤器实例被销毁前调用
* 通常用于释放资源、关闭连接等清理操作
*/
@Override
public void destroy() {
// 调用父类销毁方法(默认实现为空)
Filter.super.destroy();
}
}
令牌校验:
package org.example.threelayerdecouplingdomeapplication2.filter;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.example.threelayerdecouplingdomeapplication2.utils.JwtUtils;
import java.io.IOException;
/**
* Token过滤器 - 用于验证HTTP请求中的JWT令牌
* 该过滤器会拦截所有请求,对需要认证的接口进行令牌校验
* 实现了基于JWT的无状态认证机制
*/
@WebFilter(urlPatterns = "/*")
public class TokenFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 将通用请求/响应对象转换为HTTP专用对象
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
// 获取请求URI,用于判断是否为无需认证的接口
String requestURI = request.getRequestURI();
// 设置响应字符编码为UTF-8,防止中文乱码
response.setCharacterEncoding("UTF-8");
// 白名单处理:登录和注册接口无需认证,直接放行
if (requestURI.contains("/login") || requestURI.contains("/register")) {
filterChain.doFilter(request, response); // 放行请求到后续过滤器或Servlet
return;
}
// 从请求头中获取JWT令牌
String token = request.getHeader("token");
// 令牌存在性检查
if (token != null && !token.isEmpty()) {
try {
// 调用工具类解析令牌:验证签名有效性、过期时间等
JwtUtils.parseToken(token);
// 令牌验证通过,放行请求
filterChain.doFilter(request, response);
} catch (Exception e) {
// 令牌验证失败,返回401状态码和错误信息
response.getWriter().write("非法令牌");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
} else {
// 未提供令牌,返回401状态码和错误信息
response.getWriter().write("未登入");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
}
}
Filter-过滤器链
介绍:一个web应用中,可以配置多个过滤器,这多个过滤器就形成了一个过滤器链。
顺序:注解配置的Filter,优先级是按照过滤器类名(字符串)的自然排序。
拦截器
概念:是一种动态拦截方法调用的机制,类似于过滤器。Spring框架中提供的,主要用来动态拦截控制器方法的执行。作用:拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码。
快速入门
1·定义拦截器,实现HandlerInterceptor接口,并实现其所有方法。
2·注册拦截器
令牌校验:
1·获取请求url。
2·判断请求url中是否包含login,如果包含,说明是登录操作,放行
3·获取请求头中的令牌( token)。
4·判断令牌是否存在,如果不存在,响应401。
5·解析token,如果解析失败,响应401 。
6·放行。
package org.example.threelayerdecouplingdomeapplication2.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.example.threelayerdecouplingdomeapplication2.utils.JwtUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
@Component
public class TokenInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
if (requestURI.contains("/login") || requestURI.contains("/register")) {
return true;
}
String token = request.getHeader("token");
response.setCharacterEncoding("UTF-8");
if (token != null&& !token.isEmpty()) {
try {
JwtUtils.parseToken(token);
return true;
} catch (Exception e) {
response.getWriter().write("非法令牌");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
}
else {
response.getWriter().write("未登入");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
return false;
}
}
详情
配置时指定拦截器对象,添加拦截路径(“/**”)和排除拦截的路径(“/login”)
配置拦截路径
过滤器和拦截器同时存在的时候先经过过滤器在通过拦截器