8、幽络源微服务项目实战:前端登录跨域同源策略处理+axios封装+权限的递归查询增删改+鉴权测试

前言

在前面的教程中,我们将后端的登录、认证、权限校验都已学会并经过测试,本章我们来让前端真正的对接上后端的登录接口。

下载Axios并封装API工具

在前端项目中,对于这种前后端分离的项目,api的请求主要是用axios库,执行如下命令即可

bash 复制代码
npm install axios

然后我们在src目录下创建一个utils目录,再创建一个名为api.js的文件,加入如下代码,作为axios的封装工具

javascript 复制代码
import axios from 'axios'

// 创建axios实例
const api = axios.create({
    baseURL: 'http://localhost:8001', // 显式指定后端地址
    timeout: 10000
})

/**
 * 通用请求方法(支持.then()链式调用)
 * @param {object} config - 请求配置
 * @returns {Promise} 返回原始Promise对象
 */
export const request = ({ method, url, data, params }) => {
    return api({ method, url, data, params })
        .then(response => response.data) // 解构出data字段
        .catch(error => {
            console.error(`API请求失败 [${method?.toUpperCase()}] ${url}:`, error)
            return Promise.reject(error) // 继续抛出错误
        })
}

// 快捷方法(均返回Promise)
export const get = (url, params) => request({ method: 'get', url, params })
export const post = (url, data) => request({ method: 'post', url, data })
export const put = (url, data) => request({ method: 'put', url, data })
export const del = (url, params) => request({ method: 'delete', url, params })
/**
 * //使用示例:发送登录请求
 * import { post } from '@/utils/api'
 *
 *       post("/auth/login",{
 *         "username":this.form.username,
 *         "password":this.form.password
 *       }).then(res=>{
 *           console.log(res)
 *       })
 *
 * **/

如图

修改登录真实请求

现在我们来到LoginView,将之前模拟登录的代码改为真正的登录请求,如图,幽络源这里将vue2中登录页面的登录方法修改为了利用我们的api.js进行了post请求"/auth/login"接口

测试登录出现跨域

我们启动前端和后端项目,随便填写用户名和密码,此时我们点击登录按钮,便会看到如下界面

打开浏览器控制台,可以看到这里出现了一条信息,如下:

Access to XMLHttpRequest at 'http://localhost:8001/auth/login' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

这条提示表示 我们本地8080请求本地8001的接口被CORS 策略阻止了,CORS是HTTP的资源共享机制,而浏览器由于有同源策略的安全措施使不同源的请求被阻止。

对于这种跨域问题, 前端或者后端都可以通过配置来解决,幽络源这里提供最简单的方法,即后端处理跨域。

在后端项目的tenant模块中创建config包,作为配置包,然后创建CorsConfig类,具体代码如下

java 复制代码
package com.tenant.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

//允许跨域配置
@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedHeaders("*")
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowCredentials(false)
                .maxAge(3600);
    }
}

加上这段配置,跨域的问题就处理了。但是:由于我们这里用到了SpringSecurity,这里还有个配置需要修改,那就是我们对http的配置,修改为如下:对比之前的配置可以发现,这里我们将cors不在用disable()去禁止了,而是直接开启。

java 复制代码
@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and()
                .csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers("/auth/login").permitAll()
                .anyRequest().authenticated();
        //添加我们自己的过滤器,在登录前就加上token过滤器
        http.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

另外幽络源这里有个提醒,若是要允许前端Cookie、Authorization登浏览器自动管理的这种凭据的话,那这里后端的配置中就需要将 allowCredentials 设置为 true,并且 allowedOrigins 也必须得指定明确的域名!

处理跨域后再次测试

如图,当我们加上跨域处理的配置后,重启项目测试登录,先测试错误的用户名和密码,可以看到成功的响应了提示信息:用户名或密码错误。

然后我们再传入正确的用户名和密码,如图可以看到后端成功的响应了登录成功信息,并且返回了JWT给前端

思考后续的接口访问

按照之前我们的登录与请求测试接口的流程,现在我们前段拿到了JWT,那么还需将JWT添加到前端请求头的"token"中去请求其他接口,否则会被后端我们自定义的的Token过滤器拦截掉。

