spring boot+iview 前后端分离架构之登录的实现(二十五)

65 篇文章 5 订阅
42 篇文章 2 订阅

公众号

在这里插入图片描述
大家可以直接微信扫描上面的二维码关注我的公众号,然后回复【bg25】 里面就会给到源代码的下载地址同时会附上相应的视频教程,并定期在我的公众号上给大家推送相应的技术文章,欢迎大家关注我的公众号。

登录的实现

在第二十四章我们已经实现了我们的整个权限架构了,但是我们还没有和我们整个系统的流程结合起来,那么在本章我们将结合登录将我们整个鉴权流程给串联起来,首先在上一章我们虽然引入了redis但是我们没有将redis的配置配置上去因此我们在本章将redis的配置补充上去

补全redis配置

首先打开我们的application-dev.yml增加如下的配置:

spring:
  redis:
    host: 127.0.0.1
    password: 123456
    port: 6379
    database: 0
    jedis:
      pool:
        max-active: 8
        max-idle: 20
        min-idle: 0
      timeout: 2000

接着打开我们的主入口类BgAdminWebCoreApplication增加redis的bean的注入:

@Bean
	RedisCache redisCache(){
		return new RedisCache();
	}

补全User的序列化

由于我们使用了redis保存用户的信息因此用户信息我们需要进行序列化,因此我们需要将User.java进行序列化,序列化部分代码如下:

@Table(name = "t_user")
public class User implements Serializable {

    private static final long serialVersionUID = -5809782578282943998L;

    ......
}

实现用户的登录以及获取用户信息

根据当前登录用户的ID来获取用户所拥有的菜单数据

首先用户登录成功以后我们需要根据当前登录用户的ID来获取用户所拥有的菜单数据,因此需要在TreeDao中增加getLoginUserTree方法,代码如下:

/**
     * 功能描述:根据用户登录的ID来获取菜单节点的数据
     * @param userId 用户ID
     * @return 返回数据集合
     */
    List<Tree> getLoginUserTree(@Param("userId") String userId);

接着配置TreeDao.xml文件,新增的代码如下:

!-- 根据用户登录的ID来获取菜单节点的数据 -->
    <select id="getLoginUserTree" resultMap="BaseResultMap">
        select tt.* from t_tree tt left join  t_role_tree trr on tt.treeId = trr.treeId where trr.roleId in
        (select tur.roleId from t_user tu left join t_user_role tur on tu.userId = tur.userId  where
        tu.userId = #{userId}  )
    </select>

根据token来获取用户数据

当用户登录成功以后会获取到一个token,此时前端直接通过这个token来获取相关的用户信息,因此我们在UserDao中增加了一个根据token来获取用户信息的方法【getUserInfo】代码如下:

 /**
     * 功能描述:根据token来获取用户数据
     * @param token token的值
     * @return 返回获取的结果
     */
    User getUserInfo(@Param("token")String token);

接着配置UserDao.xml新增的代码如下:

<!-- 根据token来获取用户数据 -->
    <select id="getUserInfo" resultMap="BaseResultMap">
         select * from t_user where token = #{token}
    </select>

UserService的实现

用户登录和获取用户信息的逻辑实现的代码如下:

package com.github.bg.admin.core.service;

import com.github.bg.admin.core.auth.Auth;
import com.github.bg.admin.core.constant.SystemStaticConst;
import com.github.bg.admin.core.dao.TreeDao;
import com.github.bg.admin.core.dao.UserDao;
import com.github.bg.admin.core.entity.ReturnInfo;
import com.github.bg.admin.core.entity.Tree;
import com.github.bg.admin.core.entity.User;
import com.github.bg.admin.core.util.JsonUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;

/**
 * @author linzf
 * @since 2019/07/05
 * 类描述:用户的service类的实现
 */
@Service
@Transactional(rollbackFor = {IllegalArgumentException.class})
public class UserService {

    @Autowired
    private UserDao userDao;

    @Autowired
    private TreeDao treeDao;

    /**
     * 鉴权的实现
     */
    @Autowired
    private Auth authProvider;

    /**
     * 功能描述:实现用户登陆
     *
     * @param loginAccount  用户账号
     * @param loginPassword 用户密码
     * @return 返回登陆结果
     */
    public ReturnInfo login(String loginAccount, String loginPassword) {
        return authProvider.login(loginAccount, loginPassword);
    }

    /**
     * 功能描述:根据token来获取用户数据
     *
     * @param token token的值
     * @return 返回获取的结果
     */
    public ReturnInfo getUserInfo(String token) {
        User user = userDao.getUserInfo(token);
        if (user == null) {
            return new ReturnInfo(SystemStaticConst.FAIL, "获取账号信息错误");
        }
        user.setLoginPassword(null);
        List<Tree> treeList = treeDao.getLoginUserTree(user.getUserId());
        String[] access = new String[treeList.size()];
        for (int i = 0; i < treeList.size(); i++) {
            access[i] = treeList.get(i).getTreeCode();
        }
        Map<String, Object> result = JsonUtils.objToMap(user);
        result.put("access", access);
        return new ReturnInfo(SystemStaticConst.SUCCESS, "登陆成功", result);
    }

}

UserController的实现

用户登录和获取用户信息的响应的实现的代码如下:

package com.github.bg.admin.core.controller;

import com.github.bg.admin.core.constant.SystemStaticConst;
import com.github.bg.admin.core.entity.ReturnInfo;
import com.github.bg.admin.core.service.UserService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

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

/**
 * @author linzf
 * @since 2019/5/15
 * 类描述:用户的controller
 */
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    /**
     * 功能描述:根据token来获取用户数据
     *
     * @param token token的值
     * @return 返回获取的结果
     */
    @ApiOperation(value = "根据token来获取用户数据")
    @PostMapping("getUserInfo")
    public ReturnInfo getUserInfo(@RequestParam(name = "token") String token) {
        return userService.getUserInfo(token);
    }

    /**
     * 功能描述:实现用户登陆
     *
     * @param loginAccount  用户账号
     * @param loginPassword 用户密码
     * @return 返回登陆结果
     */
    @ApiOperation(value = "实现用户登陆")
    @PostMapping("login")
    public ReturnInfo login(@RequestParam(name = "loginAccount") String loginAccount, @RequestParam(name = "loginPassword") String loginPassword) {
        return userService.login(loginAccount, loginPassword);
    }

}

鉴权拦截器的实现

拦截器的逻辑实现

上面已经实现了我们登录和获取用户信息的逻辑过程了,但是我们并没有对我们的权限进行相应的拦截,因此我们需要在此处实现我们的拦截器,因此我们在core包底下创建一个filter包同时在该包底下创建一个过滤器【AuthInterceptor】在实现该过滤器之前我们需要实现WriteUtil工具类代码如下:

package com.github.bg.admin.core.util;


import com.github.bg.admin.core.entity.ReturnInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author linzf
 * @since 2019/6/10
 * 类描述:
 */
public class WriteUtil {

    static Logger log = LoggerFactory.getLogger(WriteUtil.class);

    /**
     * 功能描述:异常信息处理
     *
     * @param httpServletResponse
     * @param failCode            失败码
     * @param msg                 失败原因
     */
    public static void write(HttpServletResponse httpServletResponse, int failCode, String msg) {
        PrintWriter writer = null;
        try {
            /**
             * 设置允许跨域请求和返回页面的编码
             */
            httpServletResponse.setCharacterEncoding("UTF-8");
            httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
            httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
            writer = httpServletResponse.getWriter();
            String result = JsonUtils.objToJson(new ReturnInfo(failCode, msg));
            writer.print(result);
        } catch (IOException e) {
            log.error("response error", e);
        } finally {
            if (writer != null) {
                writer.close();
            }
        }
    }

}

拦截器AuthInterceptor的代码实现如下:

package com.github.bg.admin.core.filter;


import com.github.bg.admin.core.auth.ReleaseUrl;
import com.github.bg.admin.core.constant.SystemStaticConst;
import com.github.bg.admin.core.entity.User;
import com.github.bg.admin.core.util.RedisCache;
import com.github.bg.admin.core.util.StringUtils;
import com.github.bg.admin.core.util.WriteUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author linzf
 * @since 2019-05-14
 * 类描述:拦截器实现权限的拦截
 */
