SpringBoot-登录校验
// 后面用(统一管理类)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
/**
* 响应码,1 代表成功; 0 代表失败
*/
private Integer code;
/**
* 响应信息 描述字符串
*/
private String msg;
/**
* 返回的数据
*/
private Object data;
/**
* 增删改 成功响应
*/
public static Result success() {
return new Result(1, "success", null);
}
/**
* 查询 成功响应
* @param data
* @return
*/
public static Result success(Object data) {
return new Result(1, "success", data);
}
/**
* 失败响应
* @param msg
* @return
*/
public static Result error(String msg) {
return new Result(0, msg, null);
}
}
登录校验
什么是登录校验?
- 所谓登录校验,指的是我们在服务器端接收到浏览器发送过来的请求之后,首先我们要对请求进行校验。先要校验一下用户登录了没有,如果用户已经登录了,就直接执行对应的业务操作就可以了;如果用户没有登录,此时就不允许他执行相关的业务操作,直接给前端响应一个错误的结果,最终跳转到登录页面,要求他登录成功之后,再来访问对应的数据。
首先我们在宏观上先有一个认知:
前面在讲解HTTP协议的时候,我们提到HTTP协议是无状态协议。什么又是无状态的协议?
- 所谓无状态,指的是每一次请求都是独立的,下一次请求并不会携带上一次请求的数据。而浏览器与服务器之间进行交互,基于HTTP协议也就意味着现在我们通过浏览器来访问了登陆这个接口,实现了登陆的操作,接下来我们在执行其他业务操作时,服务器也并不知道这个员工到底登陆了没有。因为HTTP协议是无状态的,两次请求之间是独立的,所以是无法判断这个员工到底登陆了没有。
那应该怎么来实现登录校验的操作呢?具体的实现思路可以分为两部分:
- 在员工登录成功后,需要将用户登录成功的信息存起来,记录用户已经登录成功的标记。
- 在浏览器发起请求时,需要在服务端进行统一拦截,拦截后进行登录校验。
- 想要判断员工是否已经登录,我们需要在员工登录成功之后,存储一个登录成功的标记,接下来在每一个接口方法执行之前,先做一个条件判断,判断一下这个员工到底登录了没有。如果是登录了,就可以执行正常的业务操作,如果没有登录,会直接给前端返回一个错误的信息,前端拿到这个错误信息之后会自动的跳转到登录页面。
- 我们程序中所开发的查询功能、删除功能、添加功能、修改功能,都需要使用以上套路进行登录校验。此时就会出现:相同代码逻辑,每个功能都需要编写,就会造成代码非常繁琐。
- 为了简化这块操作,我们可以使用一种技术:统一拦截技术。
- 通过统一拦截的技术,我们可以来拦截浏览器发送过来的所有的请求,拦截到这个请求之后,就可以通过请求来获取之前所存入的登录标记,在获取到登录标记且标记为登录成功,就说明员工已经登录了。如果已经登录,我们就直接放行(意思就是可以访问正常的业务接口了)。
1、会话技术
1. 概述
会话:用户打开一个浏览器,点击了很多超链接,访问多个web资源,关闭浏览器,这个过程可以称之为会话
有状态会话:一个同学来过教室,下次再来教室,我们会知道这个同学曾经来过,称之为有状态会话;
你能怎么证明你是师大的学生?
你 师大
1、通知书 师大给你通知书
2、学校登记 师大标记你来过了
一个网站,怎么证明你来过?
客户端 服务端
- 服务端给客户端一个信件,客户端下次访问服务器带上信件就可以了; cookie
- 服务器登记你来过了,下次你来的时候我来匹配你;
需要注意的是:会话是和浏览器关联的,当有三个浏览器客户端和服务器建立了连接时,就会有三个会话。同一个浏览器在未关闭之前请求了多次服务器,这多次请求是属于同一个会话。比如:1、2、3这三个请求都是属于同一个会话。当我们关闭浏览器之后,这次会话就结束了。而如果我们是直接把web服务器关了,那么所有的会话就都结束了。
会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据。
服务器会接收很多的请求,但是服务器是需要识别出这些请求是不是同一个浏览器发出来的。比如:1和2这两个请求是不是同一个浏览器发出来的,3和5这两个请求不是同一个浏览器发出来的。如果是同一个浏览器发出来的,就说明是同一个会话。如果是不同的浏览器发出来的,就说明是不同的会话。而识别多次请求是否来自于同一浏览器的过程,我们就称为会话跟踪。
我们使用会话跟踪技术就是要完成在同一个会话中,多个请求之间进行共享数据。
为什么要共享数据呢?
由于HTTP是无状态协议,在后面请求中怎么拿到前一次请求生成的数据呢?此时就需要在一次会话的多次请求之间进行数据共享
会话跟踪方案
- 客户端会话跟踪技术:Cookie
- 服务端会话跟踪技术:Session
- 令牌技术:Token
2. Cookie
- 客户端技术(响应,请求)
- 服务器端在给客户端在响应数据的时候,会自动的将 cookie 响应给浏览器,浏览器接收到响应回来的 cookie 之后,会自动的将 cookie 的值存储在浏览器本地。接下来在后续的每一次请求当中,都会将浏览器本地所存储的 cookie 自动地携带到服务端。
- 服务器会 自动 的将 cookie 响应给浏览器。
- 浏览器接收到响应回来的数据之后,会 自动 的将 cookie 存储在浏览器本地。
- 在后续的请求当中,浏览器会 自动 的将 cookie 携带到服务器端。
为什么这一切都是自动化进行的?
是因为 cookie 它是 HTTP 协议当中所支持的技术,而各大浏览器厂商都支持了这一标准。在 HTTP 协议官方给我们提供了一个响应头和请求头:
- 响应头 Set-Cookie :设置Cookie数据的
- 请求头 Cookie:携带Cookie数据的
优点:HTTP协议中支持的技术
缺点:
- 移动端APP无法使用Cookie
- 不安全,用户可以自己禁用Cookie
- Cookie不能跨域
跨域介绍:
- 现在的项目,大部分都是前后端分离的,前后端最终也会分开部署,前端部署在服务器 192.168.150.200 上,端口 80,后端部署在 192.168.150.100上,端口 8080
- 我们打开浏览器直接访问前端工程,访问url:
http://192.168.150.200/login.html
- 然后在该页面发起请求到服务端,而服务端所在地址不再是localhost,而是服务器的IP地址192.168.150.100,假设访问接口地址为:
http://192.168.150.100:8080/login
- 那此时就存在跨域操作了,因为我们是在
http://192.168.150.200/login.html
这个页面上访问了http://192.168.150.100:8080/login
接口- 此时如果服务器设置了一个Cookie,这个Cookie是不能使用的,因为Cookie无法跨域
区分跨域的维度:
- 协议
- IP/协议
- 端口
只要上述的三个维度有任何一个维度不同,那就是跨域操作
举例:
http://192.168.150.200/login.html ----------> https://192.168.150.200/login [协议不同,跨域]
http://192.168.150.200/login.html ----------> http://192.168.150.100/login [IP不同,跨域]
http://192.168.150.200/login.html ----------> http://192.168.150.200:8080/login [端口不同,跨域]
http://192.168.150.200/login.html ----------> http://192.168.150.200/login [不跨域]
@Slf4j
@RestController
public class SessionController {
/**
* 设置Cookie
* @param response
* @return
*/
@GetMapping("/c1")
public Result cookie1(HttpServletResponse response) {
// 设置Cookie/响应Cookie
response.addCookie(new Cookie("login_username", "xiaokoen"));
return Result.success();
}
/**
* 获取Cookie
*
* @param request
* @return
*/
@GetMapping("/c2")
public Result cookie2(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
for (Cookie cookie : cookies) {
log.info("【cookie】:{},{}", cookie.getName(), cookie.getValue());
}
return Result.success();
}
}
- 从请求中拿到cookie信息
Cookie[] cookies = req.getCookies(); // 获得cookie
cookie.getName() // 获得cookie中的key
cookie.getValue() // 获得cookie中的value
- 服务器响应给客户端cookie
new Cookie("lastLoginTime", System.currentTimeMillis() + ""); //新建一个cookie
cookie.setMaxAge(24*60*60); // 设置cookie的有效期
resp.addCookie(cookie); // 响应给客户端一个cookie
Cookie一般保存在本地的用户目录下Appdate;
一个网站Cookie是否存在上限?
- 一个Cookie只能保存一个信息;
- 一个web站点(服务器)可以给浏览器发送多个cookie,最多存放20个cookie
- Cookie大小有限制4kb;
- 浏览器上限300个cookie
删除cookie
- 不设置有效期,关闭浏览器,自动失效
- 有效期设置为0;
编码解码:
// URL编码
String name = URLEncoder.encode("小焦同学", "UTF-8");
Cookie cookie = new Cookie("name", name);
// URL解码
String name = URLDecoder.decode(cookie.getValue(), "UTF-8");
out.write(name);
3. Session
- 服务器技术,利用这个技术,可以保存用户的会话信息?我们可以把信息或者数据放到session中
常见:网站登录之后,下次不用登录了,第二次访问直接上去
流程
-
如果我们现在要基于 Session 来进行会话跟踪,浏览器在第一次请求服务器的时候,我们就可以直接在服务器当中来获取到会话对象Session。如果是第一次请求Session ,会话对象是不存在的,这个时候服务器会自动的创建一个会话对象Session 。而每一个会话对象Session ,它都有一个ID(示意图中Session后面括号中的1,就表示ID),我们称之为 Session 的ID。
-
接下来,服务器端在给浏览器响应数据的时候,它会将 Session 的 ID 通过 Cookie 响应给浏览器。其实在响应头当中增加了一个 Set-Cookie 响应头。这个 Set-Cookie 响应头对应的值是不是cookie? cookie 的名字是固定的 JSESSIONID 代表的服务器端会话对象 Session 的 ID。浏览器会自动识别这个响应头,然后自动将Cookie存储在浏览器本地。
-
接下来,在后续的每一次请求当中,都会将 Cookie 的数据获取出来,并且携带到服务端。接下来服务器拿到JSESSIONID这个 Cookie 的值,也就是 Session 的ID。拿到 ID 之后,就会从众多的 Session 当中来找到当前请求对应的会话对象Session。
什么是Session:
- 服务器会给每一个用户(浏览器)创建一个Session对象;
- 一个Session独占一个浏览器,只要浏览器没有关闭,这个Session就存在;
- 用户登录之后,整个网站它都可以访问!–>保存用户信息;保存购物车信息…
// session创建的时候做了哪些事情(将value存放到cookie中)
// Cookie cookie = new Cookie("JSESSIONID", sessionId);
// resp.addCookie(cookie);
使用场景:
- 保存一个登陆用户信息;
- 购物车信息;
- 在整个网站中经常使用的数据,我们将它保存在session中;
使用session:
@Slf4j
@RestController
public class SessionController {
@GetMapping("/s1")
public Result session1(HttpSession session,HttpServletRequest request) {
HttpSession session1 = request.getSession(true);
log.info("HttpSession-s1: {}", session.hashCode());
// 往session中存储数据
// session 就是一个大的HashMap
session.setAttribute("loginUser", "tom");
session.setAttribute("name","小焦同学");
Person person = new Person("小焦",15);
session.setAttribute("person", person);
// 获取Session的ID
String sessionId = session.getId();
// 判断Session是不是新创建
if (session.isNew()) {
resp.getWriter().write("session创建成功ID:"+sessionId);
}else
resp.getWriter().write("session已经在服务器中存在了ID:"+sessionId);
// session创建的时候做了哪些事情(将value存放到cookie中)
// Cookie cookie = new Cookie("JSESSIONID", sessionId);
// resp.addCookie(cookie);
return Result.success();
}
@GetMapping("/s2")
public Result session2(HttpServletRequest request) {
HttpSession session = request.getSession();
// session.hashCode():每个对象都有唯一标识,如果两个对象的hashCode相同,那么两个session就是同一个
log.info("HttpSession-s2: {},{}", session.hashCode(), session.getId());
// 从session中获取数据
Object loginUser = session.getAttribute("loginUser");
log.info("loginUser: {}", loginUser);
return Result.success(loginUser);
}
@GetMapping("/s2")
public Result session2(HttpSession session) {
// session.hashCode():每个对象都有唯一标识,如果两个对象的hashCode相同,那么两个session就是同一个
log.info("HttpSession-s2: {},{}", session.hashCode(), session.getId());
// 从session中获取数据
Object loginUser = session.getAttribute("loginUser");
log.info("loginUser: {}", loginUser);
return Result.success(loginUser);
}
// 移除session数据
session.removeAttribute("name");
// 手动注销session,再重新访问服务器会重新生成一个新的sessionId
session.invalidate();
}
session销毁:
- 手动销毁:session.invalidate()
- 自动销毁:web.xml
会话自动过期:web.xml:
<!--设置session默认的失效时间-->
<session-config>
<!--1440分钟后session自动失效,session以分钟为单位-->
<session-timeout>1440</session-timeout>
</session-config>
注意:session名字可能会改变,如果改变就要改n个servlet类,这时使用常量,在servlet中直接使用类名.常量名
public final static String USER_SESSION = "USER_SESSION";
req.getSession().setAttribute(Constant.USER_SESSION,username);
四种区别:
pageContext.setAttribute("name1", "小焦01"); // 保存的数据只在一个页面中有效。
request.setAttribute("name2", "小焦02"); // 保存的数据只有在一个请求中有效,请求转发(跳转)会携带这个数据。
session.setAttribute("name3", "小焦03"); // 保存的数据只有在一次会话(从打开浏览器到关闭浏览器)中有效。
application.setAttribute("name4", "小焦04"); // 保存的数据在服务器中有效,从打开服务器到关闭服务器。
@GetMapping("/s1")
public Result session1(HttpSession session,HttpServletRequest request) {
HttpSession session1 = request.getSession(true);
log.info("HttpSession-s1: {}", session.hashCode());
// 往session中存储数据
// session 就是一个大的HashMap
session.setAttribute("loginUser", "tom");
return Result.success();
}
@GetMapping("/s2")
public Result session2(HttpServletRequest request) {
HttpSession session = request.getSession();
// session.hashCode():每个对象都有唯一标识,如果两个对象的hashCode相同,那么两个session就是同一个
log.info("HttpSession-s2: {},{}", session.hashCode(), session.getId());
// 从session中获取数据
Object loginUser = session.getAttribute("loginUser");
log.info("loginUser: {}", loginUser);
return Result.success(loginUser);
}
优缺点
- 优点:Session是存储在服务端的,安全
- 缺点:
- 服务器集群环境下无法直接使用Session
- 移动端APP(Android、IOS)中无法使用Cookie
- 用户可以自己禁用Cookie
- Cookie不能跨域
Session 底层是基于Cookie实现的会话跟踪,如果Cookie不可用,则该方案,也就失效了。
服务器集群环境为何无法使用Session?
- 首先第一点,我们现在所开发的项目,一般都不会只部署在一台服务器上,因为一台服务器会存在一个很大的问题,就是单点故障。所谓单点故障,指的就是一旦这台服务器挂了,整个应用都没法访问了。
所以在现在的企业项目开发当中,最终部署的时候都是以集群的形式来进行部署,也就是同一个项目它会部署多份。比如这个项目我们现在就部署了 3 份。
而用户在访问的时候,到底访问这三台其中的哪一台?其实用户在访问的时候,他会访问一台前置的服务器,我们叫负载均衡服务器,我们在后面项目当中会详细讲解。目前大家先有一个印象负载均衡服务器,它的作用就是将前端发起的请求均匀的分发给后面的这三台服务器。
此时假如我们通过 session 来进行会话跟踪,可能就会存在这样一个问题。用户打开浏览器要进行登录操作,此时会发起登录请求。登录请求到达负载均衡服务器,将这个请求转给了第一台 Tomcat 服务器。
Tomcat 服务器接收到请求之后,要获取到会话对象session。获取到会话对象 session 之后,要给浏览器响应数据,最终在给浏览器响应数据的时候,就会携带这么一个 cookie 的名字,就是 JSESSIONID ,下一次再请求的时候,是不是又会将 Cookie 携带到服务端?
好。此时假如又执行了一次查询操作,要查询部门的数据。这次请求到达负载均衡服务器之后,负载均衡服务器将这次请求转给了第二台 Tomcat 服务器,此时他就要到第二台 Tomcat 服务器当中。根据JSESSIONID 也就是对应的 session 的 ID 值,要找对应的 session 会话对象。
我想请问在第二台服务器当中有没有这个ID的会话对象 Session, 是没有的。此时是不是就出现问题了?我同一个浏览器发起了 2 次请求,结果获取到的不是同一个会话对象,这就是Session这种会话跟踪方案它的缺点,在服务器集群环境下无法直接使用Session。
Session和Cookie的区别:
- Cookie是把用户的数据写给用户的浏览器,浏览器保存(可以保存多个)
- Session把用户的数据写到用户独占Session中,服务器端保存(保存重要信息,减少服务器资源的浪费)
- Session对象由服务器创建
- session是在服务器中存放数据(每个用户的sessionID都是唯一的)、cookie数据存放在客户端中
4. ApplicationContext
servletContext是所有用户共用一个,session是每个用户一个sessionID
2、JWT令牌
官网:https://jwt.io/
- JSON Web Token是目前最流行的跨域认证解决方案,,适合前后端分离项目通过Restful API进行数据交互时进行身份认证
- 它将用户信息加密到token里,服务器不保存任何用户信息,服务器通过使用保存的秘钥验证token的正确性,只要正确即通过验证
- JWT令牌就是一个字符串
(前端)发送,前端发送请求到后端,放到请求头上面
对数据 进行编码(可以反编码)
JWT的组成:(JWT令牌由三个部分组成,三个部分之间使用英文的点来分割)
- 第一部分:Header(头), 记录令牌类型、签名算法等。 例如:{“alg”:“HS256”,“type”:“JWT”}
- 第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。 例如:{“id”:“1”,“username”:“Tom”}
- 第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、payload,并加入指定秘钥,通过指定签名算法计算而来。
签名的目的就是为了防jwt令牌被篡改,而正是因为jwt令牌最后一个部分数字签名的存在,所以整个jwt 令牌是非常安全可靠的。一旦jwt令牌当中任何一个部分、任何一个字符被篡改了,整个令牌在校验的时候都会失败,所以它是非常安全可靠的。
JWT是如何将原始的JSON格式数据,转变为字符串的呢?
其实在生成JWT令牌时,会对JSON格式的数据进行一次编码:进行base64编码
Base64:是一种基于64个可打印的字符来表示二进制数据的编码方式。既然能编码,那也就意味着也能解码。所使用的64个字符分别是A到Z、a到z、 0- 9,一个加号,一个斜杠,加起来就是64个字符。任何数据经过base64编码之后,最终就会通过这64个字符来表示。当然还有一个符号,那就是等号。等号它是一个补位的符号
需要注意的是Base64是编码方式,而不是加密方式。
1、生成和校验
- 导入依赖
<!-- JWT依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
- JWT工具类
public class JwtUtils {
/**
* 生成JWT令牌
* @param claims JWT第二部分负载 payload 中存储的内容
* @return
*/
public static String generateJwt(Map<String, Object> claims) {
String jwt = Jwts.builder()
.addClaims(claims) // 自定义信息(有效载荷)
.signWith(SignatureAlgorithm.HS256, "xiaokoen") // 签名算法(头部)
.setExpiration(new Date(System.currentTimeMillis() + 1800000L)) // 过期时间
.compact();
return jwt;
}
/**
* 解析JWT令牌
* @param jwt JWT令牌
* @return JWT第二部分负载 payload 中存储的内容
*/
public static Claims parseJWT(String jwt) {
Claims claims = Jwts.parser()
.setSigningKey("xiaokoen") // 指定签名密钥(必须保证和生成令牌时使用相同的签名密钥)
.parseClaimsJws(jwt) // 指定令牌Token
.getBody();
return claims;
}
}
第一部分解析出来,看到JSON格式的原始数据,所使用的签名算法为HS256。
第二个部分是我们自定义的数据,之前我们自定义的数据就是id,还有一个exp代表的是我们所设置的过期时间。
由于前两个部分是base64编码,所以是可以直接解码出来。但最后一个部分并不是base64编码,是经过签名算法计算出来的,所以最后一个部分是不会解析的。
篡改令牌中的任何一个字符,在对令牌进行解析时都会报错,所以JWT令牌是非常安全可靠的。
JWT令牌过期后,令牌就失效了,解析的为非法令牌。
通过以上测试,我们在使用JWT令牌时需要注意:
-
JWT校验时使用的签名秘钥,必须和生成JWT令牌时使用的秘钥是配套的。
-
如果JWT令牌解析校验时报错,则说明 JWT令牌被篡改 或 失效了,令牌非法。
2、登录下发令牌
- 生成令牌
- 在登录成功之后来生成一个JWT令牌,并且把这个令牌直接返回给前端
- 校验令牌
- 拦截前端请求,从请求中获取到令牌,对令牌进行解析校验
JwtUtils工具类
public class JwtUtils {
/**
* 生成JWT令牌
* @param claims JWT第二部分负载 payload 中存储的内容
* @return
*/
public static String generateJwt(Map<String, Object> claims) {
String jwt = Jwts.builder()
.addClaims(claims) // 自定义信息(有效载荷)
.signWith(SignatureAlgorithm.HS256, "xiaokoen") // 签名算法(头部)
.setExpiration(new Date(System.currentTimeMillis() + 1800000L)) // 过期时间
.compact();
return jwt;
}
}
LoginController控制器
@PostMapping("/login")
public Result login(@RequestBody Emp emp) {
log.info("【准备登录】");
Emp dbEmp = empService.login(emp);
if (dbEmp != null) {
HashMap<String, Object> emps = new HashMap();
emps.put("username",emp.getUsername());
emps.put("password",emp.getPassword());
String token = JwtUtils.generateJwt(emps);
return Result.success(token);
}
return Result.error("用户名或密码不正确");
}
登录请求完成后,可以看到JWT令牌已经响应给了前端,此时前端就会将JWT令牌存储在浏览器本地。
3、过滤器Filter
什么是Filter?
- Filter表示过滤器,是 JavaWeb三大组件(Servlet、Filter、Listener)之一。
- 过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能
- 使用了过滤器之后,要想访问web服务器上的资源,必须先经过滤器,过滤器处理完毕之后,才可以访问对应的资源。
- 过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等。
1、Filter拦截路径
补充:FastJson
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
1、概述
fastjson.jar是阿里开发的一款专门用于Java开发的包,可以方便的实现json对象与JavaBean对象的转换,实现JavaBean对象与json字符串的转换,实现json对象与json字符串的转换。实现json的转换方法很多,最后的实现结果都是一样的。
2、fastjson三个主要的类
JSONObject代表json对象
- JSONObject实现了Map接口,所以JSONObject底层操作是由Map实现的
- JSONObject对应json对象,通过各种形式的get()方法可以获取json对象中的数据,也可以利用size(),isEmpty()等方法获取“键:值”对的个数和判断是否为空。其本质是通过实现Map接口并调用接口中的方法完成的
JSONArray代表json对象数组
- 内部是有List接口中的方法来完成操作的
JSON代表JSONObject和JSONArray的转化
- JSON类源码分析与使用
- 主要实现json对象,json对象数组,javabean对象,json字符串之间的相互转化
2、登录校验
流程
@Slf4j
@WebFilter("/*")
public class LoginFilter implements Filter {
public static final String URL = "/login";
public static final String TOKEN = "token";
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.info("执行过滤器...");
/**
* 1. 获取请求路径
* 2. 判断是否是登录请求
* 3. 获取请求头token
* 4. 判断是否有token
* 5. 解析token
* 6. 放行
*/
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
// 获取请求路径
StringBuffer requestUrl = request.getRequestURL();
String url = requestUrl.toString();
/**
* 登录界面不可以拦截,如果都拦截了,登录都没法登录了
*/
// 判断是否是登录请求
if (url.contains(URL)){
filterChain.doFilter(servletRequest,servletResponse);
return;
}
// 获取请求头token
String token = request.getHeader(TOKEN);
// 如果没有token,则没有权限
if (token == null){
error(response);
return;
}
try {
// 解析token,如果成功放行,如果抛出异常,token可能过期或者被别人恶意修改,没有权限
JwtUtils.parseJWT(token);
filterChain.doFilter(servletRequest,servletResponse);
}catch (Exception e){
error(response);
return;
}
}
/**
* 返回未登录结果(返回失败消息)
* @param response
* @throws IOException
*/
private void error(HttpServletResponse response) throws IOException {
Result result = Result.error("没有token权限,请重新登录");
String error = JSONObject.toJSONString(result);
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(error);
}
3、过滤器链
比如:在我们web服务器当中,定义了两个过滤器,这两个过滤器就形成了一个过滤器链。
而这个链上的过滤器在执行的时候会一个一个的执行,会先执行第一个Filter,放行之后再来执行第二个Filter,如果执行到了最后一个过滤器放行之后,才会访问对应的web资源。
访问完web资源之后,按照我们刚才所介绍的过滤器的执行流程,还会回到过滤器当中来执行过滤器放行后的逻辑,而在执行放行后的逻辑的时候,顺序是反着的。
先要执行过滤器2放行之后的逻辑,再来执行过滤器1放行之后的逻辑,最后在给浏览器响应数据。
4、拦截器Interceptor
什么是拦截器?
- 是一种动态拦截方法调用的机制,类似于过滤器。
- 拦截器是Spring框架中提供的,用来动态拦截控制器方法的执行。
拦截器的作用
- 拦截请求,在指定方法调用前后,根据业务需要执行预先设定的代码。
在拦截器当中,我们通常也是做一些通用性的操作,比如:我们可以通过拦截器来拦截前端发起的请求,将登录校验的逻辑全部编写在拦截器当中。在校验的过程当中,如发现用户登录了(携带JWT令牌且是合法令牌),就可以直接放行,去访问spring当中的资源。如果校验时发现并没有登录或是非法令牌,就可以直接给前端响应未登录的错误信息。
1、Interceptor拦截路径
2、登录校验
流程
- 创建拦截器
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
public static final String URL = "/login";
public static final String TOKEN = "token";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取请求路径
StringBuffer requestUrl = request.getRequestURL();
String url = requestUrl.toString();
/**
* 登录界面不可以拦截,如果都拦截了,登录都没法登录了
*/
// 判断是否是登录请求
if (url.contains(URL)){
return true;
}
// 获取请求头token
String token = request.getHeader(TOKEN);
// 如果没有token,则没有权限
if (token == null){
error(response);
return false;
}
try {
// 解析token,如果成功放行,如果抛出异常,token可能过期或者被别人恶意修改,没有权限
JwtUtils.parseJWT(token);
return true;
}catch (Exception e){
error(response);
return false;
}
}
/**
* 返回未登录结果(返回失败消息)
* @param response
* @throws IOException
*/
private void error(HttpServletResponse response) throws IOException {
Result result = Result.error("没有token权限,请重新登录");
String error = JSONObject.toJSONString(result);
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(error);
}
}
- 创建WebMvcConfigurer配置类
@Configuration // 让IOC容器知道这是一个配置类,IOC容器才会扫描它
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**") // 拦截的路径
.excludePathPatterns("/login"); // 去除的路径
}
}