再次封装axios请求

由于后续我们每次请求都需要为请求头加上"token",不可能每次都手动去添加,因此这里幽络源需要修改axios的封装方式,并添加上请求和响应的拦截器,让每次前端请求时,都自动添加上"token"。

将我们的 api.js 修改为如下,注意:这里保留了上面的快捷封装方式,可以不使用,但还是值得去学习的。

java 复制代码
import axios from 'axios'

// 创建axios实例
const api = axios.create({
    baseURL: 'http://localhost:8001',
    timeout: 10000
})

// 请求拦截器:自动添加Token
api.interceptors.request.use(config => {
    const token = localStorage.getItem('jwt_token')
    if (token) {
        config.headers['token'] = token // 统一使用'token'作为键名
    }
    return config
}, error => {
    return Promise.reject(error)
})

// 响应拦截器
api.interceptors.response.use(
    response => response.data, // 直接返回data
    error => {
        if (error.response?.status === 401) {
            localStorage.removeItem('jwt_token')
            window.location.href = '/login'
        }
        return Promise.reject(error)
    }
)
export default api //直接导出api实例

/**
 * 封装通用请求方法
 * @param {object} config - { method, url, data, params }
 * @returns {Promise} 返回处理后的Promise
 */
export const service = ({ method, url, data, params }) => {
    return api({ method, url, data, params }) // 直接返回api实例的调用
}
// 快捷方法(可选)
export const get = (url, params) => service({ method: 'get', url, params })
export const post = (url, data) => service({ method: 'post', url, data })
export const put = (url, data) => service({ method: 'put', url, data })
export const del = (url, params) => service({ method: 'delete', url, params })
/**
 * //快捷方法使用示例:发送登录请求
 * import { post } from '@/utils/api'
 *
 *       post("/auth/login",{
 *         "username":this.form.username,
 *         "password":this.form.password
 *       }).then(res=>{
 *           console.log(res)
 *       })
 * **/

封装登录请求

修改了axios请求封装后,我们来将登录请求也封装了,后续接口会有很多,因此幽络源这里会将请求的封装统一放在src下的名为api的目录,然后再在api的目录下创建名为tenant的目录,再创建auth.js对应后端的auth接口,内容与图如下

javascript 复制代码
import request from '@/utils/api.js';

export function login(data) {
    return request({
        url: '/auth/login',
        method: 'post',
        data
    });
}

继续修改登录逻辑

上面我们因为考虑到请求头需要携带token,加上了请求拦截器,进而进一步封装了axios,并封装了登录请求,这里登录的逻辑也得修改,代码与图如下

javascript 复制代码
handleLogin() {
      // 验证所有字段
      this.validate('username')
      this.validate('password')

      // 如果有错误,不提交
      if (this.errors.username || this.errors.password) {
        return
      }

      this.errorMessage = ''

      //发送登录请求
      login({
        "username":this.form.username,
        "password":this.form.password
      }).then(res=>{
          if (res.code===0){ //登录失败的处理
            this.$message({
              message: res.message,
              type: 'warning'
            });
          }else{ //登录成功的处理
            localStorage.setItem('jwt_token', res.data);
            this.$message.success('登录成功');
            this.$router.push("/")
          }
      })

    },

再次测试登录功能

如图,分别测试错误的账号密码和正确的账号密码,结果如下,非常成功

权限的增删查改完善

权限的增删查改就比较简单了,直接新疆controller,写对应的service,调用mybatisplus自带的方法即可,需要注意的是权限的查询在前端是有父子分级的,这个逻辑幽络源这里准备在后端做,当然前端也能做,如图

后端权限递归查询修改与前端权限查询完善

这里权限的树状查询就像是父子菜单一样,后期可能不止有两层级,因此这里得递归的去操作,首先修改Permission类为其添加子节点,如下

java 复制代码
@Data
@TableName("tbs_permission")
public class Permission {
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    @TableField("name")
    private String name;

    @TableField("code")
    private String code;

    @TableField("type")
    private Integer type;

    @TableField("parent_id")
    private Long parentId;

    @TableField("path")
    private String path;

    @TableField("icon")
    private String icon;

    @TableField("sort")
    private Integer sort;

