基础登录功能
LoginController
@PostMapping("/login")
Result login(@RequestBody Emp emp) {
log.info("前端,发送了一个登录请求");
Emp e = empService.login(emp);
return e!=null?Result.success():Result.error("用户" +
"名或密码错误");
}
Service
@Override
public Emp login(Emp emp) {
return empMapper.login(emp);
}
DAO
@Select("SELECT * From emp WHERE username=#{username} and" +
"password=#{password}")
Emp login(Emp emp);
利用Postman进行校验
乱输得用户名和密码.
退出后,访问相应地页面的url,直接进入了系统的页面.这时候用户名和密码就变得没有意义.
这时候就需要进行登录校验
登录校验
会话技术
令牌技术是当前企业开发中最主流的技术
会话跟踪
在请求时,后端自动给访问的浏览器生成了一个cookie并通过响应头{set-cookie}返回给浏览器。此后浏览器每次发送请求都会向浏览器自动发送这个cookie(cookie请求头)
Cookie
//设置cookie
@GetMapping("/c1")
public Result cookie(HttpServletResponse response) {
// Cookie参数也是一个键值对
response.addCookie(new Cookie("logusername","jzr10086"));
return Result.success();
}
发送请求 http://localhost:8080/c1
响应头中set-cookie头被设置为指定的参数
获取Cookie
//获取cookie
@GetMapping("/c2")
public Result cookie2(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
for(Cookie cookie : cookies) {
if(cookie.getName().equals("logusername")) {
System.out.println(cookie.getName()+cookie.getValue());
}
}
return Result.success();
}
访问c2时携带了对应的cookie,这样就建立了一次会话,当服务器停止运行或者浏览器关闭才会结束会话。
跨域:
这时候如果服务器端要设置cookie将无法使用。因为服务器端无法跨域访问到前端。
Session
设置Session
//设置session
@GetMapping("/s1")
public Result session(HttpSession session) {
log.info("Http-session-s1:{}" , session.hashCode());
session.setAttribute("logusername","jzr10086");
return Result.success();
}
访问 localhost:8080/s1
Set-Cookie是Session对象的id;
cookie储存在本地浏览器上
session储存在服务器上
获取Session
//获取session
@GetMapping("/s2")
public Result session2(HttpSession session) {
log.info("Http-session-s2:{}" , session.hashCode());
Object obj = session.getAttribute("logusername");
log.info("session2:{}" , obj.toString());
return Result.success();
}
访问s2:
两次请求的hashcode相同,说明两次请求共用了一个session
同一个浏览器发送的请求可能被负载均衡服务器转到不同的后端服务器上,导致后端服务器找不到对应的session对象
JWT令牌
缺点:需要自己实现令牌的生成检验等操作
服务端在浏览器请求之后,生成一个令牌并传送给浏览器。此后每次请求都会有服务端校验令牌的有效性。
生成校验JWT
依赖:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
@Test
void testGenJwt() {
claims.put("id","1");
claims.put("name","test");
String jwt = Jwts.builder()
.signWith(SignatureAlgorithm.HS256,"jzr666") //生成JWT令牌使用的算法和密钥
.setClaims(claims) //自定义内容(载荷)
.setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000)) //设置有效期
.compact(); //按照要求生成对应的数字令牌
System.out.println(jwt);
}
运行单元测试类,生成了对应的令牌。
可以通过公钥解密之后看到对应的JWT令牌内容,但是内容不可被更改。对应得JWT令牌不可被伪造。
·
利用对应的密钥进行解码
@Test
void praseJwt(){
Claims claim = Jwts.parser()
.setSigningKey("jzr666") //指定签名密钥
.parseClaimsJwt("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoidGVzdCIsImlkIjoiMSIsImV4cCI6MTcyNTY4Mjk1NH0.O3q9xHqoMJAf1Pm1vdmmUzADXsUsBoQLaKa-Vc3bVsQ") //注意是jws不是jwt
.getBody();
System.out.println(claim);
}
获取到了载荷中存放的内容
如果篡改令牌中的任意一个字符,在解析的时候都会报错。令牌过期之后,解析也会报错
登陆后下发令牌
通过Result响应以Json格式返回给前端
以后前端在每次请求时,都会将令牌携带到服务端(通过请求头的token)。
过滤器Filter
快速入门
实现接口并重写三个方法:
在启动类中加入ServletComponentScan,因为Filter组件并不属于SpringBoot的内容
package com.yuyu.realproject.Filter;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import java.io.IOException;
//表示拦截所有请求
@WebFilter("/*")
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("doFilter初始化方法执行了");
//调用doFilter方法放行,继续访问对应的资源
filterChain.doFilter(servletRequest, servletResponse);
}
@Override //销毁方法,只调用一次
public void destroy() {
System.out.println("拦截到了请求");
}
}
在请求之前的emps查询接口时,首先被doFilter函数拦截,然后执行filterChain的doFilter操作放行,然后开始执行查询操作
从控制台的日志中可以看到idoFilter函数的执行过程
记得要再过滤器的Controller类上加上WebFilter注解。
详解
执行流程
1.放行前逻辑
2.放行操作,进行前端请求的操作
3.执行完毕,放行后逻辑
拦截路径
过滤器链
DoFilter实际上是将当前的服务放行到下一个过滤器,如果后面没有过滤器了,则访问对应的web资源
类名在软件包中排名越靠前,越早执行。由于Java类在软件包中按照字符串的顺序进行排序,所以只需要重构类名,就能得到所需要的执行顺序。
小结:
登录校验(利用过滤器实现)
1.执行登录请求时并不需要Jwt令牌
2.Jwt令牌存在且有效就允许放行
过程:
登录检验过滤器的实现
package com.yuyu.realproject.Filter;
import com.alibaba.fastjson.JSONObject;
import com.yuyu.realproject.Pojo.Result;
import com.yuyu.realproject.utils.JwtUtils;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import java.io.IOException;
@Slf4j
@WebFilter("/*")
public class LoginCheckFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//获取请求和响应对象
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String url = request.getRequestURI().toString();
if (url.contains("login")) {
log.info("登录请求,直接放行");
filterChain.doFilter(request, response);
return;
}
String Jwt = request.getHeader("token");
if(StringUtils.hasLength(Jwt)){
log.info("请求头中没有相应地令牌");
Result error = Result.error("not login in");
//手动转换json对象,使用阿里的fastjson工具包
String notlogin = JSONObject.toJSONString(error);
//直接将登录失败的信息相应给浏览器
response.getWriter().write(notlogin);
return;
}
try{
JwtUtils.praseJwt(Jwt);
}catch (Exception e){
e.printStackTrace();
log.info("当前令牌无效");
Result error = Result.error("not login in");
//手动转换json对象,使用阿里的fastjson工具包
String notlogin = JSONObject.toJSONString(error);
//直接将登录失败的信息相应给浏览器
response.getWriter().write(notlogin);
return;
}
//如果登录成功,直接放行
filterChain.doFilter(request,response);
}
}
e.printStackTrace()是 Java 中的一种异常处理机制中的语句。
以下是关于它的详细解释:
基本含义
在 Java 编程中,当程序运行过程中发生异常(Exception)时,可以通过try - catch语句块来捕获异常。e.printStackTrace()通常放在catch块中,其中e是捕获到的异常对象。它的主要作用是打印异常的堆栈跟踪信息。
前端看到Not_login标识会自动重定向到登录页面。 也就是说这步操作是在前端实现的
拦截器Interceptor
拦截器和Fillter的区别在于是由Spring提供的?
1.进行HandlerInterceptor的实现
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
//按ctrl+o进行生成
//目标资源方法运行前运行,返回true放行,返回false不放行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println ("preHandler 运行了");
return true;
}
//目标资源方法运行后运行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println ("postHandler 运行了");
}
@Override //视图渲染完成后运行
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println ("运行完了");
}
}
2.进行拦截器的配置,添加拦截器,记得加上configuration注解
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
//添加拦截器的配置
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor (loginInterceptor).addPathPatterns("/**");
}
}
从日志中可以看到,拦截器的preHandler首先执行,return true之后,放行进入登录的Controller,执行结束后执行postController。当所有操作结束之后,这个请求由一个afterCompletion进行终结
拦截路径的配置:
不同拦截路径的配置
过滤器和拦截器的区别
登录校验:
和过滤器的逻辑基本类似。
异常处理
出现异常的时候,返回的异常并不符合开发文档中定义的错误,所以前端无法对这样的异常进行处理。
如果需要新增的部门已经存在,由于部门表中用部门名称作为主键,sql的insert语句会报错,服务器会返回500错误。但是前端不抓包的话并不会发现这样的错误。
方案:全局异常处理器
加上RestControllerAdvice注解,在捕获异常的方法中要加上ExceptionHandler注解
这个注解包含ResponseBody,会将返回的对象转化为Json格式,所以不需要自己手动转换
@RestControllerAdvice
public class exceptionHandler {
@ExceptionHandler(Exception.class)
public Result ex(Exception e){
e.printStackTrace ();
return Result.error ("服务访问失败,请练习管理员");
}
}
这样就把异常转化成了前端可以处理的Result