前言

该demo采用的是前后端分离的开发模式,会简单的介绍vue3的简单使用,在后端进行跨域的配置,前端采用axios对后端发起请求,将原生的axios请求二次封装为工具类,减少代码量。写这篇文章主要是给别人做一个参考,因为最开始前后端分离的登陆注册都写的昏昏沉沉。


一、vue前端项目创建及依赖

对于vue3项目的创建直接使用自带的vue ui(个人喜欢这个可视化界面)
在这里插入图片描述
直接使用仪表盘创建项目,并加入依赖,对于前端的话我就简单的用文本框来装饰一下即可(自己扩展)
在这里插入图片描述
创建完成之后,加入需要的依赖,在这里,我加入了如下依赖:
在这里插入图片描述
另外,在插件这个选项当中,加入vuex以及vue-router两个插件
在这里插入图片描述
然后便可以开始利用vs code(个人习惯)编写前端的代码了。

二、前端代码编写

1.依赖配置以及页面创建

首先进行main.js的配置,代码如下:

import { createApp } from 'vue'
import App from './App.vue'
import store from './store'
import router from './router'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import axios from 'axios'
import VueAxios from 'vue-axios'
const app = createApp(App)
app.use(VueAxios,axios).use(ElementPlus).use(router).use(store).mount('#app')
// 引入element-plus自带的图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
    app.component(key, component)
}

2.创建登录界面(项目自带的界面可以自己噶掉)

创建登录界面,并进行路由配置

{
    path: '/login',
    name: 'login',
    component: () => import('@/views/loginView.vue')
},
{
    path: '/index',
    name: '首页',
    component: () => import('@/views/indexView.vue')
}

编写个现在比较流行的界面,代码直接贴这里了,代码直接放上去会有一定的错误,接下来列举报错的地方:

  1. 左上角的logo,需要自己找一张logo图片,背景图片是我直接从网上找的。
  2. 向后端请求的接口需要更改,在script的doLogin函数中。
<template>
  <div class="root">
    <div class="login_wrapper">
      <div class="login_nav">
        <div style="float: left">
          <img class="login_logo" src="../image/logo.png" />
        </div>
        <div style="display: flex; align-items: center; float: left">
          <span>XXX管理系统</span>
        </div>
      </div>
      <div class="login_block">
        <div class="login_content">
          <img
            class="login_backimg"
            src="http://cdn-oss.delicloud.com/front/oa/static/media/img_chahua.2002d69f.png"
            alt="背景图"
          />
          <div class="login_card">
            <el-card class="box-card">
              <template #header>
                <div class="card-header">
                  <span>登录</span>
                </div>
              </template>
              <el-input
                style="margin-top: 20px; width: 85%"
                v-model="sysUser.userName"
                placeholder="请输入账号"
                prefix-icon="User"
              /><br />
              <el-input
                style="margin-top: 30px; width: 85%"
                v-model="sysUser.userPassword"
                type="password"
                prefix-icon="Lock"
                placeholder="请输入密码"
              /><br />
              <el-button
                style="margin-top: 30px"
                type="primary"
                @click="doLogin"
                >登录</el-button
              >
            </el-card>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref } from "vue";
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import axios from "axios";
let router = useRouter();

let sysUser = ref({
  userName: "",
  userPassword: "",
});

let doLogin = () => {
  //表单验证
  if (sysUser.value.userName == "") {
    ElMessage({
      showClose: true,
      message: "用户名不能为空",
      type: "error",
    });
    return;
  }
  if (sysUser.value.userPassword == "") {
    ElMessage({
      showClose: true,
      message: "密码不能为空",
      type: "error",
    });
    return;
  }
  axios.post("http://localhost:8082/crm/user/selectSysUserByNameByPass", sysUser.value)
    .then((res) => {
      let sysUser = res.data.sysUser;
      let token = res.data.token;
      if (res.data.success == "false") {
        ElMessage({
          showClose: true,
          message: res.data.message,
          type: "error",
        });
      } else {
        //JSON.stringify(sysUser) 将对象转变成字符串
        sessionStorage.setItem("sysUser", JSON.stringify(sysUser));
        sessionStorage.setItem("jwt_token", token);
        router.push("/index");
        ElMessage({
          showClose: true,
          message: "登录成功,欢迎回来:" + sysUser.userName,
          type: "success",
        });
      }
    })
    .catch((error) => {
      console.log(error);
    });
};
</script>

