当前位置: 移动技术网 > IT编程>开发语言>Java > 荐 SpringBoot+Vue实现SpringSecurity+验证码登录与点击刷新验证码

荐 SpringBoot+Vue实现SpringSecurity+验证码登录与点击刷新验证码

2020年07月23日  | 移动技术网IT编程  | 我要评论

效果图

总体页面

在这里插入图片描述

表单填写完成在这里插入图片描述

验证码错误在这里插入图片描述

密码或用户名错误

在这里插入图片描述

登录成功

在这里插入图片描述

后端

建表SQL

CREATE TABLE `admin` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户自增Id',
  `username` varchar(64) COLLATE utf8_bin NOT NULL COMMENT '用户登录名',
  `password` varchar(200) COLLATE utf8_bin NOT NULL COMMENT '用户登录密码',
  `isEnable` tinyint(1) NOT NULL DEFAULT '1' COMMENT '用户是否被禁用(1:禁用,0:启用)',
  `name` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT '真实姓名',
  `avatar` varchar(300) COLLATE utf8_bin DEFAULT NULL COMMENT '用户头像',
  `phone` varchar(64) COLLATE utf8_bin DEFAULT NULL COMMENT '用户电话',
  `QQ` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT 'QQ',
  `wechat` varchar(64) COLLATE utf8_bin DEFAULT NULL COMMENT '微信',
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
insert  into `admin`(`id`,`username`,`password`,`isEnable`,`name`,`avatar`,`phone`,`QQ`,`wechat`) values 
(1,'admin','$2a$10$7PMd9hwuYYe6.8rgmfn1x.GbrS4FyuH1nf80xb45zqm/ntZyzccBG',1,'张三','密码为111,这个字段是给头像用的','111','2548841623','微信'),
(2,'admins','$2a$10$k.uVP6DEc/X.DceezRIi1.zALL6rhsMpCVZFbt6pZDppdFV3wLIWi',1,'李四','密码为111,这个字段是给头像用的','111','111','111');
CREATE TABLE `admin_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户表与角色表的关联表自增ID',
  `adminID` int(11) NOT NULL COMMENT '对应用户表的ID',
  `roleID` int(11) NOT NULL COMMENT '对于角色表的ID',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
insert  into `admin_role`(`id`,`adminID`,`roleID`) values (1,1,1);
CREATE TABLE `role` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '角色表自增Id',
  `name` varchar(64) COLLATE utf8_bin NOT NULL COMMENT '角色的英文名,给电脑用的',
  `nameZH` varchar(64) COLLATE utf8_bin NOT NULL COMMENT '角色中文名,给用户看的',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
insert  into `role`(`id`,`name`,`nameZH`) values 
(1,'ROLE_system','系统管理员'),
(2,'ROLE_admin','用户'),
(3,'ROLE_user','普通用户');

环境(POM.xml)

<!-- SPringBoot版本 -->
<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.3.1.RELEASE</version>
	<relativePath/> <!-- lookup parent from repository -->
</parent>

<!-- web依赖 -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Durid-->
<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>druid-spring-boot-starter</artifactId>
	<version>1.1.14</version>
</dependency>

<!-- 验证码 -->
<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>2.3.2</version>
</dependency>

<dependency>
	<groupId>cn.hutool</groupId>
	<artifactId>hutool-all</artifactId>
	<version>4.5.1</version>
</dependency>

<!-- M -->
<dependency>
	<groupId>org.mybatis.spring.boot</groupId>
	<artifactId>mybatis-spring-boot-starter</artifactId>
	<version>1.3.2</version>
</dependency>

<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<scope>runtime</scope>
</dependency>

实体类

用户实体类Admin(主要实现七个方法)

public class Admin implements UserDetails{//实现UserDetails,让springsecurity接管用户对象
	private static final long serialVersionUID = 1L;
	private Integer id;//id
    private String username;//用户名
    private String password;//密码
    private Boolean isEnable;//账号禁用与否
    private List<Role> roles;//角色集合
	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		List<SimpleGrantedAuthority> simpleGrantedAuthority = new ArrayList<>();
		for(Role role:roles) {
			simpleGrantedAuthority.add(new SimpleGrantedAuthority(role.getName()));
		}
		return simpleGrantedAuthority;
	}

	@Override
	public boolean isAccountNonExpired() {
		return true;
	}

	@Override
	public boolean isAccountNonLocked() {
		return true;
	}

	@Override
	public boolean isCredentialsNonExpired() {
		return true;
	}

	@Override
	public boolean isEnabled() {
		return isEnable;
	}

	@Override
	public String getPassword() {
		return password;
	}

	@Override
	public String getUsername() {
		return username;
	}
	//.....getter setter
}

权限实体类(Role)

public class Role {
    private Integer id;
    private String name;
    private String nameZH;
    //.....getter setter
}

用户角色关联实体类(AdnimRole)

public class AdminRole {
    private Integer id;
    private Integer adminID;
    private Integer roleID;
    //.....getter  setter
}

Mapper

AadminMapper.java

@Mapper
public interface AdminMapper {
	Admin loadUserByUsername(@Param("username")String username);//查询用户名
	List<Role> getAdminRoleById(@Param("id")Integer id);//查询用户权限
}

AdminMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="xyz.nieqiang.webapp.mapper.AdminMapper">
  <resultMap id="BaseResultMap" type="xyz.nieqiang.webapp.entity.Admin">
    <id column="id" jdbcType="INTEGER" property="id" />
    <result column="username" jdbcType="VARCHAR" property="username" />
    <result column="password" jdbcType="VARCHAR" property="password" />
    <result column="isEnable" jdbcType="BIT" property="isEnable" />
    <result column="name" jdbcType="VARCHAR" property="name" />
    <result column="QQ" jdbcType="VARCHAR" property="QQ" />
    <result column="wechat" jdbcType="VARCHAR" property="wechat" />
    <result column="avatar" jdbcType="VARCHAR" property="avatar" />
    <result column="phone" jdbcType="VARCHAR" property="phone" />
    <collection property="roles" javaType="xyz.nieqiang.webapp.entity.Role">
		<id column="rid" jdbcType="INTEGER" property="id"/>
		<result column="rname" jdbcType="VARCHAR" property="name"/>
		<result column="rnameZh" jdbcType="VARCHAR" property="nameZh"/>
    </collection>
  </resultMap>
  <sql id="Base_Column_List">
    id, username, password, avatar, phone, isEnable, QQ, wechat, name
  </sql>
  
  <!-- 登录关键查询 -->
  <select id="loadUserByUsername" resultType="xyz.nieqiang.webapp.entity.Admin">
  	SELECT <include refid="Base_Column_List"></include> FROM admin WHERE username=#{username}
  </select>
  
  <!-- 角色获取关键查询 -->
  <select id="getAdminRoleById" resultType="xyz.nieqiang.webapp.entity.Role">
  	SELECT r.* FROM role r, admin_role ar WHERE ar.adminID=#{id} AND ar.roleID=r.id
  </select>
</mapper>

serviceImpl

import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import cn.hutool.core.util.StrUtil;
import xyz.nieqiang.webapp.entity.Admin;
import xyz.nieqiang.webapp.mapper.AdminMapper;
import xyz.nieqiang.webapp.service.AdminService;
import xyz.nieqiang.webapp.util.UserIPUtil;
@Service
public class AdminServiceImpl implements UserDetailsService{
	private static final Logger logger = LoggerFactory.getLogger(AdminServiceImpl.class);
	
	@Autowired
	private AdminMapper adminMapper;
	
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		Admin admin = adminMapper.loadUserByUsername(username);
		if(admin == null) {
			throw new UsernameNotFoundException(username);
		}
		admin.setRoles(adminMapper.getAdminRoleById(admin.getId()));
		logger.info("{}登录了",username);
		return admin;
	}
}

验证码配置

import java.util.Properties;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;

@Configuration
public class KaptchaConfiguration {
	
	@Bean
    public DefaultKaptcha producer() {
        Properties properties = new Properties();//是否有边框
        properties.put("kaptcha.border", "no");
        properties.put("kaptcha.border.color", "red");
        properties.put("kaptcha.textproducer.font.color", "black");
        properties.put("kaptcha.textproducer.char.space", "1");
        properties.put("kaptcha.textproducer.char.length", "4");//验证码文本字符长度  默认为5
        properties.put("kaptcha.image.width", "120");
        properties.put("kaptcha.image.height", "50");
        properties.put("kaptcha.textproducer.font.size", "40");
        Config config = new Config(properties);
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
	
//	kaptcha.border  是否有边框  默认为true  我们可以自己设置yes,no  
//	kaptcha.border.color   边框颜色   默认为Color.BLACK  
//	kaptcha.border.thickness  边框粗细度  默认为1  
//	kaptcha.producer.impl   验证码生成器  默认为DefaultKaptcha  
//	kaptcha.textproducer.impl   验证码文本生成器  默认为DefaultTextCreator  
//	kaptcha.textproducer.char.string   验证码文本字符内容范围  默认为abcde2345678gfynmnpwx  
//	kaptcha.textproducer.char.length   验证码文本字符长度  默认为5  
//	kaptcha.textproducer.font.names    验证码文本字体样式  默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)  
//	kaptcha.textproducer.font.size   验证码文本字符大小  默认为40  
//	kaptcha.textproducer.font.color  验证码文本字符颜色  默认为Color.BLACK  
//	kaptcha.textproducer.char.space  验证码文本字符间距  默认为2  
//	kaptcha.noise.impl    验证码噪点生成对象  默认为DefaultNoise  
//	kaptcha.noise.color   验证码噪点颜色   默认为Color.BLACK  
//	kaptcha.obscurificator.impl   验证码样式引擎  默认为WaterRipple  
//	kaptcha.word.impl   验证码文本字符渲染   默认为DefaultWordRenderer  
//	kaptcha.background.impl   验证码背景生成器   默认为DefaultBackground  
//	kaptcha.background.clear.from   验证码背景颜色渐进   默认为Color.LIGHT_GRAY  
//	kaptcha.background.clear.to   验证码背景颜色渐进   默认为Color.WHITE  
//	kaptcha.image.width   验证码图片宽度  默认为200  
//	kaptcha.image.height  验证码图片高度  默认为50

}

工具类


public class HTTPResponse {
	private Integer status;
	private String message;
	private Object data;
	
	public static HTTPResponse ok(String message) {
		return new HTTPResponse(200, message);
	}
	public static HTTPResponse ok(Object data) {
		return new HTTPResponse(200, data);
	}	
	public static HTTPResponse ok(String message,Object data) {
		return new HTTPResponse(200, message, data);
	}	
	public static HTTPResponse error(Integer status, String message) {
		return new HTTPResponse(500, message, null);
	}	
	public static HTTPResponse error(String message) {
		return new HTTPResponse(500, message, null);
	}	
	public static HTTPResponse error(String message,Object data) {
		return new HTTPResponse(500, message, data);
	}	
	protected HTTPResponse() {super();}
	private HTTPResponse(Integer status, String message) {
		this.status = status;
		this.message = message;
	}	
	private HTTPResponse(Integer status, Object data) {
		this.status = status;
		this.data = data;
	}	
	private HTTPResponse(Integer status, String message, Object data) {
		this.status = status;
		this.message = message;
		this.data = data;
	}	
	public Integer getStatus() {return status;}
	public String getMessage() {return message;}
	public Object getData() {return data;}
	public void setStatus(Integer status) {this.status = status;}
	public void setMessage(String message) {this.message = message;}
	public void setObject(Object data) {this.data = data;}
}
import java.net.InetAddress;
import java.net.UnknownHostException;

import javax.servlet.http.HttpServletRequest;

import org.springframework.security.core.context.SecurityContextHolder;

import xyz.nieqiang.webapp.entity.Admin;
public class UserIPUtil {
	
	//获取当前用户的用户名的ID
	public static Integer getCurrentAdminId() {
        return ((Admin)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getId();
    }
	
	//获取当前用户的用户姓名
	public static String getCurrentAdminName() {
        return ((Admin)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getName();
    }
	
	//获取当前用户的登录名
	public static String getCurrentAdminUserName() {
        return ((Admin)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername();
    }
	
	//获取IP
	public static String getIPAddress(HttpServletRequest request) {
		String ipAddress = null;
		try {
			ipAddress = request.getHeader("x-forwarded-for");
			if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
				ipAddress = request.getHeader("Proxy-Client-IP");
			}
			if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
				ipAddress = request.getHeader("WL-Proxy-Client-IP");
			}
			if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
				ipAddress = request.getRemoteAddr();
				if (ipAddress.equals("127.0.0.1")) {
					// 根据网卡取本机配置的IP
					InetAddress inet = null;
					try {
						inet = InetAddress.getLocalHost();
					} catch (UnknownHostException e) {
						e.printStackTrace();
					}
					ipAddress = inet.getHostAddress();
				}
			}
			// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
			if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()
				if (ipAddress.indexOf(",") > 0) {
					ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
				}
			}
		} catch (Exception e) {
			ipAddress = "未获取到IP";
		}
		return ipAddress;
	}
}

登录拦截器

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.util.StringUtils;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.code.kaptcha.Constants;

import xyz.nieqiang.webapp.util.HTTPResponse;
/**
 * 验证码拦截器
 * @CreationDate:2020年7月16日下午4:27:07
 * @Author:NieQiang
 * @ComputerName:Administrator
 */
public class LoginFilter extends UsernamePasswordAuthenticationFilter {
	private static final Logger logger = LoggerFactory.getLogger(LoginFilter.class);

	@Override
	public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) resp;
		if("POST".equalsIgnoreCase(request.getMethod()) && "/login".equals(request.getServletPath())) {
			String code = request.getParameter("code");
			String kaptchaCode = (String) request.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY);
			logger.info("用户输入验证码:{}========session中存的验证码:{}",code,kaptchaCode);
			if(StringUtils.isEmpty(code)) {
				response.setContentType("application/json;charset=UTF-8");
				response.setStatus(401);
				PrintWriter printWriter = response.getWriter();
				HTTPResponse httpResponse = HTTPResponse.error("验证码不能为空!","");
				printWriter.write(new ObjectMapper().writeValueAsString(httpResponse));
				printWriter.flush();
				printWriter.close();
				return;
			}else if(!kaptchaCode.toLowerCase().equals(code.toLowerCase())) {
				response.setContentType("application/json;charset=UTF-8");
				response.setStatus(401);
				PrintWriter printWriter = response.getWriter();
				HTTPResponse httpResponse = HTTPResponse.error("验证码错误!","");
				printWriter.write(new ObjectMapper().writeValueAsString(httpResponse));
				printWriter.flush();
				printWriter.close();
				return;
			}
		}
		chain.doFilter(request, response);
	}
}

验证码Controller

import java.awt.image.BufferedImage;
import java.io.IOException;

import javax.annotation.security.PermitAll;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import com.google.code.kaptcha.Constants;
import com.google.code.kaptcha.Producer;

@RestController
public class KaptchaController {
	@Autowired
	private Producer producer;
	
	@PermitAll//注解放行
	@GetMapping("/captcha.jpg")
	public void captcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
		response.setHeader("Cache-Control", "no-store, no-cache");
		response.setContentType("image/jpeg");

		// 生成文字验证码
		String text = producer.createText();
		// 生成图片验证码
		BufferedImage image = producer.createImage(text);
		// 保存到验证码到 session
		request.getSession().setAttribute(Constants.KAPTCHA_SESSION_KEY, text);

		ServletOutputStream out = response.getOutputStream();
		ImageIO.write(image, "jpg", out);	
		IOUtils.closeQuietly(out);
	}
}

SecurityConfiguration配置

SecurityConfiguration

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.session.InvalidSessionStrategy;
import com.fasterxml.jackson.databind.ObjectMapper;
import cn.hutool.http.HttpStatus;
import xyz.nieqiang.webapp.entity.Admin;
import xyz.nieqiang.webapp.service.impl.AdminServiceImpl;
import xyz.nieqiang.webapp.util.HTTPResponse;
import xyz.nieqiang.webapp.util.UserIPUtil;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
	private static final Logger logger = LoggerFactory.getLogger(SecurityConfiguration.class);
	
	@Autowired
	private AdminServiceImpl adminServiceImpl;
	
	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}

	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.userDetailsService(adminServiceImpl);
	}
	
	@Override
	public void configure(WebSecurity web) throws Exception {
		web.ignoring().antMatchers("/captcha.jpg");//不通过security过滤连放行
	}
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		//这里是在校验密码之前先校验验证码,如果验证码错误就不检验用户名和密码了
		http.addFilterBefore(new LoginFilter(), UsernamePasswordAuthenticationFilter.class);
		http.authorizeRequests()
        	.antMatchers("/captcha.jpg/**").permitAll()// 验证码//通过security过滤连放行
			.anyRequest().authenticated()//其他访问必须登录
			.and().formLogin()
			.successHandler(new AuthenticationSuccessHandler() {
				
				@Override
				public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
						Authentication authentication) throws IOException, ServletException {
					logger.info("{}=={}=={}",UserIPUtil.getCurrentAdminName(), new Date(), UserIPUtil.getIPAddress(request));
					response.setContentType("application/json;charset=UTF-8");
					PrintWriter printWriter = response.getWriter();
					Admin admin = (Admin) authentication.getPrincipal();
					admin.setPassword(null);//不返回password字段
					
					HTTPResponse httpResponse = HTTPResponse.ok("登录成功!",admin);
					String s = new ObjectMapper().writeValueAsString(httpResponse);
					printWriter.write(s);
					printWriter.flush();
					printWriter.close();
				}
			})
			.failureHandler(new AuthenticationFailureHandler() {
				
				@Override
				public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
						AuthenticationException exception) throws IOException, ServletException {
					logger.info("登录失败的日志");
					response.setContentType("application/json;charset=UTF-8");
					PrintWriter printWriter = response.getWriter();
					HTTPResponse httpResponse = HTTPResponse.error("登录失败", "");
					if(exception instanceof LockedException) {
						httpResponse.setMessage("账户被锁定!请联系管理员");
					}else if(exception instanceof AccountExpiredException) {
						httpResponse.setMessage("账户已过期!请联系管理员");
					}else if(exception instanceof DisabledException) {
						httpResponse.setMessage("账户被禁用!请联系管理员");
					}else if(exception instanceof CredentialsExpiredException) {
						httpResponse.setMessage("密码已过期!请联系管理员");
					}else if(exception instanceof BadCredentialsException) {
						httpResponse.setMessage("用户名或密码有误!请重新输入");
					}
					printWriter.write(new ObjectMapper().writeValueAsString(httpResponse));
					printWriter.flush();
					printWriter.close();
				}
			})
			.permitAll()
			.and()
			.logout().logoutSuccessHandler(new LogoutSuccessHandler() {
				
				@Override
				public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
						throws IOException, ServletException {
					logger.info("{}退出成功!",new Date());
					response.setContentType("application/json;charset=UTF-8");
					PrintWriter printWriter = response.getWriter();
					
					HTTPResponse httpResponse = HTTPResponse.ok("退出成功!",null);
					String s = new ObjectMapper().writeValueAsString(httpResponse);
					printWriter.write(s);
					printWriter.flush();
					printWriter.close();
				}
			}).permitAll()
			.and()
			.exceptionHandling().authenticationEntryPoint(new AuthenticationEntryPoint() {
				
				@Override
				public void commence(HttpServletRequest request, HttpServletResponse response,
						AuthenticationException authException) throws IOException, ServletException {
					response.setContentType("application/json;charset=UTF-8");
					response.setStatus(401);
					PrintWriter printWriter = response.getWriter();
					HTTPResponse httpResponse = HTTPResponse.error("访问失败!","");
					if(authException instanceof InsufficientAuthenticationException) {
						httpResponse.setMessage("尚未登录或者登录身份已过期,请登录或者重新登录!");
					}
					printWriter.write(new ObjectMapper().writeValueAsString(httpResponse));
					printWriter.flush();
					printWriter.close();
				}
			})
			.and().csrf().disable()
			.cors().disable();
		
		http.sessionManagement().invalidSessionStrategy(new InvalidSessionStrategy() {
			
			@Override
			public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response)
					throws IOException, ServletException {
				logger.info("{}退出成功!",new Date());
				response.setContentType("application/json;charset=UTF-8");
				response.setStatus(HttpStatus.HTTP_UNAUTHORIZED);
				PrintWriter printWriter = response.getWriter();
				
				HTTPResponse httpResponse = HTTPResponse.error("身份过期!",null);
				String s = new ObjectMapper().writeValueAsString(httpResponse);
				printWriter.write(s);
				printWriter.flush();
				printWriter.close();
			}
		});
	}
}

至此后端接口编写完毕

=========================以下是前端========================

前端重难点就是点击验证码刷新的现实,没啥好说的,直接贴代码吧

package.json依赖

"dependencies": {
    "ant-design-vue": "^1.6.2",
    "axios": "^0.19.2",
    "font-awesome": "^4.7.0",
    "bootstrap": "^4.5.0",
    "core-js": "^3.6.5",
    "element-ui": "^2.13.2",
    "vue": "^2.6.11",
    "vue-router": "^3.2.0",
    "vuex": "^3.4.0"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "~4.4.0",
    "@vue/cli-plugin-eslint": "~4.4.0",
    "@vue/cli-plugin-router": "~4.4.0",
    "@vue/cli-plugin-vuex": "~4.4.0",
    "@vue/cli-service": "~4.4.0",
    "babel-eslint": "^10.1.0",
    "babel-plugin-transform-remove-console": "^6.9.4",
    "compression-webpack-plugin": "^4.0.0",
    "eslint": "^6.7.2",
    "eslint-plugin-vue": "^6.2.2",
    "vue-template-compiler": "^2.6.11"
  }

Axios封装

import axios from 'axios'
import {Message} from 'element-ui'
import router from '../router'

axios.defaults.withCredentials = true; // 表示跨域请求时是否需要使用凭证,跨域访问需要发送cookie时一定要加
axios.interceptors.request.use(config=>{
     return config;
}, err=> {
     Message.error({message: '请求超时!'+err,showClose: true});
})


axios.interceptors.response.use(success=>{
          if (success.status && success.status == 200 && success.data.status == 500) {
               Message.error({
                    message: success.data.message+' '+success.data.data,
                    showClose: true
               });
               return;
          }
          if (success.data.message) {
               Message.success({
                    message: success.data.message,
                    showClose: true
               });
          }
          return success.data;
     },err=> {
          console.log(err,err.response,err.response.status);
          if(err.response.status == 500){
               // Message.error({message: "服务器连接失败⊙﹏⊙∥", showClose: true});
               router.push('/500');
          }else if (err.response.status == 504) {
               Message.error({message: '服务器连接失败⊙﹏⊙∥'+err.response.data.message,showClose: true});
          } else if (err.response.status == 404) {
               Message.error({message: '资源不存在!',showClose: true});
          }  else if (err.response.status == 403) {
               Message.error({message: '权限不足,请联系管理员!',showClose: true});
          } else if (err.response.status == 401) {
               Message.error({message:err.response.data.message,showClose: true});
               router.replace('/');
               sessionStorage.clear();
          }else if (err.response.status == 500) {
               Message.error({message:err.response.data.message,showClose: true});
          }else if (err.response == '') {
               this.$alert(err.response.message, {
                    confirmButtonText: '确定',
                    iconClass:'fa fa-exclamation-triangle',
                    title:'EXCEPTION!服务端存在异常!'
               });
               router.replace('/');
          }else {
               if (err.response.data.message) {
                    Message.error({message: err.response.data.message,showClose: true});
               }else{
                    Message.error({message: '未知错误!',showClose: true});
               }
          }
     }
)


let base = '';
export const postKeyValueRequest = (url, params)=> {
     return axios({
          method: 'POST',
          url: `${base}${url}`,
          data: params,
          transformRequest: [function (data) {
               let ret = ''
               for (let it in data) {
               ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'
               }
               return ret;
          }],
          headers: {
               'Content-Type': 'application/x-www-form-urlencoded'
          }
     });
}


export const postRequest = (url, params)=>{
     return axios({
          method: 'POST',
          url: `${base}${url}`,
          data: params,
          transformRequest: [function (data) {
               let ret = ''
               for (let it in data) {
               ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'
               }
               return ret
          }],
          headers: {
               'Content-Type': 'application/x-www-form-urlencoded'
          }
     });
}

export const putRequest = (url, params) => {
     return axios({
          method: 'PUT',
          url: `${base}${url}`,
          data: params,
          transformRequest: [function (data) {
               let ret = ''
               for (let it in data) {
               ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'
               }
               return ret
          }],
          headers: {
               'Content-Type': 'application/x-www-form-urlencoded'
          }
     });
}

export const getRequest = (url) => {
     return axios({
          method: 'GET',
          url: `${base}${url}`
     });
}

export const deleteRequest = (url) => {
     return axios({
          method: 'DELETE',
          url: `${base}${url}`
     });
}

export const uploadFileRequest = (url,params) => {
     return axios({
          method: 'POST',
          url: `${base}${url}`,
          data: params,
          headers: {
               'Content-Type': 'multipart/form-data'
          }
     });
}

export const downloadRequest = (url, params)=>{
     return axios({
          method: 'GET',
          url: `${base}${url}`,
          data: params,
          responseType: 'blob',
          headers: {
               'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8'
          }
     });
}

main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import 'ant-design-vue/dist/antd.css';
import 'element-ui/lib/theme-chalk/index.css';
import 'font-awesome/css/font-awesome.min.css'

/**Ant Design */
import {
     Layout,Menu,Tabs,Button,Icon,Input,Breadcrumb,
     Result,FormModel,Form ,Upload,Card,Modal
} from 'ant-design-vue';
Vue.use(Layout);
Vue.use(Menu);
Vue.use(Tabs);
Vue.use(Button);
Vue.use(Icon);
Vue.use(Input);
Vue.use(Breadcrumb);
Vue.use(Result);
Vue.use(Upload);
Vue.use(Card);
Vue.use(Modal);
Vue.use(Form);
Vue.use(FormModel);

/**ElementUIBloodLitchi*/
import {Image,Checkbox,Tooltip,Message,MessageBox,Popover,
     Loading, Notification} from 'element-ui';
Vue.use(Image); 
Vue.use(Checkbox);
Vue.use(Tooltip);
Vue.use(Popover);
Vue.use(Loading);
Vue.prototype.$message = Message;
Vue.prototype.$confirm = MessageBox.confirm;
Vue.prototype.$notify = Notification;

import {
     postKeyValueRequest,postRequest,
     putRequest,getRequest,deleteRequest,
     uploadFileRequest,downloadRequest
}from "@/util/axiosAPI";
// 方法封装
Vue.prototype.postKeyValueRequest = postKeyValueRequest;
Vue.prototype.postRequest = postRequest;
Vue.prototype.putRequest = putRequest;
Vue.prototype.getRequest = getRequest;
Vue.prototype.deleteRequest = deleteRequest;
Vue.prototype.uploadFileRequest = uploadFileRequest;
Vue.prototype.downloadRequest = downloadRequest;


Vue.config.productionTip = false

router.beforeEach((to, from,next)=>{
     if(to.path=='/'){
          next();
     }else if(sessionStorage.getItem('user') != null && sessionStorage.getItem('user')){
          next();
     }else if(sessionStorage.getItem('user')==null){
          next({path: '/'});
     }else{
          next({path: '/'});
     }
});

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

Login.vue

<template>
     <div class="login">
          <a-card title="登   录" :bordered="true" class="containers">
               <a-form-model layout="vertical" :model="loginForm" @submit="handleSubmit" @submit.native.prevent>
                    <a-form-model-item>
                         <a-input v-model="loginForm.username" placeholder="用户名" style="width:300px" allowClear>
                              <a-icon slot="prefix" type="user" style="color:rgba(0,0,0,.25)"/>
                         </a-input>
                    </a-form-model-item>
                    <a-form-model-item>
                         <a-input-password v-model="loginForm.password" placeholder="密 码" style="width:300px" allowClear>
                              <a-icon slot="prefix" type="lock" style="color:rgba(0,0,0,.25)" />
                         </a-input-password>
                    </a-form-model-item>
                    <a-form-model-item>
                         <a-input placeholder="点击图片刷新验证码" style="width:170px" v-model="loginForm.code" allowClear></a-input>
                         <img :src="verCode" class="verCode" @click="newVerCode">
                    </a-form-model-item>
                    <a-form-model-item>
                         <a-button type="primary" html-type="submit" style="width:300px"
                              :disabled="loginForm.username === '' || loginForm.password === '' || loginForm.code == ''">
                              登 录
                         </a-button>
                    </a-form-model-item>
               </a-form-model>
          </a-card>
     </div>
</template>
<script>
export default {
     data() {
          return {
               loginForm:{
                    username: '',
                    password: '',
                    code:""
               },
               verCode:""
          };
     },
     methods: {
          handleSubmit() {
               this.postRequest('/docs/login',
                    this.loginForm
               ).then(resp=>{
                    console.log(resp);
                    if(resp){
                         sessionStorage.clear();
                         sessionStorage.setItem('user',JSON.stringify(resp.data));
                         this.$router.replace('/all')
                    }
               });
          },
          newVerCode(){//刷新验证码后面加上随机数防止缓存导致刷新验证码失败
               this.verCode = "/docs/captcha.jpg?m="+Math.random();
          }
     },
     mounted(){
          this.newVerCode();
     }
};
</script>
<style scoped>
.login{
     position: fixed;
     top: 50%;
     left: 50%;
     transform: translate(-50%, -50%);
}

.containers{
     width: 400px;
     margin: auto;
}

.verCode{
     width:100px;
     height:31px;
     object-fit: fill;
     margin-left: 30px;
}
</style>

本文地址:https://blog.csdn.net/qq_42426937/article/details/107386264

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网