SpringBoot + MyBatis-plus + SpringSecurity + JWT实现用户无状态请求验证(前后端分离)

1、基础技术框架

技术名称 版本
SpringBoot 2.1.9.RELEASE
MyBatis-plus 3.3.1
MySQL 8.0.11
SpringSecurity 5.1.6.RELEASE
jjwt 0.9.0
lombok 1.18.10
guava 30.1.1-jre
hutool-all 5.5.2
druid 1.2.3
swagger3 3.0
aop 1.9.4
fastjson 1.2.47

2、完整pom.xml 

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.zzg</groupId>
  <artifactId>Meta</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  
  	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.9.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<java.version>1.8</java.version>
		<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<!-- 集成Mybatis-plus -->
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.3.1</version>
		</dependency>

		<!-- 集成MySQL 驱动包 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.11</version>
		</dependency>

		<!-- 集成SpringSecurity 安全框架 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		
		<!-- 集成JWT-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>

		<!--集成lombok -->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<!-- 集成guava 工具包 -->
		<dependency>
			<groupId>com.google.guava</groupId>
			<artifactId>guava</artifactId>
			<version>30.1.1-jre</version>
		</dependency>
		<!-- 集成hutool-all 工具包 -->
		<dependency>
			<groupId>cn.hutool</groupId>
			<artifactId>hutool-all</artifactId>
			<version>5.5.2</version>
		</dependency>

		<!--druid 数据库连接池 -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid-spring-boot-starter</artifactId>
			<version>1.2.3</version>
		</dependency>
		<!-- swagger3 接口文档生成器 -->
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-boot-starter</artifactId>
			<version>3.0.0</version>
			<exclusions>
				<exclusion>
					<groupId>org.springframework.plugin</groupId>
					<artifactId>spring-plugin-core</artifactId>
				</exclusion>
				<exclusion>
					<groupId>org.springframework.plugin</groupId>
					<artifactId>spring-plugin-metadata</artifactId>
				</exclusion>
			</exclusions>
		</dependency>

		<dependency>
			<groupId>org.springframework.plugin</groupId>
			<artifactId>spring-plugin-core</artifactId>
			<version>2.0.0.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.plugin</groupId>
			<artifactId>spring-plugin-metadata</artifactId>
			<version>2.0.0.RELEASE</version>
		</dependency>

		<!-- 集成SpringAop -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>

		<!-- 集成Alibaba fastjson -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.47</version>
		</dependency>

	</dependencies>


	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

3、初始化脚本

/*
 Navicat MySQL Data Transfer
 Source Server         : 192.168.1.73
 Source Server Type    : MySQL
 Source Server Version : 80015
 Source Host           : 192.168.1.73:3306
 Source Schema         : banan_test
 Target Server Type    : MySQL
 Target Server Version : 80015
 File Encoding         : 65001
 Date: 05/11/2021 19:14:17
*/
 
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
 
