SpringBoot AOP 基于前后端分离的登录拦截设计

一、前言

前后端分离开发是目前软件开发的主流,大大提高了开发效率
但也带来了很多不方便之处。

1、优点:
① 传统全栈开发的 MVC 模式将不适合,后台采取 MVP 面向接口编程,耦合度大大降低

2、缺点:
① 跨域问题不胜其扰

3、原则:
① 一个合格的后台应全力负责业务逻辑
② 前端不要参与过多的业务逻辑,专注于网页的视觉建设
否则项目耦合度高、网站的安全性低

二、思路

学过计算机网络的,都知道,每个人的电脑的 ip 地址是全球唯一的,
我们可以利用 ip 地址在数据库中的记录中的 查询、增加、删除,来进行 拦截、登录、注销
使用 AOP 编程,对指定的方法进行登录拦截

三、代码

下面以个人最近开发的一个校园外卖网站的后台代码为例(待续),抽取分享相关的代码

1、代码结构

SpringBoot AOP 基于前后端分离的登录拦截设计_第1张图片
SpringBoot AOP 基于前后端分离的登录拦截设计_第2张图片

2、登录实体 Login.java

package com.cun.entity;

import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

import io.swagger.annotations.ApiModelProperty;

@Entity
@Table(name = "t_login")
public class Login {

    @Id
    @GeneratedValue
    @ApiModelProperty(value = "主键id")
    private Integer id;

    @Column(length = 100)
    private String addressIp; //登录后的主机 ip

    private Date date; //登陆时间

    private Integer userId;  //关联登录的用户id

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getAddressIp() {
        return addressIp;
    }

    public void setAddressIp(String addressIp) {
        this.addressIp = addressIp;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

}

3、Dao层 LoginDao.java

package com.cun.dao;


import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;

import com.cun.entity.Login;

public interface LoginDao extends JpaRepository{

    // 注意:delete 操作,dao 接口层加 @Modifying ,在调用层使用 @Transactional
    @Modifying
    @Query(value = "delete from t_login where address_ip=?1", nativeQuery = true)
    void deleteLogin(String addressIp);

    @Query(value = "select * from t_login where address_ip=?1 order by date desc limit 1", nativeQuery = true)
    Login getLoginByIp(String addressIp);

}

4、安全控制 MySecurity.java

构思最为长久的地方

package com.cun.security;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.cun.dao.LoginDao;
import com.cun.entity.Login;
import com.cun.util.Json;

@Component
@Aspect
public class MySecurity {

    @Autowired
    private LoginDao loginDao;

    private final Logger logger = LoggerFactory.getLogger(MySecurity.class);

    @Pointcut("execution(public * com.cun.controller.admin.*.*(..))")
    public void log() {
    }

    @Before("log()")
    public void deoBefore(JoinPoint joinPoint) {
        logger.info("方法执行前...");

        ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = sra.getRequest();
        logger.info("url:" + request.getRequestURI());
        logger.info("ip:" + request.getRemoteHost());
        logger.info("method:" + request.getMethod());
        logger.info("class_method:" + joinPoint.getSignature().getDeclaringTypeName() + "."
                + joinPoint.getSignature().getName());
        logger.info("args:" + joinPoint.getArgs());
    }

    @After("log()")
    public void doAfter(JoinPoint joinPoint) {
        logger.info("方法执行后...");
    }

    @AfterReturning(returning = "result", pointcut = "log()")
    public void doAfterReturning(Object result) {
        logger.info("执行返回值:" + result);
    }

    @Around("log()")
    public Object trackInfo(ProceedingJoinPoint pjp) throws Throwable {

        ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = sra.getRequest();
        String remoteHost = request.getRemoteHost();
        Login loginByIp = loginDao.getLoginByIp(remoteHost);

        if (loginByIp == null) {
            logger.info("-------------没有登录-------------");
            return Json.fail();
        } else {
            logger.info("-------------登录通过-------------");
        }
        return pjp.proceed();
    }

}

5、通用返回值工具类简单设计

package com.cun.util;

import java.util.HashMap;
import java.util.Map;

public class Json {

    public static Map<String, Object> success(Object data) {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("code", 200);
        map.put("msg", "ok");
        map.put("data", data);
        return map;
    }

    public static Map<String, Object> fail() {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("code", 400);
        map.put("msg", "error");
        return map;
    }

}

四、最后

其实这种做法是不安全的,只能拦截没有编程知识的小白群众,用户可以使用代理 ip 等技术,获取他人的 ip 去登录

参考文章:
1、这篇文章清晰了 AOP 登录拦截设计的思路
spring AOP 注解实现登录权限拦截
2、这篇文章清晰了 SpringBoot 中的 AOP 基础知识
Spring boot中使用aop详解
3、这篇文章的实验,大大减省了笔者测试SpringBoot AOP 的时间
SpringBoot中利用AOP实现拦截器效果

你可能感兴趣的:(SpringBoot AOP 基于前后端分离的登录拦截设计)