    @TableField(value = "create_time", fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;

    @TableField(exist = false)
    private List<Permission> children; // 子节点
}

权限的查询修改为递归构建,如下

java 复制代码
@Override
public ResResult<?> select(String name, String code, Integer type) {
    LambdaQueryWrapper<Permission> eq = new LambdaQueryWrapper<Permission>()
            .orderByAsc(Permission::getSort)
            .like(StringUtils.isNotBlank(name), Permission::getName, name)
            .like(StringUtils.isNotBlank(code), Permission::getCode, code)
            .eq(type != null, Permission::getType, type);
    List<Permission> permissions = permissionMapper.selectList(eq);
    List<Permission> permissionsTree = buildTree(permissions, null);
    return ResResult.success(permissionsTree,"权限查询成功");
}
// 递归构建树
private List<Permission> buildTree(List<Permission> allPermissions, Long parentId) {
    return allPermissions.stream()
            .filter(p -> Objects.equals(p.getParentId(), parentId))
            .peek(p -> p.setChildren(buildTree(allPermissions, p.getId())))
            .collect(Collectors.toList());
}

前端项目新建permission.js,封装查询请求,并应用到查询界面,代码与图如下

permission.js

javascript 复制代码
import request from '@/utils/api.js';

export function selectPermission(name,code,type) {
    return request({
        url: '/permission/select?name='+name+'&code='+code+'&type='+type,
        method: 'get'
    });
}

PermissionView.vue

javascript 复制代码
//获取并且处理权限
getAndHandlePermission(){
  selectPermission(this.listQuery.name,this.listQuery.code,this.listQuery.type).then(res=>{
    if (res.code===1){ //请求成功,处理权限分级并加入到list
      this.list=res.data;

    }else { //请求失败的处理
      this.$message({
        message: res.message,
        type: 'warning'
      });
    }
  })
}

除此之外,我们还需对权限相关接口加上鉴权注解,一般来说,只允许超管对其进行增删查改,你可以通过角色编码去鉴权,也可以通过权限编码去鉴权,这里幽络源就直接用我们先前所讲的@PreAuthorize("hasAuthority(")")做鉴权即可,如图

测试权限的查询与鉴权成败

如图,幽络源登录超管的账号来到系统管理中的权限管理处,可以看到这里直接就加载了树状的权限列表

幽络源继续使用 非超管用户登录 系统,然后来到权限管理界面,结果如下显示了403拒绝访问接口,因此我们的鉴权也是成功的,当然,直接给出这样一个红色界面对用户来说是看不懂的不合理的,后续幽络源会继续整合动态菜单以及处理403的响应。

源码

https://pan.baidu.com/s/1Pxb5OEL0feh_Hg12Ye16tg?pwd=3a15

结语

如上为幽络源的8、幽络源微服务项目实战:vue2对接登录接口与跨域同源策略的处理+前端axios封装+权限的递归查询增删改+鉴权测试教程

相关推荐
光影少年12 小时前
Promise.all实现其中有一个接口失败其他结果正常返回,如何实现?
前端·promise·掘金·金石计划
DokiDoki之父12 小时前
web核心—Tomcat的下载/配置/mavenweb项目创建/通过mavenweb插件运行web项目
java·前端·tomcat
我的div丢了肿么办12 小时前
echarts4升级为echarts5的常见问题
前端·javascript·echarts
自由的疯12 小时前
java 各个JSONObject有什么不同
java·后端·架构
ZoeLandia12 小时前
Vue 项目 JSON 在线编辑、校验如何选?
前端·vue.js·json
派大星_分星12 小时前
nuxt fetch $fetch useFetch 等使用方式区别
前端
快手技术12 小时前
兼顾效率和性能!快手低代码平台在大型活动中的技术实践!
前端
WebInfra13 小时前
📱开源 AI 工具驱动 iOS 自动化 、接入全新 Qwen 模型 - Midscene v0.29 发布
前端·ios·测试
乖女子@@@13 小时前
React-props的children属性
前端·javascript·react.js
OEC小胖胖13 小时前
组件化思维(下):表单与交互组件,倾听用户的心声
前端·微信小程序·小程序·微信开放平台