Vue and Vue-Element-Admin (4): User login integration of vue-element-admin

Vue-element-admin uses mock.js data to verify users and permissions. The first thing to develop using this framework is user integration and transformation, and use the user and role information of the local test environment to complete login verification;

 One, the login logic of vue-element-admin

In the /views/login directory, index.vue is the login interface, SocialSignin.vue is the third-party login page, auth-redirect.vue did not understand;

login
| components
| | SocialSignin.vue
| auth-redirect.vue
| index.vue

 The login button in index.vue triggers the handleLogin() method, which calls the login action in store/user.js to change the state, and the login() action calls the api/user/login interface request to get the user from the backend Data are as follows:

// 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 defines the return data of the rest request of /vue-element-admin/user/login, defines the two roles of admin and editor and the content of the token, and defines the return of the post request (the parameter is loginname) is one R (code+tokens information) nested json, code is the response code of the request, tokens is the user's single state information (global maintenance~), the user/getInfo method obtains user and role information according to the token parameters, and the backend needs to provide the corresponding interface ;


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
      }
    }
  }
]

 The logic is not over. The routing logic of the front and back ends is written in the framework. You can find that the login page has been defined redirect ( http://localhost:9527/#/login?redirect=%2Fdashboard ), because it involves routing jumps It is necessary to verify permissions. There is a global permission.js in the root directory that is responsible for the global navigation guard, which defines that the home page needs to be accessed after successful login. Vuex again initiates a user/getInfo request to obtain the permissions of the corresponding user ( role), jump only if there is permission, and report an error without permission;

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()
})

So you can get the general process. The front end initiates a login request on the login page, takes the token in vuex (the encrypted string returned by the login request back end to the front end) during the routing guard, and initiates the user/info request again to obtain the user's permission from the back end Information. Although the encrypted information in the token is accompanied by all the user's information, the front-end does not decrypt it. After logging in, the front-end will carry the token header every time it requests data from the back-end, and then the back-end will verify according to the token header and return to the front-end accordingly. Information; 

2. Back-end transformation-user query interface

Local testing first solves cross-domain issues: CORS (Cross-domain Resource Sharing) issues in integration

There are also front-end debugging issues: use Vscode to debug vue in chrome

Need to complete model, mapper, service, Impi, filter (request filter), co module

Git address after the backend is completed: backend code

1. Table and model

The basic user login module requires at least 4 tables: user table (basic user information), role table (user privilege information), auth table (user password and tokens information), user and role dimension tables, which are built in the h2 database of idea 3 corresponding tables, refer to the previous blog post: web development configuration based on springboot , use springboot, mybatisPlus, h2, lambok, swagger series configuration to complete the development of the back-end interface;

// 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

After inserting the test data into the three tables, start to write the back-end restAPI query interface. First, create a userModel folder under the model/ directory, and create three new classes User.java, Role.java, and Auth.java using lambok and swagger under model/userModel. Annotations provide convenience for the use of constructors and interfaces; then create corresponding interfaces UserMapper and UserAuthMapper in the mapper/userMapper directory, and use mybatisplus inherited basemapper to customize some queries;

// 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 and Impi

Then create the corresponding instantiation classes UserService and AuthService in the service/userService directory. Impi is a class implementation and cannot be written;

// 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;
    }

}

Then create the corresponding sql query xml file under resoureces/mybatis/, keep a one-to-one correspondence with the mapper file, you can refer to github;

4、controller

Finally, it is the restAPI of the controller. The front end is the post request of /user/login (the parameters are loginname and passwd, and the return is a nested json (code+tokes)), so a corresponding API must be constructed locally, as well as user /info is the user information returned by the get request. Take login as an example. The controller here needs a tokenfilter to complete the interception of the request.

    @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 interceptor 

Including the setting of CORS and the interception of requests can be written as configuration classes. The tokenInceptor here has completed the pre-check flight and request classification, login request is released, and other requests are first obtained by the header and then the token is uniformly parsed before entering the API. The controller logic;

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);
    }
}

Finally, use postman to test the login interface and info interface;

Third, the front-end transformation of user login integration

The default is mock data, so first modify the BASE_API address in .env.development to the local backend address;

VUE_APP_BASE_API = 'http://localhost:9090'

 Test login integration is complete:

Guess you like

Origin blog.csdn.net/yezonggang/article/details/109850163