-- ----------------------------
-- Table structure for t_sys_auth
-- ----------------------------
DROP TABLE IF EXISTS `t_sys_auth`;
CREATE TABLE `t_sys_auth`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `auth_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '权限名称',
  `permission` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '权限标识',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '系统权限' ROW_FORMAT = Dynamic;
 
-- ----------------------------
-- Records of t_sys_auth
-- ----------------------------
INSERT INTO `t_sys_auth` VALUES (1, '系统用户编辑权限', 'sys:user:edit');
INSERT INTO `t_sys_auth` VALUES (2, '系统用户查询权限', 'sys:user:view');
INSERT INTO `t_sys_auth` VALUES (3, '系统角色编辑权限', 'sys:role:edit');
INSERT INTO `t_sys_auth` VALUES (4, '系统角色查询权限', 'sys:role:view');
INSERT INTO `t_sys_auth` VALUES (5, '系统权限编辑权限', 'sys:auth:edit');
INSERT INTO `t_sys_auth` VALUES (6, '系统权限查询权限', 'sys:auth:view');
INSERT INTO `t_sys_auth` VALUES (7, '系统日志编辑权限', 'sys:log:edit');
INSERT INTO `t_sys_auth` VALUES (8, '系统日志查询权限', 'sys:log:view');
 
-- ----------------------------
-- Table structure for t_sys_log
-- ----------------------------
DROP TABLE IF EXISTS `t_sys_log`;
CREATE TABLE `t_sys_log`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `method` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '请求方法',
  `uri` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '请求地址',
  `params` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '请求参数',
  `time` bigint(10) NULL DEFAULT NULL COMMENT '请求时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
 
-- ----------------------------
-- Records of t_sys_log
-- ----------------------------
INSERT INTO `t_sys_log` VALUES (2, 'GET', '/log/listData', 'page=1&limit=10', 0);
INSERT INTO `t_sys_log` VALUES (3, 'GET', '/log/list', '', 0);
INSERT INTO `t_sys_log` VALUES (4, 'GET', '/log/listData', 'page=1&limit=10', 0);
INSERT INTO `t_sys_log` VALUES (5, 'GET', '/log/listData', 'page=1&limit=10&id=2', 0);
INSERT INTO `t_sys_log` VALUES (6, 'GET', '/log/listData', 'page=1&limit=10&id=', -1);
INSERT INTO `t_sys_log` VALUES (7, 'POST', '/log/delete', '[{\"id\":1}]', 0);
INSERT INTO `t_sys_log` VALUES (8, 'GET', '/log/listData', 'page=1&limit=10&id=', 0);
INSERT INTO `t_sys_log` VALUES (9, 'GET', '/log/list', '', 0);
INSERT INTO `t_sys_log` VALUES (10, 'GET', '/log/listData', 'page=1&limit=10', 0);
INSERT INTO `t_sys_log` VALUES (11, 'GET', '/log/list', '', 0);
INSERT INTO `t_sys_log` VALUES (12, 'GET', '/log/listData', 'page=1&limit=10', 0);
INSERT INTO `t_sys_log` VALUES (13, 'GET', '/log/list', '', 0);
INSERT INTO `t_sys_log` VALUES (14, 'GET', '/log/listData', 'page=1&limit=10', 0);
INSERT INTO `t_sys_log` VALUES (15, 'GET', '/log/listData', 'page=2&limit=10', 0);
INSERT INTO `t_sys_log` VALUES (16, 'GET', '/auth/list', '', 0);
INSERT INTO `t_sys_log` VALUES (17, 'GET', '/auth/listData', 'page=1&limit=10', 0);
INSERT INTO `t_sys_log` VALUES (18, 'GET', '/auth/bind', 'id=1', 0);
INSERT INTO `t_sys_log` VALUES (19, 'GET', '/auth/roleListData', 'authId=1&page=1&limit=10', 0);
INSERT INTO `t_sys_log` VALUES (20, 'GET', '/auth/list', '', 0);
INSERT INTO `t_sys_log` VALUES (21, 'GET', '/auth/listData', 'page=1&limit=10', 0);
INSERT INTO `t_sys_log` VALUES (22, 'GET', '/log/list', '', 0);
INSERT INTO `t_sys_log` VALUES (23, 'GET', '/log/listData', 'page=1&limit=10', 0);
INSERT INTO `t_sys_log` VALUES (24, 'GET', '/log/listData', 'page=3&limit=10', 0);
INSERT INTO `t_sys_log` VALUES (25, 'GET', '/log/listData', 'page=2&limit=10', 0);
INSERT INTO `t_sys_log` VALUES (26, 'GET', '/auth/list', '', 0);
INSERT INTO `t_sys_log` VALUES (27, 'GET', '/auth/listData', 'page=1&limit=10', 0);
INSERT INTO `t_sys_log` VALUES (28, 'GET', '/log/list', '', 0);
INSERT INTO `t_sys_log` VALUES (29, 'GET', '/log/listData', 'page=1&limit=10', 0);
INSERT INTO `t_sys_log` VALUES (30, 'GET', '/auth/list', '', -1);
INSERT INTO `t_sys_log` VALUES (31, 'GET', '/auth/listData', 'page=1&limit=10', 0);
INSERT INTO `t_sys_log` VALUES (32, 'GET', '/log/list', '', 0);
INSERT INTO `t_sys_log` VALUES (33, 'GET', '/log/listData', 'page=1&limit=10', 0);
 
-- ----------------------------
-- Table structure for t_sys_role
-- ----------------------------
DROP TABLE IF EXISTS `t_sys_role`;
CREATE TABLE `t_sys_role`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `role_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '角色名称',
  `role_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色编码',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '系统角色' ROW_FORMAT = Dynamic;
 
-- ----------------------------
-- Records of t_sys_role
-- ----------------------------
INSERT INTO `t_sys_role` VALUES (1, '普通员工', 'USER');
INSERT INTO `t_sys_role` VALUES (2, '项目经理', 'PM');
 
-- ----------------------------
-- Table structure for t_sys_role_auth
-- ----------------------------
DROP TABLE IF EXISTS `t_sys_role_auth`;
CREATE TABLE `t_sys_role_auth`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `role_id` bigint(20) NULL DEFAULT NULL COMMENT '角色ID',
  `auth_id` bigint(20) NULL DEFAULT NULL COMMENT '权限ID',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '角色权限关系' ROW_FORMAT = Dynamic;
 
-- ----------------------------
-- Records of t_sys_role_auth
-- ----------------------------
INSERT INTO `t_sys_role_auth` VALUES (1, 2, 1);
INSERT INTO `t_sys_role_auth` VALUES (2, 1, 2);
INSERT INTO `t_sys_role_auth` VALUES (3, 2, 2);
INSERT INTO `t_sys_role_auth` VALUES (4, 2, 3);
INSERT INTO `t_sys_role_auth` VALUES (5, 1, 4);
INSERT INTO `t_sys_role_auth` VALUES (6, 2, 4);
INSERT INTO `t_sys_role_auth` VALUES (7, 2, 5);
INSERT INTO `t_sys_role_auth` VALUES (8, 1, 6);
INSERT INTO `t_sys_role_auth` VALUES (9, 2, 6);
INSERT INTO `t_sys_role_auth` VALUES (10, 2, 7);
INSERT INTO `t_sys_role_auth` VALUES (11, 2, 8);
 
-- ----------------------------
-- Table structure for t_sys_user
-- ----------------------------
DROP TABLE IF EXISTS `t_sys_user`;
CREATE TABLE `t_sys_user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户名称',
  `nickname` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户昵称',
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户密码',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '系统用户' ROW_FORMAT = Dynamic;
 