<style scoped>
.root {
  height: 100%;
}
.login_wrapper {
  width: 100%;
  height: 80%;
  padding-bottom: 100px;
}
.login_nav {
  height: 56px;
  background: #fff;
  box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.04), 0 2px 4px 0 rgba(0, 0, 0, 0.04);
}
.login_nav span {
  line-height: 56px;
  padding-left: 5px;
}
.login_logo {
  width: 40px;
  height: 40px;
  margin-top: 8px;
  margin-left: 20px;
}
.login_block {
  height: 100%;
  text-align: center;
}
.login_block:before {
  content: "";
  display: inline-block;
  height: 100%;
  vertical-align: middle;
  margin-right: -0.25em;
}
.login_content {
  white-space: nowrap;
}
.login_backimg,
.login_content {
  display: inline-block;
  vertical-align: middle;
}
.login_backimg {
  width: 540px;
  height: 300px;
}
.login_card {
  margin-left: 50px;
  width: 350px;
  height: 300px;
  background: #fff;
  display: inline-block;
  vertical-align: middle;
}
</style>

三.后端接口编写

1.引入Maven依赖

这里我偷个懒,把项目所有的依赖都放在这里了

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.49</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
            <version>2.3.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.11.5</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.7</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

2.数据库表结构

这里的登陆是从我之前写过的一个项目中摘出来的,这里我直接把项目中的与客户信息有关的表结构放在这里了。其中密码是被springsecurity加密之后的,黄药师,曲灵风,阿恒的密码均为123456,黄蓉的密码是123456789。

CREATE TABLE `sysuser`  (
  `userId` int(0) NOT NULL AUTO_INCREMENT COMMENT '用户编号',
  `userName` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `userPassword` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `userRoleId` int(0) NOT NULL COMMENT '用户职责ID',
  `daId` int(0) NOT NULL COMMENT '用户所属地区',
  `delMark` int(0) NOT NULL DEFAULT 1 COMMENT '除删标记',
  PRIMARY KEY (`userId`) USING BTREE,
  UNIQUE INDEX `name_un`(`userName`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sysuser
-- ----------------------------
INSERT INTO `sysuser` VALUES (1, 'huangyaoshi', '$2a$10$CYBUp/n0jikEg5b2X6W0DOSr.hUhyh7K4BwYdDeK/MTIDvM2ABnp6', 4, 1, 1);
INSERT INTO `sysuser` VALUES (4, 'qulingfeng', '$2a$10$JfybK3swetXoQdoXoadHm.SxM/QTeCXaWRUMqOtz.bI1biw9ZwL9C', 3, 1, 1);
INSERT INTO `sysuser` VALUES (6, 'huangrong', '$2a$10$ec2t2KwewPEyA1Kd1BXvzOrQzO4tvK5HDkYhMw67PoRPXZ1DiuZh6', 2, 1, 1);
INSERT INTO `sysuser` VALUES (7, 'aheng', '$2a$10$EzJwAGwtrecioUnSEhzk9ueTaARTAKF/kCugvhJ92dD8q1l7ebwcG', 1, 1, 1);

3.后端代码编写

1.后端项目结构

在这里插入图片描述

2.后端跨域配置

代码如下:

package com.neusoft.crm.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 WebMvcConfig implements WebMvcConfigurer {
	@Override
	public void addCorsMappings(CorsRegistry registry) {
		/*
		 * addMapping:配置可以被跨域的路径,可以任意配置,可以具体到直接请求路径。
		 * allowedMethods:允许的请求方式,如:POST、GET、PUT、DELETE等。
		 * allowedOrigins:允许访问的url,可以固定单条或者多条内容
		 * allowedHeaders:允许的请求header,可以自定义设置任意请求头信息。
		 * maxAge:配置预检请求的有效时间
		 */
		registry.addMapping("/**")
				.allowedOrigins("*")
				.allowedMethods("*")
				.allowedHeaders("*")
				.maxAge(36000);
	}
}

3.Mybatis-Plus配置

可能我加了分页拦截器,在直接引入之后会产生错误,所以这里可以参考其他的mybatis-plus的配置

package com.neusoft.crm.Config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Projectname: crm_system_backend
 * @Filename: MybatisPlusConfig
 * @Author: skw
 */
@Configuration
@MapperScan("com.kevin.project.mapper")
public class MybatisPlusConfig {
	/**
	 * 新增分页拦截器,并设置数据库类型为mysql
	 */
	@Bean
	public MybatisPlusInterceptor mybatisPlusInterceptor() {
		MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
		interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
		return interceptor;
	}
}

4.springsecurity配置

securityConfig类:

package com.neusoft.crm.Config;


import com.neusoft.crm.Filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;


@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
          .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
          .and()
          .authorizeRequests()
          .antMatchers("/user/**").permitAll()
          .antMatchers(HttpMethod.OPTIONS).permitAll()
          .anyRequest().authenticated();
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(HttpMethod.GET);
        web.ignoring().antMatchers("/user/**");
    }
    
}

