vue3+springboot2登录(可自己拓展其他功能)
vue3+springboot2+springsecurity实现登录功能
前言
该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')
}
编写个现在比较流行的界面,代码直接贴这里了,代码直接放上去会有一定的错误,接下来列举报错的地方:
- 左上角的logo,需要自己找一张logo图片,背景图片是我直接从网上找的。
- 向后端请求的接口需要更改,在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
更多推荐
所有评论(0)