-- ----------------------------
-- Records of t_sys_user
-- ----------------------------
INSERT INTO `t_sys_user` VALUES (1, 'user', 'C3Stones', '$2a$10$RFjGxPxq8EZokh89z.DcIeSpBJHfeRozfXUZSHnfN14bb94JKVRia');
INSERT INTO `t_sys_user` VALUES (2, 'system', '管理员', '$2a$10$RFjGxPxq8EZokh89z.DcIeSpBJHfeRozfXUZSHnfN14bb94JKVRia');
 
-- ----------------------------
-- Table structure for t_sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `t_sys_user_role`;
CREATE TABLE `t_sys_user_role`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户ID',
  `role_id` bigint(20) NULL DEFAULT NULL COMMENT '角色ID',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户角色关系' ROW_FORMAT = Dynamic;
 
-- ----------------------------
-- Records of t_sys_user_role
-- ----------------------------
INSERT INTO `t_sys_user_role` VALUES (1, 1, 1);
INSERT INTO `t_sys_user_role` VALUES (2, 2, 2);
 
SET FOREIGN_KEY_CHECKS = 1;

4、项目结构截图:

SpringBoot + MyBatis-plus + SpringSecurity + JWT实现用户无状态请求验证(前后端分离)

 5、springsecurity handler 定义

package com.zzg.security.handler;

import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
import com.zzg.common.vo.Response;
import cn.hutool.http.HttpStatus;

/**
 * 无权限处理类
 * 
 * @author zzg
 *
 */
@Component
public class UserAccessDeniedHandler implements AccessDeniedHandler {

	@Override
	public void handle(HttpServletRequest request, HttpServletResponse response,
			AccessDeniedException accessDeniedException) throws IOException {
		Response<String> responseBody  = Response.error(HttpStatus.HTTP_UNAUTHORIZED, "Unauthorized", "用户未认证");
		response.getWriter().write(JSON.toJSONString(responseBody));		
	}

}
package com.zzg.security.handler;

