Vue-element-admin使用mock.js数据进行用户和权限的验证,使用该框架开发的首要事情就是用户集成改造,使用本地测试环境的用户和角色信息完成登录验证;
一、vue-element-admin的登录逻辑
在/views/login目录中index.vue是登录界面,SocialSignin.vue是第三方登录页面,auth-redirect.vue没看懂;
-
login
-
| components
-
| | SocialSignin.vue
-
| auth-redirect.vue
-
| index.vue
index.vue中的登录按钮触发handleLogin()这个methods,调用store/user.js中login这个action实现状态的更改,login()这个action再调用api/user/login的接口请求,从后端拿用户数据,如下:
-
// hadleLogin方法完成用户登录动作
-
handleLogin() {
-
this.$refs.loginForm.validate(
valid => {
-
if (valid) {
-
this.loading =
true
-
this.$store.dispatch(
'user/login',
this.loginForm)
-
.then(
() => {
-
this.$router.push({
path:
this.redirect ||
'/',
query:
this.otherQuery })
-
this.loading =
false
-
})
-
.catch(
() => {
-
this.loading =
false
-
})
-
}
else {
-
console.log(
'error submit!!')
-
return
false
-
}
-
})
-
}
-
-
//store/user.js中login这个action的代码:
-
-
const actions = {
-
// user login
-
login({ commit }, userInfo) {
-
const { username, password } = userInfo
-
return
new
Promise(
(resolve, reject) => {
-
login({
username: username.trim(),
password: password }).then(
response => {
-
const { data } = response
-
commit(
'SET_TOKEN', data.token)
-
setToken(data.token)
-
resolve()
-
}).catch(
error => {
-
reject(error)
-
})
-
})
-
}
-
}
-
-
// api/user/login的接口请求代码:
-
-
import request
from
'@/utils/request'
-
-
export
function login(data) {
-
return request({
-
url:
'/vue-element-admin/user/login',
-
method:
'post',
-
data
-
})
-
}
mock/user.js定义了/vue-element-admin/user/login这个rest请求的返回数据,定义了admin和editor这2个角色和token内容,定义了post请求(参数是loginname)的返回是一个R(code+tokens信息)嵌套json,code是请求的响应码,tokens是用户的单一状态信息(全局维护~),user/getInfo方法根据token参数得到用户和角色信息,后端需要提供相应接口;
-
-
const tokens = {
-
admin: {
-
token:
'admin-token'
-
},
-
editor: {
-
token:
'editor-token'
-
}
-
}
-
-
const users = {
-
'admin-token': {
-
roles: [
'admin'],
-
introduction:
'I am a super administrator',
-
avatar:
'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
-
name:
'Super Admin'
-
},
-
'editor-token': {
-
roles: [
'editor'],
-
introduction:
'I am an editor',
-
avatar:
'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
-
name:
'Normal Editor'
-
}
-
}
-
-
module.exports = [
-
// user login
-
{
-
url:
'/vue-element-admin/user/login',
-
type:
'post',
-
response:
config => {
-
const { username } = config.body
-
const token = tokens[username]
-
-
// mock error
-
if (!token) {
-
return {
-
code:
60204,
-
message:
'Account and password are incorrect.'
-
}
-
}
-
-
return {
-
code:
20000,
-
data: token
-
}
-
}
-
}
-
]
逻辑并没有结束,前后端分离的路由逻辑写在框架里面,可以发现在登录页已定义redirect(http://localhost:9527/#/login?redirect=%2Fdashboard),因为涉及到路由的跳转就要验证权限问题,在根目录下有个全局的permission.js负责全局的导航守卫,里面定义了首页登录成功后需要在vuex中取权限,vuex再次发起user/getInfo请求得到对应用户的权限(role),如果有权限才会跳转,没有权限报错;
-
import router
from
'./router'
-
import store
from
'./store'
-
import { Message }
from
'element-ui'
-
import NProgress
from
'nprogress'
// progress bar
-
import
'nprogress/nprogress.css'
// progress bar style
-
import { getToken }
from
'@/utils/auth'
// get token from cookie
-
import getPageTitle
from
'@/utils/get-page-title'
-
NProgress.configure({
showSpinner:
false })
// NProgress Configuration
-
const whiteList = [
'/login',
'/auth-redirect']
// no redirect whitelist
-
-
//导航守卫,在router之前,拿到to和from以及下一步动作next
-
router.beforeEach(
async(to,
from, next) => {
-
// 开启进度条
-
NProgress.start()
-
// 拿title
-
document.title = getPageTitle(to.meta.title)
-
// 判断浏览器是否持有token
-
const hasToken = getToken()
-
// 有token若是登录页就放行,有token不是登录页就验证是否有权限,有就放行,没有的话在vuex中拿,vuex发起user/getinfo请求拿权限,并记住想要跳转到的页面,也就是redirect的位置;
-
if (hasToken) {
-
if (to.path ===
'/login') {
-
next({
path:
'/' })
-
NProgress.done()
// hack: https://github.com/PanJiaChen/vue-element-admin/pull/2939
-
}
else {
-
const hasRoles = store.getters.roles && store.getters.roles.length >
0
-
if (hasRoles) {
-
next()
-
}
else {
-
try {
-
const {roles} =
await store.dispatch(
'user/getInfo')
-
const accessRoutes =
await store.dispatch(
'permission/generateRoutes', roles)
-
router.addRoutes(accessRoutes)
-
next({...to,
replace:
true})
-
}
catch (error) {
-
await store.dispatch(
'user/resetToken')
-
Message.error(error ||
'Has Error')
-
next(
`/login?redirect=${to.path}`)
-
NProgress.done()
-
}
-
}
-
}
-
}
else {
-
//没有token的话白名单就放行,不是的话就跳到登录页记录要去的页面;
-
if (whiteList.indexOf(to.path) !==
-1) {
-
// in the free login whitelist, go directly
-
next()
-
}
else {
-
next(
`/login?redirect=${to.path}`)
-
NProgress.done()
-
}
-
}
-
})
-
-
router.afterEach(
() => {
-
// 结束进度条
-
NProgress.done()
-
})
所以能够得到大致的流程,前端在登录页发起login请求,在路由守卫时候取vuex中的token(login请求后端返给前端的加密串),再次发起user/info请求从后端得到用户的权限信息,虽然token里面的加密信息附带了用户的所有信息,但是前端不做解密,登录后前端每次向后端请求数据,都会携带token头,然后后端根据token头再去验证并返回前端相应的信息;
二、后端改造--用户查询接口
本地测试首先解决跨域问题:集成中的CORS(跨域资源共享)问题
还有前端调试问题:使用Vscode在chrome中调试vue
需要完成model、mapper、service、Impi、filter(请求过滤器)、co模块
后端完成后的git地址:后端代码
1、表和model
基本的用户登录模块至少需要4个表:user表(用户基本信息)、role表(用户权限信息)、auth表(用户密码和tokens信息)、user和role的维表,在idea的h2数据库建3个相应的表,参考之前博文:基于springboot的web开发配置,使用springboot、mybatisPlus、h2、lambok、swagger系列配置完成后端接口的开发;
-
// user表
-
create
table LT_SYS_USER
-
(
-
USER_ID
VARCHAR(
20)
not
null
-
primary
key,
-
USER_CODE
VARCHAR(
64),
-
USER_NAME
VARCHAR(
100)
not
null,
-
USER_NAME_EN
VARCHAR(
100),
-
ORGAN_ID
VARCHAR(
20),
-
ORGAN_CODE
VARCHAR(
64),
-
ORGAN_NAME
VARCHAR(
100),
-
EMAIL
TEXT,
-
MOBILE
VARCHAR(
100),
-
PHONE
VARCHAR(
100),
-
SEX
INT,
-
AVATAR
TEXT,
-
SIGN
VARCHAR(
200),
-
USER_SORT
INT,
-
STATUS
INT
not
null,
-
CREATE_BY
VARCHAR(
64),
-
CREATE_DATE DATETIME,
-
UPDATE_BY
VARCHAR(
64),
-
UPDATE_DATE DATETIME,
-
REMARKS
TEXT,
-
DELETED
VARCHAR(
5)
not
null,
-
CORP_ID
VARCHAR(
64),
-
REG_DATE DATETIME
-
);
-
// role角色表
-
create
table LT_SYS_ROLE
-
(
-
ROLE_ID
VARCHAR(
20)
not
null
-
primary
key,
-
ROLE_CODE
VARCHAR(
64)
not
null,
-
ROLE_NAME
VARCHAR(
100)
not
null,
-
ROLE_TYPE
VARCHAR(
100),
-
ROLE_SORT
INT,
-
DATA_SCOPE
INT,
-
BIZ_SCOPE
VARCHAR(
255),
-
DELETED
TINYINT
not
null,
-
CREATE_BY
VARCHAR(
64)
not
null,
-
CREATE_DATE DATETIME
not
null,
-
UPDATE_BY
VARCHAR(
64),
-
UPDATE_DATE DATETIME,
-
REMARKS
TEXT,
-
CORP_ID
VARCHAR(
64)
-
);
-
-
//auth权限表
-
create
table LT_SYS_USER_AUTH
-
(
-
AUTH_ID
VARCHAR(
20)
not
null
-
primary
key,
-
USER_ID
VARCHAR(
20)
not
null,
-
LOGIN_NAME
VARCHAR(
100)
not
null,
-
PASSWD
VARCHAR(
100)
not
null,
-
TOKEN
TEXT,
-
EXPIRE_TIME DATETIME,
-
DINGTALK_OPENID LONGTEXT,
-
WELINK_OPENID
VARCHAR(
100),
-
WX_OPENID
VARCHAR(
100),
-
MOBILE_IMEI
VARCHAR(
100),
-
USER_TYPE
VARCHAR(
16),
-
PWD_SECURITY_LEVEL
INT,
-
PWD_UPDATE_DATE DATETIME,
-
PWD_UPDATE_RECORD
TEXT,
-
PWD_QUEST_UPDATE_DATE DATETIME,
-
LAST_LOGIN_IP
VARCHAR(
100),
-
LAST_LOGIN_DATE DATETIME,
-
FREEZE_DATE DATETIME,
-
FREEZE_CAUSE
VARCHAR(
200),
-
USER_WEIGHT
INT,
-
CREATE_BY
VARCHAR(
64),
-
CREATE_DATE DATETIME,
-
UPDATE_BY
VARCHAR(
64),
-
UPDATE_DATE DATETIME,
-
REMARKS
TEXT,
-
DELETED
INT
default
0,
-
STATUS
VARCHAR(
100)
-
);
-
-
// USER和role维表
-
create
table LT_SYS_USER_ROLE
-
(
-
USER_ID
VARCHAR(
20)
not
null,
-
ROLE_ID
VARCHAR(
20)
not
null,
-
primary
key (USER_ID, ROLE_ID)
-
);
-
-
comment
on
column LT_SYS_USER_ROLE.USER_ID
is
'用户id';
-
comment
on
column LT_SYS_USER_ROLE.ROLE_ID
is
'角色id';
2、mapper
3个表分别插入测试数据后,开始写后端restAPI查询接口,首先model/目录下创建userModel文件夹,model/userModel下新建3个类User.java,Role.java,Auth.java使用lambok和swagger注解提供构造器和接口使用的便利;然后在mapper/userMapper目录下创建对应的接口UserMapper和UserAuthMapper等,这里使用mybatisplus的继承basemapper再自定义一些查询;
-
// usermapper接口
-
package com.example.testspring.mapper.userMapper;
-
import com.example.testspring.model.userModel.Role;
-
import com.example.testspring.model.userModel.User;
-
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-
import com.example.testspring.model.userModel.UserRole;
-
import org.apache.ibatis.annotations.Mapper;
-
import org.apache.ibatis.annotations.Param;
-
-
import java.util.List;
-
@Mapper
-
public
interface UserMapper extends BaseMapper<User>{
-
User getByIdLazy(String userId);
-
User findById(String userId);
-
boolean deleteRoleByUserId(String userId);
-
boolean insertRolesBatch(@Param("list") List<UserRole> list);}
-
-
//RoleMapper 接口
-
package com.example.testspring.mapper.userMapper;
-
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-
import com.example.testspring.model.userModel.Role;
-
import org.apache.ibatis.annotations.Mapper;
-
-
import java.util.List;
-
@Mapper
-
public
interface RoleMapper extends BaseMapper<Role> {
-
List<Role> getListByUserId(String userId);
-
Role findById(String roleId);
-
}
-
-
//AuthMapper
-
package com.example.testspring.mapper.userMapper;
-
import com.example.testspring.model.userModel.Auth;
-
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-
import org.apache.ibatis.annotations.Mapper;
-
-
@Mapper
-
public
interface AuthMapper extends BaseMapper<Auth>{
-
// Auth getByLoginName(String loginName);
-
}
3、service和Impi
然后在service/userService目录下创建对应的实例化类UserService和AuthService等,Impi是类实现,写不下;
-
// UserService
-
package com.example.testspring.service.userService;
-
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-
import com.example.testspring.mapper.userMapper.RoleMapper;
-
import com.example.testspring.mapper.userMapper.UserMapper;
-
import com.example.testspring.model.userModel.Role;
-
import com.example.testspring.model.userModel.User;
-
import com.example.testspring.model.userModel.UserRole;
-
import com.example.testspring.service.UserService;
-
import org.springframework.beans.factory.annotation.Autowired;
-
import org.springframework.stereotype.Service;
-
import java.util.List;
-
import java.util.stream.Collectors;
-
@Service
-
public
class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
-
-
@Autowired
-
private UserMapper UserMapper;
-
@Autowired
-
private RoleMapper RoleMapper;
-
@Override
-
public User getUserAndRolesById(String userId) {
-
User ltSysUser = UserMapper.findById(userId);
-
List<Role> roles = RoleMapper.getListByUserId(userId);
-
ltSysUser.setRoles(roles);
-
return ltSysUser;
-
}
-
-
@Override
-
public boolean saveRoleIdsByUserId(String userId, List<String> roleIds) {
-
UserMapper.deleteRoleByUserId(userId);
-
List<UserRole> list = roleIds.stream().map(roleId -> {
-
return
new UserRole(roleId, userId);
-
}).collect(Collectors.toList());
-
return UserMapper.insertRolesBatch(list);
-
}
-
}
-
-
//RoseService
-
package com.example.testspring.service.userService;
-
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-
import com.example.testspring.mapper.userMapper.RoleMapper;
-
import com.example.testspring.model.userModel.Role;
-
import com.example.testspring.service.RoleService;
-
import org.springframework.beans.factory.annotation.Autowired;
-
import org.springframework.stereotype.Service;
-
-
import java.util.List;
-
@Service
-
-
public
class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
-
@Autowired
-
private RoleMapper roleMapper;
-
@Override
-
public List<Role> getListByUserId(String userId) {
-
return roleMapper.getListByUserId(userId);
-
}
-
-
}
-
//AuthService AuthService 类
-
package com.example.testspring.service.userService;
-
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
-
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
-
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
-
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-
import com.example.testspring.mapper.userMapper.AuthMapper;
-
import com.example.testspring.model.userModel.Auth;
-
import com.example.testspring.service.AuthService;
-
import com.example.testspring.utils.SecurityUtil;
-
import org.springframework.stereotype.Service;
-
-
import java.util.List;
-
@Service
-
public
class AuthServiceImpl extends ServiceImpl <AuthMapper, Auth> implements AuthService {
-
@Override
-
public Auth getByLoginName(String loginName) {
-
LambdaQueryWrapper<Auth> queryWrapper = Wrappers.<Auth>lambdaQuery()
-
.eq(Auth::getLoginName, loginName);
-
List<Auth> ltSysUserAuthList =
this.baseMapper.selectList(queryWrapper);
-
if (CollectionUtils.isEmpty(ltSysUserAuthList)) {
-
return
null;
-
}
-
else
if (ltSysUserAuthList.size() >
1) {
-
log.error(
"用户账号下包含多个用户信息,请检查数据!");
-
}
-
return ltSysUserAuthList.get(
0);
-
}
-
-
@Override
-
public boolean updatePwdByUserId(String userId, String newPwd) {
-
-
String entryptPassword = SecurityUtil.entryptPassword(newPwd);
-
LambdaUpdateWrapper<Auth> wrapper = Wrappers.<Auth>lambdaUpdate()
-
.eq(Auth::getUserId, userId).set(Auth::getPasswd, entryptPassword);
-
return
this.baseMapper.update(
null, wrapper) >
0;
-
}
-
-
}
然后在resoureces/mybatis/下创建对应的sql查询xml文件,保持跟mapper文件一一对应,可以参照github;
4、controller
最后,就是controller的restAPI,前端是/user/login这个post请求(参数是loginname和passwd,回传的是一个嵌套json(code+tokes)),因此本地要构造一个对应的API,还有user/info是get请求返回的是用户信息,以login为例,这里的controller需要tokenfilter过滤器来完成对请求的拦截
-
@PostMapping("/login")
-
@ApiImplicitParam(name = "req", value = "用户登陆信息", dataType = "LoginReq")
-
public R login(@RequestBody @NotNull LoginReq req) throws Exception {
-
String loginName = req.getLoginName();
-
String passwd = req.getPasswd();
-
Auth info = authService.getByLoginName(loginName);
-
// 验证用户状态
-
LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery().eq(User::getUserId, info.getUserId()).eq(User::getStatus,
0);
-
User user = userService.getOne(wrapper);
-
Assert.notNull(user,
"用户已禁用, 请联系管理员");
-
-
if (SecurityUtil.validatePassword(passwd, info.getPasswd())) {
-
// 封装 Token
-
String jwtToken = generateByUserInfo(info);
-
return R.ok(jwtToken);
-
}
-
else {
-
throw
new LightUapException(LightErrorCode.FORBIDDEN);
-
}
-
}
-
-
private String generateByUserInfo(Auth auth) throws Exception {
-
String userId = auth.getUserId();
-
User user = userService.getUserAndRolesById(userId);
-
if (user ==
null) {
-
throw
new LightUapException(
"用户信息不存在,请联系管理员");
-
}
-
// 查询用户账户信息
-
LightUserEntity userEntity = BeanConverter.convert(user, LightUserEntity.class);
-
userEntity.setLoginName(auth.getLoginName());
-
/**
-
* 返回前台时,只返回当前用户的角色
-
*/
-
List<Role> roles = user.getRoles();
-
if (!CollectionUtils.isEmpty(roles) && roles.size() >=
1) {
-
List<LightRoleEntity> collect = roles.stream().map(r -> r.setUsers(
null)).map(r -> LightRoleEntity.builder()
-
.roleId(r.getRoleId()).roleCode(r.getRoleCode()).roleName(r.getRoleName()).roleType(r.getRoleType())
-
.build()).collect(Collectors.toList());
-
userEntity.setRoles(collect);
-
// 默认一个个角色为主要角色
-
// userEntity.setRole(collect.get(0));
-
}
-
// 生成 token
-
// 去除用户信息中的头像防止token过大
-
userEntity.setAvatar(
"");
-
String jwtToken = LightTokenUtil.createJwtDefaultExp(userEntity);
-
return jwtToken;
-
}
5、filter拦截器
包括对于CORS的设置和对请求的拦截都可以写成配置类,这里的tokenInceptor完成了预检飞行和请求的分类,login请求放行,其他请求先拿到header后对token统一进行解析后,再进入API的controller逻辑;
-
package com.example.testspring.utils;
-
-
import com.alibaba.fastjson.JSON;
-
import com.alibaba.fastjson.JSONObject;
-
import com.example.testspring.model.userModel.LightUserEntity;
-
import com.example.testspring.req.LightException;
-
import org.apache.commons.lang3.StringUtils;
-
import org.springframework.http.server.reactive.ServerHttpRequest;
-
import org.springframework.http.server.reactive.ServerHttpResponse;
-
import org.springframework.web.cors.CorsUtils;
-
import org.springframework.web.server.ServerWebExchange;
-
import org.springframework.web.servlet.ModelAndView;
-
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
-
import org.springframework.web.util.WebUtils;
-
-
import javax.servlet.http.HttpServletRequest;
-
import javax.servlet.http.HttpServletResponse;
-
import java.io.PrintWriter;
-
import java.util.Base64;
-
import java.util.Enumeration;
-
-
public
class TokenInterceptor extends HandlerInterceptorAdapter {
-
-
/**
-
* 根据请求不同对token进行处理
-
* @param request
-
* @param response
-
* @param handler
-
* @return
-
* @throws Exception
-
*/
-
@Override
-
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
-
// 预检请求,预检飞行
-
if(CorsUtils.isCorsRequest(request) &&
"OPTIONS".equals(request.getMethod())){
-
response.setCharacterEncoding(
"UTF-8");
-
response.setContentType(
"application/json; charset=utf-8");
-
response.setStatus(
200);
-
response.setHeader(
"Access-Control-Allow-Credentials",
"true");
-
response.setHeader(
"Access-Control-Allow-Origin",
"http://localhost:9527");
-
response.setHeader(
"Access-Control-Allow-Headers",
"x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN,token,username,client");
-
response.setHeader(
"Access-Control-Expose-Headers",
"x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN,token,username,client");
-
PrintWriter out =
null ;
-
try{
-
JSONObject res =
new JSONObject();
-
res.put(
"200",
"sucess");
-
out = response.getWriter();
-
out.append(res.toString());
-
return
false;
-
}
-
catch (Exception e){
-
e.printStackTrace();
-
response.sendError(
500);
-
return
false;
-
}
-
}
-
String accessToken = request.getHeader(
"Authorization");
-
//System.out.println(request.getHeader("Authorization"));
-
//String str=request.getParameter("Authorization");
-
if (StringUtils.isNotBlank(accessToken)) {
-
LightUserEntity subject = LightTokenUtil.getSubject(accessToken);
-
request.getSession().setAttribute(
"USER_INFO", subject);
-
return
true;
-
}
-
throw
new LightException(
"TOKEN不合法,访问拒绝");
-
}
-
-
@Override
-
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
-
ModelAndView modelAndView)
throws Exception {
-
super.postHandle(request, response, handler, modelAndView);
-
}
-
}
最后,使用postman测试登录接口和info接口;
三、用户登录集成的前端改造
默认是mock数据,所以先修改.env.development中BASE_API地址为本地的后端地址;
VUE_APP_BASE_API = 'http://localhost:9090'
测试登录集成完成:

转载:https://blog.csdn.net/yezonggang/article/details/109850163