登录功能登录校验(重点)
1.会话技术
2.JWT令牌
全称:JSON Web Token (https:/ljwt.io/)
定义了一种简洁的、自包含的格式,用于在通信双方以json数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的。组成:
第一部分:Header(头),记录令牌类型、签名算法等。例如: {“alg”:“HS256” ,“type” :“JWT”}
第二部分: Payload(有效载荷),携带一些自定义信息、默认信息等。例如:{“id”.“1” ,“username”:“Tom”}
第三部分: Signature(签名),防止Token被篡改、确保安全性。将header、 payload,并加入指定秘钥,通过指定签名算法计算而来。
引入jwt依赖
< dependency>
< groupld>io.jsonwebtoken< /groupld>
< artifactld>jjwt< /artifactld>
< version>0.9.1< /version>
< /dependency>
测试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,"itheima") .setClaims(claims)//自定义内容(载荷) .setExpiration(new Date(System.currentTimeMillis()+3600*1000))//有效期为一个小时 .compact(); System.out.println(jwt); } //解析jwt @Test public void testParseJwt(){ Claims claims = Jwts.parser() .setSigningKey("itheima") .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoidG9tIiwiaWQiOjEsImV4cCI6MTY4MDUyODU0MH0.kb92jCCJnV9mXbn6qz_3yNzLzO5zzDTxn4Cq4ysCSd0") .getBody(); System.out.println(claims); }
JWT校验时使用的签名秘钥,必须和生成JWT令牌时使用的秘钥是配套的。
如果JWT令牌解析校验时报错,则说明JWT令牌被篡改或失效了,令牌非法。
3.过滤器Filter
放行后访问对应资源,资源访问完成后,还会回到Filter中。
回到Filter中,执行的是放行后的逻辑。
代码实现在Filter包下创建一个LoginCheckFilter 类
@Slf4j @WebFilter(urlPatterns = "/*") public class LoginCheckFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest req=(HttpServletRequest) servletRequest; HttpServletResponse resp=(HttpServletResponse) servletResponse; //1.获取请求url String url= req.getRequestURI().toString(); log.info("请求路径的url:{}",url); //2.判断请求url是否包含login 如果包含 说明是登录操作 放行 if (url.contains("login")){ log.info("这是登录操作 放行"); filterChain.doFilter(servletRequest, servletResponse); return; } //3.获取请求头中的令牌(token) String jwt = req.getHeader("token"); //4,判断令牌是否存在 如果不存在 返回错误结果(未登录) if (!StringUtils.hasLength(jwt)){ log.info("请求头token为空,返回未登录信息"); Result error=Result.error("NOT_LOGIN"); //手动转换 对象json 引入阿里巴巴fastjson依赖 //<!--fastJson--> // <dependency> // <groupId>com.alibaba</groupId> // <artifactId>fastjson</artifactId> // <version>1.2.76</version> // </dependency> String notlogin = JSONObject.toJSONString(error); resp.getWriter().write(notlogin); return; } //5.解析token 如果解析失败 返回错误结果(未登录) try { JwtUtils.parseJWT(jwt); }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("令牌合法 放行"); filterChain.doFilter(servletRequest, servletResponse); } }
4.拦截器Interceptor
实现代码新建一个LoginCheckInterceptor类
@Component @Slf4j public class LoginCheckInterceptor implements HandlerInterceptor { @Override//目标方法运行前 执行 返回true 放行 flase 不放行 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //1.获取请求url String url= request.getRequestURI().toString(); log.info("请求路径的url:{}",url); //2.判断请求url是否包含login 如果包含 说明是登录操作 放行 if (url.contains("login")){ log.info("这是登录操作 放行"); return true; } //3.获取请求头中的令牌(token) String jwt = request.getHeader("token"); //4,判断令牌是否存在 如果不存在 返回错误结果(未登录) if (!StringUtils.hasLength(jwt)){ log.info("请求头token为空,返回未登录信息"); Result error=Result.error("NOT_LOGIN"); //手动转换 对象json 阿里巴巴fastjson String notlogin = JSONObject.toJSONString(error); response.getWriter().write(notlogin); return false; } //5.解析token 如果解析失败 返回错误结果(未登录) try { JwtUtils.parseJWT(jwt); }catch (Exception e){//解析失败 e.printStackTrace(); log.info("解析令牌失败,返回未登录错误信息"); Result error=Result.error("NOT_LOGIN"); //手动转换 对象json 阿里巴巴fastjson String notlogin = JSONObject.toJSONString(error); response.getWriter().write(notlogin); return false; } //6.放行 log.info("令牌合法 放行"); return true; } @Override//目标方法执行后运行 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); } @Override//视图渲染完毕后运行 最后运行 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { HandlerInterceptor.super.afterCompletion(request, response, handler, ex); } }
新建一个配置类WebMvcConfigurer
@Configuration//配置类 public class WebMvcConfigurer implements org.springframework.web.servlet.config.annotation.WebMvcConfigurer { @Autowired private LoginCheckInterceptor loginCheckInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**"); } }
异常处理
spring事务管理
1.事务回顾
2.Spring事务管理
spring事务管理日志
logging:
level:
org.springframework.jdbc.support.JdbcTransactionManager: debug
3.事务进阶
2.propagation
REQUIRED:大部分情况下都是用该传播行为即可。
REQUIRES_NEWN:当我们不希望事务之间相互影响时,可以使用该传播行为。比如:下订单前需要记录日志,不论订单保存成功与否,都需要保证日志记录能够记录成功
AOP基础
1.AOP概述
AOP:Aspect Orie
nted Programming(面向切面编程、面向方面编程),其实就是面向特定方法编程。
动态代理是面向切面编程最主流的实现。而$pringAOP是Spring框架的高级技术,旨在管理bean对象的过程中,主要通过底层的动态代理机制,对特定的方法进行编程。
2.AOP快速入门
3.AOP核心概念
AOP进阶
书写建议
所有业务方法名在命名时尽量规范,方便切入点表达式快速匹配、如:查询类方法都是find开头,更新类方法都是update开头。
描述切入点方法通常基于接口描述,而不是直接描述实现类,增强拓展性。
在满足业务需要的前提下,尽量缩小切入点的匹配范围。
如:包名匹配尽量不使用…使用*匹配单个包。
创建实体类OperateLog
@Data @NoArgsConstructor @AllArgsConstructor public class OperateLog { private Integer id; //ID private Integer operateUser; //操作人ID private LocalDateTime operateTime; //操作时间 private String className; //操作类名 private String methodName; //操作方法名 private String methodParams; //操作方法参数 private String returnValue; //操作方法返回值 private Long costTime; //操作耗时 }
创建OperateLogMapper接口
@Mapper public interface OperateLogMapper { //插入日志数据 @Insert("insert into operate_log (operate_user, operate_time, class_name, method_name, method_params, return_value, cost_time) " + "values (#{operateUser}, #{operateTime}, #{className}, #{methodName}, #{methodParams}, #{returnValue}, #{costTime});") public void insert(OperateLog log); }
创建Log注解
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Log { }
创建LogAspect类
@Component @Aspect//切面类 @Slf4j public class LogAspect { @Autowired private HttpServletRequest request; @Autowired private OperateLogMapper operateLogMapper; @Around("@annotation(com.itheima.anno.Log)") public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable { //操作人id //获取请求头中的wjt令牌 解析令牌 String jwt = request.getHeader("token"); Claims claims = JwtUtils.parseJWT(jwt); Integer operateUser=(Integer) claims.get("id"); //操作时间 LocalDateTime operateTime=LocalDateTime.now(); //操作类名 String className=joinPoint.getTarget().getClass().getName(); //操作方法名 String methodName = joinPoint.getSignature().getName(); //操作方法参数 Object[] args = joinPoint.getArgs(); String methodParams= Arrays.toString(args); //方法返回值 //调用原始目标方法时间 long begin = System.currentTimeMillis(); Object result = joinPoint.proceed(); long end = System.currentTimeMillis(); String returnValue = JSONObject.toJSONString(result); //操作耗时 long costTime=end-begin; //调用原始目标方法 //记录操作日志 OperateLog operateLog=new OperateLog(null,operateUser,operateTime,className,methodName,methodParams,returnValue,costTime); operateLogMapper.insert(operateLog); log.info("aop操作日志{}",operateLog); return result; } }
最后在增删改方法上加上@Log注解
美好的一天,到此结束,下次继续努力!