import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
import com.zzg.common.vo.Response;
import cn.hutool.http.HttpStatus;

/**
 * 登录失败处理类
 * 
 * @author zzg
 *
 */
@Component
public class UserLoginFailureHandler implements AuthenticationFailureHandler {

	@Override
	public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException exception) throws IOException {
		Response<String> responseBody  = Response.error(HttpStatus.HTTP_BAD_REQUEST, "Bad Request", "请求失败");
		response.getWriter().write(JSON.toJSONString(responseBody));	
	}
}
package com.zzg.security.handler;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
import com.zzg.common.vo.Response;
import com.zzg.security.entity.UserDetails;
import com.zzg.security.jwt.JwtTokenUtil;
import cn.hutool.http.HttpStatus;

/**
 * 登录成功处理类
 * 
 * @author zzg
 *
 */
@Component
public class UserLoginSuccessHandler implements AuthenticationSuccessHandler {

	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) throws IOException {
		UserDetails userDetails = (UserDetails) authentication.getPrincipal();

		Map<String, Object> paramter = new HashMap<String, Object>();
		paramter.put("username", userDetails.getUsername());
		String jwtToken = JwtTokenUtil.createToken(paramter);

		Response<String> responseBody  = Response.error(HttpStatus.HTTP_OK, "Login Success", jwtToken);
		response.getWriter().write(JSON.toJSONString(responseBody));
	}
}
package com.zzg.security.handler;

import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
import com.zzg.common.vo.Response;
import cn.hutool.http.HttpStatus;

/**
 * 登出成功处理类
 * 
 * @author zzg
 *
 */
@Component
public class UserLogoutSuccessHandler implements LogoutSuccessHandler {

	@Override
	public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
			throws IOException {
		Response<String> responseBody  = Response.error(HttpStatus.HTTP_OK, "Logout Success", "用户退出成功");
		response.getWriter().write(JSON.toJSONString(responseBody));		
	}
}
package com.zzg.security.handler;

import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
import com.zzg.common.vo.Response;
import cn.hutool.http.HttpStatus;

/**
 * 未登录处理类
 * 
 * @author zzg
 *
 */
@Component
public class UserNotLoginHandler implements AuthenticationEntryPoint {

	@Override
	public void commence(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException authException) throws IOException {
		Response<String> responseBody  = Response.error(HttpStatus.HTTP_UNAUTHORIZED, "Unauthorized", "用户未认证");
		response.getWriter().write(JSON.toJSONString(responseBody));	
	}
}

6、springsecurity UserDetails 定义

package com.zzg.security.entity;

import java.io.Serializable;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import com.zzg.sys.entity.User;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 系统用户详情
 * 
 * @author zzg
 *
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class UserDetails extends User
		implements org.springframework.security.core.userdetails.UserDetails, Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * 用户角色
	 */
	private Collection<GrantedAuthority> authorities;

	/**
	 * 账号是否过期
	 */
	private boolean isAccountNonExpired = false;

	/**
	 * 账号是否锁定
	 */
	private boolean isAccountNonLocked = false;

	/**
	 * 证书是否过期
	 */
	private boolean isCredentialsNonExpired = false;

	/**
	 * 账号是否有效
	 */
	private boolean isEnabled = true;

	/**
	 * 获得用户权限
	 */
	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		return authorities;
	}

	/**
	 * 判断账号是否过期
	 */
	@Override
	public boolean isAccountNonExpired() {
		return isAccountNonExpired;
	}

	/**
	 * 判断账号是否锁定
	 */
	@Override
	public boolean isAccountNonLocked() {
		return isAccountNonLocked;
	}

	/**
	 * 判断证书是否过期
	 */
	@Override
	public boolean isCredentialsNonExpired() {
		return isCredentialsNonExpired;
	}

	/**
	 * 判断账号是否有效
	 */
	@Override
	public boolean isEnabled() {
		return isEnabled;
	}

}

7、springsecurity UserDetailsService定义

package com.zzg.security.service;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zzg.security.entity.UserDetails;
import com.zzg.sys.entity.Role;
import com.zzg.sys.entity.User;
import com.zzg.sys.service.RoleService;
import com.zzg.sys.service.UserService;