public class AuthInterceptor implements HandlerInterceptor {

    private Logger log = LoggerFactory.getLogger(AuthInterceptor.class);

    /**
     * 操作redis的实现类
     */
    @Autowired
    private RedisCache redisCache;
    /**
     * 获取放行的权限的接口
     */
    @Autowired
    private ReleaseUrl releaseUrl;


    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
        String token = httpServletRequest.getHeader("x-access-token");
        String actionUrl = httpServletRequest.getServletPath();
        if (SystemStaticConst.ACTION_TYPE_OPTIONS.equals(httpServletRequest.getMethod())) {
            return true;
        }

        /**
         * 判断当前的响应地址是否可以放行
         */
        String releasePath = releaseUrl.getReleaseUrl();
        if (releasePath.indexOf(actionUrl) == -1) {
            if (token == null || "".equals(token) || "null".equals(token)) {
                WriteUtil.write(httpServletResponse, SystemStaticConst.NOT_LOGIN, "用户未登录");
                return false;
            }
            /**
             * 判断当前的用户是否有权限访问当前节点
             */
            log.debug("token:{}", token);
            String powerPath = StringUtils.getObjStr(redisCache.getString(token));
            if ("".equals(powerPath)) {
                WriteUtil.write(httpServletResponse, SystemStaticConst.AUTH_TOKEN_EXPIRE, "token过期");
                return false;
            }
            if (powerPath.indexOf(actionUrl) == -1) {
                WriteUtil.write(httpServletResponse, SystemStaticConst.AUTH_FAIL, "用户无权限,请联系系统管理员");
                return false;
            }
            /**
             * 根据token来获取当前登录的用户的信息
             */
            User user = redisCache.getObject(token + "_USER", User.class);
            if (user == null) {
                WriteUtil.write(httpServletResponse, SystemStaticConst.NOT_LOGIN, "用户未登录");
                return false;
            }
        }
        return true;
    }
}

配置拦截器

上面我们虽然编写好了拦截器可是拦截器并没有生效,因此我们需要配置拦截器让拦截器生效因此我们在config目录底下创建一个InterceptorConfig代码如下:

package com.github.bg.admin.core.config;

import com.github.bg.admin.core.filter.AuthInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author linzf
 * @since 2019-06-04
 * 类描述:设置拦截器
 */
@Component
public class InterceptorConfig implements WebMvcConfigurer {

    /**
     * 初始化鉴权拦截器
     */
    private AuthInterceptor authInterceptor = new AuthInterceptor();

    /**
     * 注入权限拦截器bean
     */
    @Bean
    AuthInterceptor authInterceptor(){
        return  authInterceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 先加的拦截器先执行
        registry.addInterceptor(authInterceptor()).addPathPatterns("/**");
    }
}

前端工程的改造

由于我们之前在编写前端的时候一直采用的是mock数据的形式来进行调用,但是我们现在需要改造成前后端交互了,因此我们需要改造lib底下的【axios.js】、api底下的【base.js】以及config底下的【env.js】改造完成以后的代码如下:

axios.js的改造

import Axios from 'axios';
import {Message} from 'iview';
import config from '../config/run.config';
import userStore from '../store/module/user';
import qs from 'qs';

class httpRequest {
  constructor() {
    this.options = {
      method: '',
      url: ''
    };
    // 存储请求队列
    this.queue = [];
  }


  // 销毁请求实例
  destroy(url) {
    delete this.queue[url];
    const queue = Object.keys(this.queue);
    return queue.length;
  }

