提示:
前后端分离实现含验证码登录
前言
花了一下午,终于做出能够使用验证码的登录页面。
因为前后端不分离的情况下,验证码是存储在session中的,然后调用需要用到验证码功能的接口时,从session中获取验证码的值,然后与前端页面传过来的值进行比对。但前后端分离用不到session,所以
基于前后端分离的基础上,我做了含验证码的登录功能,后端验证码使用redis存储,解决验证码存储的问题。具体实现流程,让我们往下看吧。
一、先来看看页面成果
二、前端开发
因为是学后端的,前端不是特别熟,如果有什么错误或不好的地方,欢迎大家指出来。
路由跳转之类的都是基础配置,就不
一一展示代码了,直接上登录页面吧
Login代码:
<template>
<div id="background">
<div class="login-box">
<div class="login-left">
<div class="text-left">
<h2>Welocme</h2>
<h2>进入外卖系统后台</h2>
</div>
</div>
<div class="login-right">
<h2 class="login-title"><el-icon><platform/></el-icon> 后台登录系统</h2>
<el-form :model="form" :rules="rules" label-width="80px">
<el-form-item label="用户名" prop="username">
<el-input :prefix-icon="User" :suffix-icon="ArrowDown" v-model.trim="form.username" placeholder="请输入用户名"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input
:suffix-icon="Hide"
:prefix-icon="Unlock"
type="password"
v-model.trim="form.password"
placeholder="请输入密码"
></el-input>
</el-form-item>
<el-form-item label="验证码" prop="verifyCode">
<el-input v-model.trim="form.verifyCode" :prefix-icon="EditPen" class="code" size="large" placeholder="请输入验证码"/>
<img src="http://localhost:8080/kaptcha" alt="" class="codeimg">
</el-form-item>
<el-form-item>
<el-button type="primary" @click="login()">登录</el-button>
<el-button @click="resetForm(form)">重置</el-button>
</el-form-item>
</el-form>
</div>
</div>
</div>
</template>
<script setup>
import { reactive,ref } from 'vue'
import axios from "axios"
import { Hide, Unlock,User,ArrowDown,Platform,EditPen } from '@element-plus/icons-vue'
import router from '../../router/index'
const form = reactive({
username: 'Jone',
password: '12345',
verifyCode: ''
})
const rules = reactive({
username: [
{ required: true, message: '请输入用户名!', trigger: 'blur' },
{ min: 3, max: 7, message: '用户名长度为5-12', trigger: 'blur' },
],
password: [
{ required: true, message: '请输入密码!', trigger: 'blur' },
{ min: 5, max: 12, message: '密码长度为5-12', trigger: 'blur' },
],
verifyCode: [
{ required: true, message: '请输入验证码!', trigger: 'blur' },
],
})
function login() {
axios({
method: 'post',
url: 'http://localhost:8080/customer/login',
data:{
username: form.username,
password: form.password,
verifyCode: form.verifyCode
},
}).then(resp =>{
if(resp.data.flag==true){
//登录成功
// userInfo.setToken(resp.data.data);
router.push("/") //跳转路由
}else{
console.log('用户名或密码错误');
return false;
}
});
}
function resetForm(form){
form.username = "",
form.password = ""
}
</script>
<style scoped>
#background {
width: 100%;
height: 100%;
background: url("../../assets/50.jpg");
background-size: 100% 100%;
position: fixed;
top: 0;
left: 0;
}
.login-box {
height: 350px;
width: 800px;
margin: 250px auto;
border: 1px solid black;
position: relative;
}
.login-left{
height: 100%;
width: 400px;
position:absolute;
text-align: center;
text-align: center;
}
.login-right{
height: 100%;
width: 400px;
margin-left: 400px;
position:absolute;
background-color: white ;
}
.login-title {
text-align: center;
margin-bottom: 30px;
}
.text-left{
padding-top: 100px;
}
.el-form-item{
padding: 8px;
}
.regist{
padding-left: 320px;
color: red;
}
.el-button{
margin: 0 50px 0 33px;
}
.el-icon{
vertical-align: middle
}
.code{
width: 120px;
}
.codeimg{
padding-left: 50px;
}
</style>
三.后端开发
pom.xml
要导入的依赖
<!--mybatisPlus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<!--druid数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
<!--mysql数据库-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--JWT依赖-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.15.0</version>
</dependency>
<!-- kaptcha验证码 -->
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
<!--Redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
application.yml
redis记得写上你自己的host
spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3307/xinxin_takeout?characterEncoding=utf-8&userSSL=false
username: root
password: root
redis:
host: **********
port: 6379
database: 0
timeout: 1800000
lettuce:
pool:
max-active: 20
max-wait: -1
max-idle: 5
min-idle: 0
mybatis-plus:
global-config:
db-config:
id-type: auto
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
config类:
JTW的拦截器配置以及验证码的相关配置
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JWTInterceptor())
.addPathPatterns("/books/**") //其他接口token验证
.excludePathPatterns("/user/login") //放行
.excludePathPatterns("/user/register"); //放行
}
}
@Configuration
public class KaptchaConfig {
@Bean
public DefaultKaptcha getDefaultKaptcha(){
DefaultKaptcha captchaProducer = new DefaultKaptcha();
Properties properties = new Properties();
properties.setProperty("kaptcha.border", "yes");
properties.setProperty("kaptcha.border.color", "105,179,90");
properties.setProperty("kaptcha.textproducer.font.color", "blue");
properties.setProperty("kaptcha.image.width", "110");
properties.setProperty("kaptcha.image.height", "40");
properties.setProperty("kaptcha.textproducer.font.size", "30");
properties.setProperty("kaptcha.session.key", "code");
properties.setProperty("kaptcha.textproducer.char.length", "4");
properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");
Config config = new Config(properties);
captchaProducer.setConfig(config);
return captchaProducer;
}
}
interceptions类
JWT拦截器的编写,网上都是有代码的,直接复制下来就行
public class JWTInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if("OPTIONS".equals(request.getMethod().toUpperCase())) {
System.out.println("Method:OPTIONS");
return true;
}
Map<String,Object> map = new HashMap<>();
//获取请求头中的令牌
String token = request.getHeader("Token");
try{
//验证令牌
JWTUtils.verify(token);
return true;//放行请求
}catch (JWTDecodeException e){ //捕捉签名异常怎么办
e.printStackTrace();
map.put("msg","无效签名");
}catch (TokenExpiredException e){ //捕捉令牌过期异常
e.printStackTrace();
map.put("msg","令牌过期");
}catch (AlgorithmMismatchException e){ //捕捉算法不匹配异常
e.printStackTrace();
map.put("msg","算法不匹配");
}catch (InvalidClaimException e){ //捕捉失效的payload异常
e.printStackTrace();
map.put("msg","失效的payload ");
}catch (Exception e){
map.put("msg","其他的异常");
e.printStackTrace();
}
map.put("state",false); //设置状态
//将map转为JSON jackson 发给前端
String json = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(json);
return false;
}
}
utils 类
统一响应
import lombok.Data;
@Data
public class R<T> {
private boolean flag;
private T data;
private String msg;
public R(boolean flag, T data, String msg) {
this.flag = flag;
this.data = data;
this.msg = msg;
}
public R(boolean flag, T data) {
this.flag = flag;
this.data = data;
}
public R(boolean flag) {
this.flag = flag;
}
}
JWT工具类
public class JWTUtils {
//签名
private static final String SIGN = "!QQ546R@FA$asf564E##S45$%456asfdFGAag*^SDg#%FG";
/**
* 生成token header.payload.signature
*/
public static String getToken(Map<String,String> map) {
Calendar instance = Calendar.getInstance();
instance.add(Calendar.HOUR,1); //过期时间
//创建jwt builder
JWTCreator.Builder builder = JWT.create();
//payload(传入的map数据)
map.forEach((k,v) ->{
builder.withClaim(k,v);
});
String token = builder.withExpiresAt(instance.getTime()) //指定过期时间
.sign(Algorithm.HMAC256(SIGN));//sign
return token;
}
/**
* 验证token合法性
*/
public static void verify(String token){
JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
}
/**
* 获取token信息
*/
public static DecodedJWT getTokenInfo(String token){
DecodedJWT verify = JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
return verify;
}
}
pojo层
都是mybatisplus自动生成的
@TableName(value ="u_customer")
@Data
public class Customer implements Serializable {
@TableId(type = IdType.AUTO)
private Long cId;
private Long username;
private String password;
}
mapper层
@Mapper
public interface CustomerMapper extends BaseMapper<Customer> {
Customer login(@Param("username") String username);
}
<select id="login" resultType="com.xinxue.pojo.Customer">
select * from u_customer where username=#{username}
</select>
service层
public interface CustomerService extends IService<Customer> {
R<String> login(String username, String password,String verifyCode);
}
/**
* @author zhang
* @description 针对表【u_customer】的数据库操作Service实现
* @createDate 2022-05-09 22:04:27
*/
@Service
public class CustomerServiceImpl extends ServiceImpl<CustomerMapper, Customer> implements CustomerService{
@Autowired
private CustomerMapper customerMapper;
@Autowired
private RedisTemplate redisTemplate;
@Override
public R<String> login(String username, String password,String verifyCode) {
Map<String,String> map = new HashMap<String,String>();
String capText = (String) redisTemplate.opsForValue().get("capText");
System.out.println(capText);
Customer customer = customerMapper.login(username);
if (customer!=null){
password = DigestUtils.md5DigestAsHex(password.getBytes());
if (password.equals(customer.getPassword()) && verifyCode.equals(capText)){
map.put("name",customer.getName());
map.put("sex",customer.getSex());
map.put("age",String.valueOf(customer.getAge()));
map.put("email",customer.getEmail());
map.put("phone",customer.getPhone());
String token = JWTUtils.getToken(map);
return new R<String>(true,token,"登录成功!!!");
}
}
return new R<String>(false,null,"用户名或密码错误!!!");
}
}
controller层
import com.google.code.kaptcha.Constants;
import com.google.code.kaptcha.Producer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.image.BufferedImage;
@Controller
public class KaptchaController {
@Autowired
private Producer captchaProducer ;
@Autowired
private RedisTemplate redisTemplate;
@CrossOrigin
@RequestMapping("/kaptcha")
public void getKaptchaImage(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpSession session = request.getSession();
response.setDateHeader("Expires", 0);
response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
response.addHeader("Cache-Control", "post-check=0, pre-check=0");
response.setHeader("Pragma", "no-cache");
response.setContentType("image/jpeg");
//生成验证码
String capText = captchaProducer.createText();
redisTemplate.opsForValue().set("capText",capText);
//向客户端写出
BufferedImage bi = captchaProducer.createImage(capText);
ServletOutputStream out = response.getOutputStream();
ImageIO.write(bi, "jpg", out);
try {
out.flush();
} finally {
out.close();
}
}
}
@RestController
@RequestMapping("/customer")
public class CustomerController {
@Autowired
private CustomerService customerService;
// 登录
@PostMapping("/login")
@CrossOrigin
public R<String> login(@RequestBody Map<String,String> map){
String username = map.get("username");
String password = map.get("password");
String verifyCode = map.get("verifyCode");
return customerService.login(username, password, verifyCode);
}
}
总结
我感觉写进去的东西太多了,有很多东西其实不用写上去的,就比如mybatisplus自动生成的那些,但是为了代码完整写,还是附带上去了,还有就是JWT和验证码的代码,都是自己网上找的,也是没有太多的必要,但不加上又感觉哪里少了点东西。希望大家能多给一点鼓励。
版权声明:本文为weixin_53423402原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。