登录校验
约 2031 字大约 7 分钟
2025-02-26
1. 会话技术
会话:用户打开浏览器,访问Wb服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应。
会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据。
会话跟踪方案:
客户端会话跟踪技术:
Cookie
优点:http协议支持
缺点:移动端APP无法使用、不安全用户可以自己警用Cookie、Cookie不能跨域
服务端会话跟踪技术:
Session
优点:存储在服务端
缺点:服务器集群时无法使用、Cookie的缺点
令牌技术
优点:支持PC端移动端、解决集群环境下认证问题、减轻服务器端存储压力
缺点:需要自己实现
2. JWT令牌(JSON Web Token)
定义了一种简洁的、自包含的格式,用于在通信双方以JSON数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的。
组成:
Base64:是一种基于64个可打印字符(A-Za-z0-9+/)来表示二进制数据的编码方式,对JSON编码。
第一部分:Header(头),记录签名算法、令牌类型等。例如:
{“alg”:"HS256","type":"JWT"}
第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。例如:
{"id":"1","username":"Tom"}
第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、payload,并加入指定秘钥,通过指定签名算法计算而来(作用是:服务端通过密钥识别当前请求属于哪个会话)。
添加依赖:
<!-- JWT令牌--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
生成令牌样例:
@Test
public void testGenJwt(){
Map<String, Object> claims = new HashMap<>();
claims.put("id", 1);
claims.put("username", "admin");
String jwt = Jwts.builder()
.signWith(SignatureAlgorithm.HS256, "hammay") // 签名算法
.setClaims(claims) // 存放自定义数据(载荷)
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 3600)) // 过期时间为1小时后
.compact();
System.out.println("jwt = " + jwt);
}
解析令牌样例
@Test
public void testParseJwt(){
Claims claims = Jwts.parser()
.setSigningKey("hammay") // 签名算法
.parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNzM5NzExMTgzLCJ1c2VybmFtZSI6ImFkbWluIn0.Kty3gCTsgWCKIHf2S3RW9lMbSwN434I9vgaDbYg7Bjw") // 令牌
.getBody(); // 获取自定义数据(载荷)
System.out.println("claims = " + claims);
}
3. 登录校验实现
令牌生成:登录成功后,生成WT令牌,并返回给前端 令牌校验:在请求到达服务端后,对令牌进行统一拦截、校验
引入工具类(自定义签名秘钥):
package com.ham.utils; 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 = "hammay"; // 自定义签名秘钥 private static Long expire = 43200000L; // 过期时间:12小时 /** * 生成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; }
(1) 过滤器(Filter)
可以把对资源的请求拦截下来,从而实现一些特殊的功能。一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等。
Filter的实现类中(Filter是servlet下的):
@Override // 过滤请求,在服务器收到请求时执行一次
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("拦截请求,放行前逻辑");
// 放行,让请求继续往下传
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("放行后逻辑");
}
在应用类上添加@ServletComponentScan
注解,开启对Servlet组件的支持。
放行后访问对应资源,资源访问完成后,会回到Filter中,并执行放行后逻辑
拦截路径
添加
@WebFilter
注解,参数urlPatterns表示拦截哪些请求。具体路径,比如/login,访问该路径才会拦截
目录拦截,比如/emps/*,访问/emps下的所有资源都会被拦截
拦截所有,/*。
过滤器链
一个web应用中,可以配置多个过滤器,这多个过滤器就形成了一个过滤器链,按照命名字符串顺序依次执行。
过滤器实现登录校验:
@Slf4j
@WebFilter(urlPatterns = "/*")
public class LoginCheckFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 要使用HttpServletRequest和HttpServletResponse的部分方法
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
// 1. 获取请求URL
String requestURL = request.getRequestURL().toString();
log.info("拦截到请求:{}", requestURL);
// 2. 判断是否包含/login,如果包含,放行
if (requestURL.contains("login")){
log.info("登录请求,放行");
filterChain.doFilter(servletRequest, servletResponse);
return;
}
// 3. 获取请求头的令牌token
String jwt = request.getHeader("token");
// 4. 判断令牌是否存在,如果不存在,返回未登录信息
if (StringUtils.isEmpty(jwt)){
log.info("请求头token为空,未登录");
Result error = Result.error("NOT_LOGIN");
// 手动转换为json 使用阿里巴巴的fastjson工具类
String json = JSONObject.toJSONString(error);
response.getWriter().write(json); // 将json数据写回客户端
return;
}
// 5. 验证令牌,如果验证失败,返回未登录信息
try {
JwtUtils.parseJWT(jwt);
} catch (Exception e) {
e.printStackTrace();
log.info("令牌校验失败,未登录");
Result error = Result.error("未登录");
String json = JSONObject.toJSONString(error);
response.getWriter().write(json);
return;
}
// 6. 放行
log.info("令牌校验成功,放行");
filterChain.doFilter(servletRequest, servletResponse);
}
}
(2) 拦截器(Interceptor)
概念:是一种动态拦截方法调用的机制,类似于过滤器。Spring框架中提供的,用来动态拦截控制器方法的执行。
作用:拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码。
定义拦截器:
@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...");
// HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override // 视图渲染完毕后运行,最后运行
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion...");
// HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
- 注册配置拦截器:
@Slf4j
@RestController
@RequestMapping("/login")
public class LoginController {
@Autowired
private EmpService empService;
@PostMapping
public Result login(@RequestBody Emp emp){
log.info("登录:" + emp.getUsername());
Emp e = empService.login(emp);
if (e != null) {
// 登录成功,生成jwt令牌,返回给前端
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("用户名或密码错误");
}
}
拦截路径:
/*:只拦截一级目录
/**:拦截所有目录
/depts/*:拦截depts下的一级目录
/depts/**:拦截depts下的所有目录
校验实现拦截器定义:
@Slf4j
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
@Override // 目标资源方法运行前执行,返回true,放行,返回false,不放行
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. 获取请求URL
String requestURL = request.getRequestURL().toString();
log.info("拦截到请求:{}", requestURL);
// 2. 判断是否包含/login,如果包含,放行
if (requestURL.contains("login")){
log.info("登录请求,放行");
return true;
}
// 3. 获取请求头的令牌token
String jwt = request.getHeader("token");
// 4. 判断令牌是否存在,如果不存在,返回未登录信息
if (StringUtils.isEmpty(jwt)){
log.info("请求头token为空,未登录");
Result error = Result.error("NOT_LOGIN");
// 手动转换为json 使用阿里巴巴的fastjson工具类
String json = JSONObject.toJSONString(error);
response.getWriter().write(json); // 将json数据写回客户端
return false;
}
// 5. 验证令牌,如果验证失败,返回未登录信息
try {
JwtUtils.parseJWT(jwt);
} catch (Exception e) {
e.printStackTrace();
log.info("令牌校验失败,未登录");
Result error = Result.error("未登录");
String json = JSONObject.toJSONString(error);
response.getWriter().write(json);
return false;
}
// 6. 放行
log.info("令牌校验成功,放行");
return true;
}
}
(3) 关系与区别
- 过滤器与拦截器的执行流程:
接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现Handlerlnterceptor接口。
拦截范围不同:过滤器Filter会拦截所有的资源,而Interceptor只会拦载Spring环境中的资源。