  // 请求拦截
  interceptors(instance, url) {
    // 添加请求拦截器
    instance.interceptors.request.use(config => {
      if (userStore.state.token != '') {
        config.headers['x-access-token'] = userStore.state.token;
      }
      return config
    }, error => {
      // 对请求错误做些什么
      return Promise.reject(error);
    })

    // 添加响应拦截器
    instance.interceptors.response.use((res) => {
      let {data} = res;
      // 表示当前的操作的用户还没有登录,因此重新跳转到登录页面
      if (data.code == 401) {
        userStore.state.token = '';
        window.location.href = window.location.pathname + '#/login';
        Message.error('未登录,或登录失效,请登录');
      } else if (data.code == 409) {
        // token过期重新刷新token的值
        this.refreshToken();
      }
      return data;
    }, (error) => {
      Message.error('服务内部错误');
      // 对响应错误做点什么
      return Promise.reject(error);
    })
  }

  // 创建实例
  create() {
    let conf = {
      baseURL: config.runConfig.baseUrl,
      timeout: 5000,
      headers: {
        'Access-Control-Allow-Origin': '*',
        'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
        'X-URL-PATH': location.pathname
      }
    };
    return Axios.create(conf);
  }

  refreshToken() {
    Axios.post(config.runConfig.refreshTokenUrl, qs.stringify({refreshToken: userStore.state.refreshToken}))
      .then(response => {
        let {data} = response;
        // 表示可以重新发起请求
        if (data.code == 200) {
          userStore.state.token = data.obj.token;
          userStore.state.refreshToken = data.obj.refreshToken;
          localStorage.setItem('token', data.obj.token);
          localStorage.setItem('refreshToken', data.obj.refreshToken);
        } else {
          userStore.state.token = '';
          userStore.state.refreshToken = '';
          localStorage.setItem('token', '');
          localStorage.setItem('refreshToken', '');
          window.location.href = window.location.pathname + '#/login';
          Message.error('登录失效,请登录');
        }
      })
      .catch((error) => {
        console.log('error=>' + error)
      })
  }

  // 请求实例
  request(options) {
    let instance = this.create();
    this.interceptors(instance, options.url);
    options = Object.assign({}, options);
    this.queue[options.url] = instance;
    return instance(options);
  }
}

export default httpRequest;

base.js的改造

import axios from '../lib/api.request';
import qs from 'qs';
import config from '../config/run.config';

let Axios
if (config.runConfig.mock) {
  Axios = require('axios');
  Axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'
}

// 是否需要刷新token的标志,防止在刷新token的时候,发送的请求到后端导致最后退出系统
let isRefresh = false;

export function fetch(url, params = {}) {
  if (config.runConfig.mock) {
    return new Promise((resolve, reject) => {
      Axios.post(url, params)
        .then(response => {
          resolve(response.data);
        })
        .catch((error) => {
          reject(error);
        })
    })
  } else {
    return new Promise((resolve, reject) => {
      // 如果是在刷新token,那么这时候其他请求将会被500毫秒以后再次触发
      if(isRefresh){
        setTimeout(()=>{
          fetch(url,params);
        },1000)
      }else{
        axiosPost(url,params,resolve)
      }
    });
  }
}

// 递归调用,保证在token过期刷新token的时候可以实现请求的二次发送
function axiosPost(url,params,resolve){
  axios.request({
    url: url,
    data: qs.stringify(params),
    method: 'post'
  }).then(res => {
    if (res.code == 409) {
      isRefresh = true;
      setTimeout(() => {
        isRefresh = false;
        axiosPost(url,params,resolve)
      }, 1000)
    }else{
      resolve(res);
    }
  })
}

env.js的改造

export default "development";

启动程序查看效果

那么到此处我们就已经完成了登录的整个改造了,我们需要分别启动相应的程序来验证我们的效果了,首先我们需要启动我们的redis数据库、接着启动我们的后端工程、最后启动我们的前端工程,然后直接访问http://127.0.0.1:8080/#/login
在这里插入图片描述
接着我们输入账号admin密码123456,我们会看到如下的两个请求,同时会跳转到首页。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
但是我们现在点击上面的任何一个菜单节点都会报错,这是正常的因为现在我们所有的请求都转到了后端,因此我们需要在下面的章节开始编写相应的接口。
上一篇文章地址:spring boot+iview 前后端分离架构之后端鉴权体系的实现(二十四)
下一篇文章地址:spring boot+iview 前后端分离架构之数据字典的实现(二十六)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笨_鸟_不_会_飞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值