/**
 * 用户登录Service
 * 
 * @author zzg
 *
 */
@Service
public class UserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService {

	@Autowired
	private UserService userService;

	@Autowired
	private RoleService roleService;

	/**
	 * 根据用户名查用户信息
	 * 
	 * @param username 用户名称
	 * @return 用户详细信息
	 */
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		QueryWrapper<User> queryWrapper = new QueryWrapper<>();
		queryWrapper.eq("username", username);
		User user = userService.getOne(queryWrapper);
		if (user != null) {
			UserDetails userDetails = new UserDetails();
			BeanUtils.copyProperties(user, userDetails);

			// 用户角色
			Set<GrantedAuthority> authorities = new HashSet<>();

			// 查询用户角色
			List<Role> roleList = roleService.findByUserId(userDetails.getId());
			roleList.forEach(role -> {
				authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getRoleCode()));
			});

			userDetails.setAuthorities(authorities);

			return userDetails;
		}
		return null;
	}

}

8、springsecurity AuthenticationProvider定义

package com.zzg.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;
import com.zzg.security.entity.UserDetails;
import com.zzg.security.service.UserDetailsService;
import cn.hutool.core.util.StrUtil;

/**
 * 用户登录验证处理类
 * 
 * @author zzg
 *
 */
@Component
public class UserAuthenticationProvider implements AuthenticationProvider {

	@Autowired
	private UserDetailsService userDetailsService;

	/**
	 * 身份验证
	 */
	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		// 获取用户名
		String username = (String) authentication.getPrincipal();
		// 获取密码
		String password = (String) authentication.getCredentials();

		UserDetails userDetails = (UserDetails) userDetailsService.loadUserByUsername(username);
		if (userDetails == null) {
			throw new UsernameNotFoundException("用户名不存在");
		}

		if (!StrUtil.equals(username, userDetails.getUsername())
				|| !new BCryptPasswordEncoder().matches(password, userDetails.getPassword())) {
			throw new BadCredentialsException("用户名或密码错误");
		}

		return new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
	}

	/**
	 * 支持指定的身份验证
	 */
	@Override
	public boolean supports(Class<?> authentication) {
		return true;
	}

}

9. jwt 工具类定义

package com.zzg.security.jwt;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

public class JwtTokenUtil {
	/** 密钥secret */
	private static String secret = "meta";

	/** 过期时间,单位为秒 */
	private static long expire = 7 * 24 * 60 * 60;

	/**
	 * 解析token
	 * 
	 * @param jsonWebToken
	 * @return
	 */
	public static Claims parseToken(String token) {
		Claims claims = null;
		try {
			claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
		} catch (Exception e) {
			return claims;
		}

		return claims;
	}

	/**
	 * 新建token
	 * 
	 * @param audience
	 * @param issuer
	 * 
	 * @return
	 */
	public static String createToken(Map<String, Object> paramter) {
		if (Objects.isNull(paramter)) {
			paramter = new HashMap<>();
		}
		// 过期时间
		Date expireDate = new Date(System.currentTimeMillis() + expire * 1000);

		return Jwts.builder().setHeaderParam("typ", "JWT") // 设置头部信息
				.setClaims(paramter) // 装入自定义的用户信息
				.setExpiration(expireDate) // token过期时间
				.signWith(SignatureAlgorithm.HS512, secret) // 密钥
				.compact();

	}

	/**
	 * 刷新token
	 * 
	 * @param audience
	 * @param issuer
	 * 
	 * @return
	 */
	public static String referToken(Map<String, Object> paramter) {
		if (Objects.isNull(paramter)) {
			paramter = new HashMap<>();
		}
		// 过期时间
		Date expireDate = new Date(System.currentTimeMillis() + expire * 1000);

		return Jwts.builder().setHeaderParam("typ", "JWT") // 设置头部信息
				.setClaims(paramter) // 装入自定义的用户信息
				.setExpiration(expireDate) // token过期时间
				.signWith(SignatureAlgorithm.HS512, secret) // 密钥
				.compact();

	}

}

10.jwt 凭证验证拦截器

package com.zzg.security.filter;