JwtAuthenticationTokenFilter类(在Filter文件夹下):
(加入这个文件之后,会对所有的请求进行拦截,在securityConfig中进行拦截放行的除外,如果想要使发送的其他请求有效,就是在前端发送的请求头中携带后端返回的token,在文章的末尾我会给出具体的格式。)

package com.neusoft.crm.Filter;

import com.neusoft.crm.Utils.JwtUtil;
import com.neusoft.crm.Utils.UserDetailsImpl;
import com.neusoft.crm.mapper.SysuserMapper;
import com.neusoft.crm.po.Sysuser;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Autowired
    private SysuserMapper sysuserMapper;
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        
        String token = request.getHeader("Authorization");
        if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) {
            filterChain.doFilter(request, response);
            return;
        }
        token = token.substring(7);
        String userid;
        try {
            Claims claims = JwtUtil.parseJWT(token);
            userid = claims.getSubject();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        
        Sysuser sysuser = sysuserMapper.selectById(Integer.parseInt(userid));
        
        if (sysuser == null) {
            throw new RuntimeException("用户未登录");
        }
        
        UserDetailsImpl loginUser = new UserDetailsImpl(sysuser);
        UsernamePasswordAuthenticationToken authenticationToken =
          new UsernamePasswordAuthenticationToken(loginUser, null, null);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        
        filterChain.doFilter(request, response);
    }
}

JwtUtils类(放在Utils包下):这个类主要是根据用户登录的过程中查出来的用户id来生成对应的JwtToken并返回前端,对应的token用于前端发送请求的校验,判断用户是否为登录状态或者是否为垃圾请求。

package com.neusoft.crm.Utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;

@Component
public class JwtUtil {
    public static final long JWT_TTL = 60 * 60 * 1000L * 24 * 14;  // 有效期14天
    public static final String JWT_KEY = "JSDFSDFSDFASJDHASDASDdfa32dJHASFDA67765asda123";
    
    public static String getUUID() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }
    
    public static String createJWT(String subject) {
        JwtBuilder builder = getJwtBuilder(subject, null, getUUID());
        return builder.compact();
    }
    
    private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        SecretKey secretKey = generalKey();
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        if (ttlMillis == null) {
            ttlMillis = JwtUtil.JWT_TTL;
        }
        
        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);
        return Jwts.builder()
          .setId(uuid)
          .setSubject(subject)
          .setIssuer("sg")
          .setIssuedAt(now)
          .signWith(signatureAlgorithm, secretKey)
          .setExpiration(expDate);
    }
    
    public static SecretKey generalKey() {
        byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        return new SecretKeySpec(encodeKey, 0, encodeKey.length, "HmacSHA256");
    }
    
    public static Claims parseJWT(String jwt) throws Exception {
        SecretKey secretKey = generalKey();
        return Jwts.parserBuilder()
          .setSigningKey(secretKey)
          .build()
          .parseClaimsJws(jwt)
          .getBody();
    }
}

