效果图
总体页面
表单填写完成
验证码错误
密码或用户名错误
登录成功
后端
建表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
如对本文有疑问, 点击进行留言回复!!
before社区电量是什么意思 Before社区电量获得方法
RecycleView入门详解(教你全面掌握RecycleView用法)
动态权限请求框架RxPermissions(几行代码搞定权限)
URL路径@PathVariable出现点号“.“时值遭截断问题
网友评论