import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import com.zzg.security.entity.UserDetails;
import com.zzg.security.jwt.JwtTokenUtil;
import com.zzg.security.service.UserDetailsService;

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
	 @Autowired
	 UserDetailsService userDetailsService;

	    @Override
	    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
	        String authHeader = request.getHeader("Authorization");

	        if (authHeader != null && authHeader.startsWith("Bearer")) {
	            final String authToken = authHeader.substring("Bearer".length());

	            String username = (String) JwtTokenUtil.parseToken(authToken).get("username");

	            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
	                UserDetails userDetails = userDetailsService.loadUserByUsername(username);

	                if (userDetails != null) {
	                    UsernamePasswordAuthenticationToken authentication =
	                            new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
	                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

	                    SecurityContextHolder.getContext().setAuthentication(authentication);
	                }
	            }
	        }

	        chain.doFilter(request, response);
	    }

}

11、springsecurity 配置对象

package com.zzg.security.conf;

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.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.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.web.authentication.UsernamePasswordAuthenticationFilter;
import com.zzg.security.UserAuthenticationProvider;
import com.zzg.security.filter.JwtAuthenticationTokenFilter;
import com.zzg.security.handler.UserAccessDeniedHandler;
import com.zzg.security.handler.UserLoginFailureHandler;
import com.zzg.security.handler.UserLoginSuccessHandler;
import com.zzg.security.handler.UserLogoutSuccessHandler;
import com.zzg.security.handler.UserNotLoginHandler;

/**
 * 系统安全核心配置
 * 
 * @author zzg
 *
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
	public static final Logger logger = LoggerFactory.getLogger(WebSecurityConfig.class);

	/**
	 * 无权限处理类
	 */
	@Autowired
	private UserAccessDeniedHandler userAccessDeniedHandler;

	/**
	 * 用户未登录处理类
	 */
	@Autowired
	private UserNotLoginHandler userNotLoginHandler;

	/**
	 * 用户登录成功处理类
	 */
	@Autowired
	private UserLoginSuccessHandler userLoginSuccessHandler;

	/**
	 * 用户登录失败处理类
	 */
	@Autowired
	private UserLoginFailureHandler userLoginFailureHandler;

	/**
	 * 用户登出成功处理类
	 */
	@Autowired
	private UserLogoutSuccessHandler userLogoutSuccessHandler;

	/**
	 * 用户登录验证
	 */
	@Autowired
	private UserAuthenticationProvider userAuthenticationProvider;

	/**
	 * jwt 拦截器
	 */
	@Autowired
	JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

	/**
	 * 加密方式
	 * 
	 * @return
	 */
	@Bean
	public BCryptPasswordEncoder bCryptPasswordEncoder() {
		return new BCryptPasswordEncoder();
	}

	
	/**
	 * 用户登录验证
	 */
	@Override
	protected void configure(AuthenticationManagerBuilder auth) {
		auth.authenticationProvider(userAuthenticationProvider);
	}

	/**
	 * 安全权限配置
	 */
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		  // 去掉 CSRF
        http.csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 使用 JWT,关闭token
                .and()
                .httpBasic().authenticationEntryPoint(userNotLoginHandler)
                .and()
                .authorizeRequests()

//                .anyRequest()
//                .access("@rbacauthorityservice.hasPermission(request,authentication)") // RBAC 动态 url 认证

                .and()
                .formLogin()  //开启登录
                .successHandler(userLoginSuccessHandler) // 登录成功
                .failureHandler(userLoginFailureHandler) // 登录失败
                .permitAll()

                .and()
                .logout()
                .logoutSuccessHandler(userLogoutSuccessHandler)
                .permitAll();



        http.exceptionHandling().accessDeniedHandler(userAccessDeniedHandler); // 无权访问 JSON 格式的数据
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); // JWT Filter
	}
}

12、PostMan模拟前后端分离效果截图

SpringBoot + MyBatis-plus + SpringSecurity + JWT实现用户无状态请求验证(前后端分离)

 SpringBoot + MyBatis-plus + SpringSecurity + JWT实现用户无状态请求验证(前后端分离)

 13、项目源码

上一篇:python 实现JWT


下一篇:SpringCloud鉴权