5.SysUserController层编写(只贴了与登录有关的函数):

代码如下:

@RequestMapping("/user/selectSysUserByNameByPass")
	public Map<String, Object> selectSysUserByNameByPass(@RequestBody Sysuser sysUser) {
		return sysUserService.getToken(sysUser);
	}
6.SysuserService层编写(同上):
Map<String, Object> getToken(Sysuser sysUser);

6.SysUserServiceImp层编写:

在这里我加入try-catch捕获异常主要是因为springsecurity自带的异常捕获机制,在登录失败,例如密码错误或者用户不存在等情况出现的时候,错误信息是直接产生在后台,我在这里捕获了异常信息,并将该信息返回给了前端,这样就知道用户产生了哪些错误行为。

 public Map<String, Object> getToken(Sysuser sysUser) {
        UsernamePasswordAuthenticationToken  authenticationToken=new UsernamePasswordAuthenticationToken(sysUser.getUserName(),sysUser.getUserPassword());
        Authentication authenticate;
        try {
            authenticate= authenticationManager.authenticate(authenticationToken);  // 登录失败,会自动处理
        }catch (Exception exception){
            Map<String,Object> map = new HashMap<>();
            map.put("success","false");
            map.put("message",exception.getMessage());
            return  map;
        }
        UserDetailsImpl loginUser = (UserDetailsImpl) authenticate.getPrincipal();
        Sysuser sysuser = loginUser.getSysuser();
        String jwt = JwtUtil.createJWT(sysuser.getUserId().toString());
        Map<String, Object> map = new HashMap<>();
        map.put("error_message", "success");
        map.put("token", jwt);
        map.put("sysUser", sysuser);
        return map;
    }

7.UserDetailImp类(放在Utils包下):

该类是springsecurity自带的用户信息类,包括用户的账号和密码,用于判断。

package com.neusoft.crm.Utils;

import com.neusoft.crm.po.Sysuser;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserDetailsImpl implements UserDetails {
    private Sysuser sysuser;
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }
    
    @Override
    public String getPassword() {
        return sysuser.getUserPassword();
    }
    
    @Override
    public String getUsername() {
        return sysuser.getUserName();
    }
    
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
    
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    
    @Override
    public boolean isEnabled() {
        return true;
    }
}

8.UserDetailsServiceImp类(放在Imp包下):

主要用于查找是否存在该用户,将用户的账号密码和数据库中进行对比

package com.neusoft.crm.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.neusoft.crm.Utils.UserDetailsImpl;
import com.neusoft.crm.mapper.SysuserMapper;
import com.neusoft.crm.po.Sysuser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private SysuserMapper sysuserMapper;
    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        QueryWrapper<Sysuser> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userName", userName);
         Sysuser sysuser = sysuserMapper.selectOne(queryWrapper);
        if (sysuser == null) {
            throw new RuntimeException("用户不存在");
        }
    
        return new UserDetailsImpl(sysuser);
    
    }
}

四.具体效果(仅涉及登陆功能)

1.登录成功

在这里插入图片描述

2.登录失败(用户名或密码错误)

在这里插入图片描述

3.登陆失败(用户不存在)

在这里插入图片描述

总结

一个简单的登录系统,一个草率的半成品
前端链接:
链接:https://pan.baidu.com/s/1QaaQhVIvil5A6249_o_4Cg?pwd=kvv3
提取码:kvv3
后端链接:
链接:https://pan.baidu.com/s/1Dq1zCE6MWx-JfZK0oPx54Q?pwd=t7q0
提取码:t7q0

Logo

前往低代码交流专区

更多推荐