spring-boot+mybatis-plus+spring-security+jwt+OAuth+redis认证授权
1.首先了解oauth是什么东西,具体是用来干嘛的。
OAuth是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用,目前的版本是2.0版。
本文对OAuth 2.0的设计思路和运行流程,做一个简明通俗的解释。http://en.wikipedia.org/wiki/OAuth
网上看到写的很好的博客:http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html
2.搭建一个spring-boot工程
1.我的目录结构如下
一个个的操作的话篇幅太多,不使用作者这种,一个spring-boot工程也是一样的。
2.添加必要的依赖
mydome的maven包,一般创建springboot项目时会自动帮我们生成的
<?xml version="1.0" encoding="UTF-8"?> <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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <packaging>pom</packaging> <modules> <module>com-dome-provider</module> <module>com-dome-model</module> <module>com-dome-common</module> <module>com-dome-biz</module> </modules> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.4.0</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>com</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <build> <pluginManagement> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.4.0</version> <configuration> <mainClass>com.dome.provider.App</mainClass> </configuration> <executions> <execution> <goals> <goal>build-info</goal> </goals> </execution> </executions> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <target>1.8</target> <source>1.8</source> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </pluginManagement> </build> </project>
com-dome-model模块的maven
<?xml version="1.0" encoding="UTF-8"?> <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"> <parent> <artifactId>demo</artifactId> <groupId>com</groupId> <version>0.0.1-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>com-dome-model</artifactId> <dependencies> <!--swagger--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <!--swagger ui--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency> <!--msql--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!--校验工具--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <!-- 代码生成器--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.3.0</version> </dependency> <!-- 模板--> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.1</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-extension</artifactId> <version>3.1.0</version> </dependency> </dependencies> </project>
com-dome-common模块
<?xml version="1.0" encoding="UTF-8"?> <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"> <parent> <artifactId>demo</artifactId> <groupId>com</groupId> <version>0.0.1-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>com-dome-common</artifactId> <dependencies> <dependency> <groupId>com</groupId> <artifactId>com-dome-model</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--redis集成--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- jjwt--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <!-- 支持 @ConfigurationProperties 注解 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <!--aop--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!-- UserAgentUtils --> <dependency> <groupId>eu.bitwalker</groupId> <artifactId>UserAgentUtils</artifactId> <version>1.21</version> </dependency> <!-- 获取系统信息 --> <dependency> <groupId>com.github.oshi</groupId> <artifactId>oshi-core</artifactId> <version>4.4.2</version> </dependency> <!-- json--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.71</version> </dependency> <!--验证码--> <dependency> <groupId>com.github.axet</groupId> <artifactId>kaptcha</artifactId> <version>0.0.9</version> </dependency> <!-- excel--> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>3.9</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>3.9</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>io.swagger</groupId> <artifactId>swagger-annotations</artifactId> <version>1.5.20</version> <scope>compile</scope> </dependency> <dependency> <groupId>jakarta.validation</groupId> <artifactId>jakarta.validation-api</artifactId> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>20.0</version> <scope>compile</scope> </dependency> </dependencies> </project>
com-dome-biz模块
<?xml version="1.0" encoding="UTF-8"?> <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"> <parent> <artifactId>demo</artifactId> <groupId>com</groupId> <version>0.0.1-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>com-dome-biz</artifactId> <dependencies> <dependency> <groupId>com</groupId> <artifactId>com-dome-model</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com</groupId> <artifactId>com-dome-common</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> </dependencies> <build> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> </resource> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> </resources> </build> </project>
com-dome-privoder模块
<?xml version="1.0" encoding="UTF-8"?> <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"> <parent> <artifactId>demo</artifactId> <groupId>com</groupId> <version>0.0.1-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>com-dome-provider</artifactId> <dependencies> <dependency> <groupId>com</groupId> <artifactId>com-dome-model</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com</groupId> <artifactId>com-dome-common</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>com</groupId> <artifactId>com-dome-biz</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <!-- 热部署--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> <!-- 安全框架--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-extension</artifactId> <version>3.3.0</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-extension</artifactId> <version>3.3.0</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-extension</artifactId> <version>3.1.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <mainClass>com.dome.provider.App</mainClass> <finalName>com-dome-provider</finalName> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.21.0</version> <configuration> <testFailureIgnore>true</testFailureIgnore> </configuration> </plugin> </plugins> </build> </project>
添加一个有趣的banner.txt,在com-dome-provider的resources中
// _ooOoo_ // // o8888888o // // 88" . "88 // // (| ^_^ |) // // O\ = /O // // ____/`---'\____ // // .' \\| |// `. // // / \\||| : |||// \ // // / _||||| -:- |||||- \ // // | | \\\ - /// | | // // | \_| ''\---/'' | | // // \ .-\__ `-` ___/-. / // // ___`. .' /--.--\ `. . ___ // // ."" '< `.___\_<|>_/___.' >'"". // // | | : `- \`.;`\ _ /`;.`/ - ` : | | // // \ \ `-. \_ __\ /__ _/ .-` / / // // ========`-.____`-.___\_____/___.-`____.-'======== // // `=---=' // // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // // 佛祖保佑 永不宕机 永无BUG //
项目大致的结构已经搭建完成
3.添加一下工具类和配置信息
在@SpringBootApplication加上要扫描的包目录和@MapperScan指向存放dao的目录,添加yml配置。
server: port: 8080 spring: #时间戳统一转换 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 #数据库配置 datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/sprin_security?useSSL=false&useUnicode=true&nullCatalogMeansCurrent=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&maxReconnects=10 password: yourpass username: root hikari: minimum-idle: 1 maximum-pool-size: 10 redis: host: 127.0.0.1 port: 6379 password: yourpass jedis: pool: # 连接池最大连接数(使用负值表示没有限制) max-active: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制) max-wait: 10000ms # 连接池中的最大空闲连接 max-idle: 8 # 连接池中的最小空闲连接 min-idle: 0 # 连接超时时间(毫秒) timeout: 10000ms servlet: multipart: # spring-boot 最大上传文件大小 max-file-size: 30MB max-request-size: 30MB aop: auto: true mybatis-plus: configuration: #配置日志 默认控制台输出 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-value: 1 logic-not-delete-value: 0 mapper-locations: classpath*:/mapper/**/*.xml type-aliases-package: com.dome.model.po jwt: tokenHeader: Authorization secret: iwqjhda8232bjgh432 #JWT的超期限时间(60*60*24*7) expiration: 604800 tokenHead: 'Bearer '
4.数据库
作者使用的是MySQL5.7的数据库,用完这些就可以启动了。
/* Navicat Premium Data Transfer Source Server : 阿里云 Source Server Type : MySQL Source Server Version : 50724 Source Host : 127.0.0.1:3306 Source Schema : sprin_security Target Server Type : MySQL Target Server Version : 50724 File Encoding : 65001 Date: 30/11/2020 16:01:47 */ SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for sys_menu -- ---------------------------- DROP TABLE IF EXISTS `sys_menu`; CREATE TABLE `sys_menu` ( `id` bigint(36) NOT NULL AUTO_INCREMENT, `type` tinyint(2) NULL DEFAULT 1 COMMENT '菜单类型 1 = 1级 2 = 2级.......', `parent` bigint(36) NULL DEFAULT 0 COMMENT '父级菜单id', `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '菜单名称', `url` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '后台生成URL', `icon` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '菜单图标icon', `permission` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '权限 sys:user:query = 系统管理:用户管理:查询', `sort` bigint(36) NULL DEFAULT NULL COMMENT '排序', `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间', `create_name` bigint(36) NULL DEFAULT NULL COMMENT '创建人', `update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '修改时间', `update_name` bigint(36) NULL DEFAULT NULL COMMENT '修改人', `version` bigint(36) NULL DEFAULT 1 COMMENT '版本号', `del_flag` tinyint(1) NULL DEFAULT 0 COMMENT '逻辑删除0 = 存在 1 = 删除', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '菜单表' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of sys_menu -- ---------------------------- INSERT INTO `sys_menu` VALUES (1, 1, 0, '系统管理', '/sys', NULL, 'sys', NULL, '2020-11-19 15:36:32', 1, '2020-11-19 15:38:16', 1, 1, 0); INSERT INTO `sys_menu` VALUES (2, 2, 1, '用户管理', '/sys/user', NULL, 'sys:user', NULL, '2020-11-19 15:36:56', 1, '2020-11-19 15:38:16', 1, 1, 0); INSERT INTO `sys_menu` VALUES (3, 3, 2, '新增用户', '/sys/user/add', NULL, 'sys:user:add', NULL, '2020-11-19 15:37:30', 1, '2020-11-19 15:38:16', 1, 1, 0); INSERT INTO `sys_menu` VALUES (4, 3, 2, '查询', '/sys/user/query', NULL, 'sys:user:query', NULL, '2020-11-19 15:38:10', 1, '2020-11-19 15:38:16', 1, 1, 0); INSERT INTO `sys_menu` VALUES (6, 3, 2, '删除', '/sys/user/del', NULL, 'sys:user:del', NULL, '2020-11-19 15:38:43', 1, '2020-11-19 15:38:43', 1, 1, 0); INSERT INTO `sys_menu` VALUES (7, 3, 2, '详情', '/sys/user/findById', NULL, 'sys:user:findById', NULL, '2020-11-27 09:48:43', NULL, '2020-11-27 09:48:59', NULL, 1, 0); -- ---------------------------- -- Table structure for sys_role -- ---------------------------- DROP TABLE IF EXISTS `sys_role`; CREATE TABLE `sys_role` ( `id` bigint(36) NOT NULL AUTO_INCREMENT, `code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色编码', `name` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '角色名称', `role_describe` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色描述', `status` tinyint(1) NULL DEFAULT 0 COMMENT '角色状态0 = 启用 = 禁用', `create_name` bigint(36) NULL DEFAULT NULL COMMENT '创建人', `update_name` bigint(36) NULL DEFAULT NULL COMMENT '修改人', `version` bigint(36) NULL DEFAULT 1 COMMENT '版本号', `del_flag` tinyint(1) NULL DEFAULT 0 COMMENT '逻辑删除0 = 存在 1 = 删除', `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间', `update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '修改时间', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色表' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of sys_role -- ---------------------------- INSERT INTO `sys_role` VALUES (1, 'gly', '管理员', '系统管理员', 0, 1, 1, 1, 0, '2020-11-19 15:35:33', '2020-11-19 15:35:33'); -- ---------------------------- -- Table structure for sys_role_menu -- ---------------------------- DROP TABLE IF EXISTS `sys_role_menu`; CREATE TABLE `sys_role_menu` ( `id` bigint(36) NOT NULL AUTO_INCREMENT, `role_id` bigint(20) NULL DEFAULT NULL COMMENT '角色id', `menu_id` bigint(20) NULL DEFAULT NULL COMMENT '菜单id', `create_name` bigint(255) NULL DEFAULT NULL COMMENT '创建人', `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色菜单中间表' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of sys_role_menu -- ---------------------------- INSERT INTO `sys_role_menu` VALUES (1, 1, 1, 1, '2020-11-19 15:40:05'); INSERT INTO `sys_role_menu` VALUES (2, 1, 2, 1, '2020-11-19 15:40:07'); INSERT INTO `sys_role_menu` VALUES (3, 1, 3, 1, '2020-11-19 15:40:11'); INSERT INTO `sys_role_menu` VALUES (4, 1, 4, 1, '2020-11-19 15:40:12'); INSERT INTO `sys_role_menu` VALUES (5, 1, 6, 1, '2020-11-19 15:40:13'); INSERT INTO `sys_role_menu` VALUES (7, 1, 7, 1, '2020-11-30 15:06:04'); -- ---------------------------- -- Table structure for sys_user -- ---------------------------- DROP TABLE IF EXISTS `sys_user`; CREATE TABLE `sys_user` ( `id` bigint(36) NOT NULL AUTO_INCREMENT, `user_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户名', `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '密码', `nick_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户昵称', `phone` decimal(12, 0) NULL DEFAULT NULL COMMENT '手机号', `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱', `status` tinyint(1) NULL DEFAULT NULL COMMENT '用户状态 0 = 启用 1 = 禁用', `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间', `create_name` bigint(36) NULL DEFAULT NULL COMMENT '创建人id', `update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '修改时间', `update_name` bigint(36) NULL DEFAULT NULL COMMENT '修改人id', `version` bigint(36) NULL DEFAULT 1 COMMENT '版本号 ', `del_flag` tinyint(1) NULL DEFAULT 0 COMMENT '逻辑删除0 = 存在 1 = 删除', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of sys_user -- ---------------------------- INSERT INTO `sys_user` VALUES (1, 'admin', '$2a$10$Fu0pt5Mf1ggTgVXM32GHmuBWntgp8a1WyVEgufwzuzlgoXT.Ou7aK', '管理员', 1111111111, '1111@qq.com', 0, '2020-11-20 15:02:31', 1, '2020-11-27 11:24:39', 1, 1, 0); -- ---------------------------- -- Table structure for sys_user_role -- ---------------------------- DROP TABLE IF EXISTS `sys_user_role`; CREATE TABLE `sys_user_role` ( `id` bigint(36) NOT NULL AUTO_INCREMENT, `user_id` bigint(36) NULL DEFAULT NULL COMMENT '用户id', `role_id` bigint(36) NULL DEFAULT NULL COMMENT '角色id', `create_name` bigint(255) NULL DEFAULT NULL COMMENT '创建人', `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户角色中间表' ROW_FORMAT = DYNAMIC; -- ---------------------------- -- Records of sys_user_role -- ---------------------------- INSERT INTO `sys_user_role` VALUES (1, 1, 1, 1, '2020-11-19 15:41:40'); SET FOREIGN_KEY_CHECKS = 1;
3.用到的一些工具类和文件
mybatis-plus的配置和代码生成配置:https://www.cnblogs.com/yiMro/p/13529094.html
jwt工具类:https://www.cnblogs.com/yiMro/p/14061996.html
全局异常捕获:https://www.cnblogs.com/yiMro/p/13672652.html
redis工具:https://www.cnblogs.com/yiMro/p/13529150.html
客服端硬盘号工具
package com.dome.common.utils; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; /** * @author YinXiaoWei * @date 2020/11/25 16:00 * 获取客户端硬盘号 */ public class HdSerialInfoUtil { public static String getHdSerialInfo() { String line = ""; //定义变量 硬盘序列号 String HdSerial = ""; try { //获取命令行参数 Process proces = Runtime.getRuntime().exec("cmd /c dir c:"); BufferedReader buffreader = new BufferedReader(new InputStreamReader(proces.getInputStream(),"gbk")); while ((line = buffreader.readLine()) != null) { //读取参数并获取硬盘序列号 if (line.indexOf("卷的序列号是 ") != -1) { HdSerial = line.substring(line.indexOf("卷的序列号是 ") + "卷的序列号是 ".length(), line.length()); break; } } } catch (IOException e) { e.printStackTrace(); } return HdSerial; } }
客服端ip工具
package com.dome.common.utils; import eu.bitwalker.useragentutils.Browser; import eu.bitwalker.useragentutils.UserAgent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.http.HttpServletRequest; import java.net.InetAddress; import java.net.UnknownHostException; /** * @author YinXiaoWei * @date 2020/11/25 15:34 * ip工具类 */ public class IpUtil { private static final Logger logger = LoggerFactory.getLogger(IpUtil.class); /** * 获取客户端IP地址 * @param request * @return */ public static String getIpAddr(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) { logger.error("获取用户的主机发生异常",e); } ipAddress = inet.getHostAddress(); } } // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 ***.***.***.***".length() int ipAddressMax = 15; if (ipAddress != null && ipAddress.length() > ipAddressMax) { // = 15 if (ipAddress.indexOf(",") > 0) { ipAddress = ipAddress.substring(0, ipAddress.indexOf(",")); } } } catch (Exception e) { ipAddress=""; logger.error("获取用户的ip地址发生异常",e); } //服务端和客户端在一台机器 if(ipAddress.toString().contains( "0:0:0:0:0:0:0:1")) { ipAddress = "127.0.0.1"; } return ipAddress; } /** * 获取浏览器信息 * @param request * @return */ public static String getBrowser(HttpServletRequest request){ String ua = request.getHeader("User-Agent"); //转成UserAgent对象 UserAgent userAgent = UserAgent.parseUserAgentString(ua); Browser browser = userAgent.getBrowser(); //浏览器名 return browser.toString(); } }
验证码生成器
package com.dome.provider.config; import com.google.code.kaptcha.impl.DefaultKaptcha; import com.google.code.kaptcha.util.Config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.Properties; /** * @author YinXiaoWei * @date 2020/11/25 15:55 * 图片验证码配置 */ @Configuration public class KaptchaConfig { @Bean public DefaultKaptcha producer(){ Properties p =new Properties(); //是否有边框 p.put("kaptcha.border","no"); //字体颜色 p.put("kaptcha.textproducer.font.color","black"); //图片宽度 p.put("kaptcha.image.width","135"); //使用哪些字符生成验证码 p.put("kaptcha.textproducer.char.string","ABCDEFHKIJZPYRSTWXabcdefhkijzprstwx0123456789"); //图片高度 p.put("kaptcha.image.height","50"); //字体大小 p.put("kaptcha.textproducer.font.size","43"); //字体颜色 p.put("kaptcha.noise.color","blue"); //字符的个数 p.put("kaptcha.textproducer.char.length","4"); //字体 p.put("kaptcha.textproducer.font.names","Arial"); Config config =new Config(p); DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); defaultKaptcha.setConfig(config); return defaultKaptcha; } }
package com.dome.common.utils; import com.google.code.kaptcha.Producer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.imageio.ImageIO; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import java.awt.image.BufferedImage; import java.io.IOException; /** * @author YinXiaoWei * @date 2020/11/25 15:43 * 验证码图片的工具类 */ @Component public class VerifyCodeUtil { private static final String AUTH_NAME = "AUTH_NAME"; @Autowired private Producer producer; @Autowired private RedisUtil redisUtil; /** * 生成验证码 * @return */ public String getAuthCode() { String id = HdSerialInfoUtil.getHdSerialInfo(); if (redisUtil.hasKey(AUTH_NAME + id)) { redisUtil.delKey(AUTH_NAME + id); } String code = producer.createText().toLowerCase(); redisUtil.setObject(AUTH_NAME + id, code); return code; } /** * 校验验证码 * @param code * @return */ public boolean verifyAuthCode(String code) { String authCode = code.toLowerCase(); String id = HdSerialInfoUtil.getHdSerialInfo(); if (!redisUtil.hasKey(AUTH_NAME + id)) { return false; } if (!redisUtil.getObject(AUTH_NAME + id).toString().trim().equals(authCode)) { return false; } return true; } /** * 返回图片 io流 * @param response * @param code */ public void backImg(HttpServletResponse response, String code) { BufferedImage img = producer.createImage(code); ServletOutputStream outputStream = null; try { outputStream = response.getOutputStream(); ImageIO.write(img,"jpg", outputStream); } catch (IOException e) { throw new RuntimeException("生成图片验证码错误!"); } } }
在resources添加logback.xml文件用于生成日志文件
<?xml version="1.0" encoding="UTF-8"?> <configuration debug="false"> <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径--> <property name="LOG_HOME" value="/Logs"/> <!-- 控制台输出 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> </encoder> </appender> <!-- 按照每天生成日志文件 --> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!--日志文件输出的文件名--> <FileNamePattern>${LOG_HOME}/com-dome-provider.log.%d{yyyy-MM-dd}.log</FileNamePattern> <!--日志文件保留天数--> <MaxHistory>30</MaxHistory> </rollingPolicy> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> </encoder> <!--日志文件最大的大小--> <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"> <MaxFileSize>10MB</MaxFileSize> </triggeringPolicy> </appender> <!-- 出错日志 appender --> <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!-- 按天回滚 daily --> <!-- log.dir 在maven profile里配置 --> <FileNamePattern>${LOG_HOME}/com-dome-provider-error-log.%d{yyyy-MM-dd}.log</FileNamePattern> <!-- 日志最大的历史 60天 --> <maxHistory>60</maxHistory> </rollingPolicy> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>WARN</level> </filter> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> </encoder> </appender> <!-- show parameters for hibernate sql 专为 Hibernate 定制 --> <logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE"/> <logger name="org.hibernate.type.descriptor.sql.BasicExtractor" level="DEBUG"/> <logger name="org.hibernate.SQL" level="DEBUG"/> <logger name="org.hibernate.engine.QueryParameters" level="DEBUG"/> <logger name="org.hibernate.engine.query.HQLQueryPlan" level="DEBUG"/> <!--myibatis log configure--> <logger name="com.apache.ibatis" level="TRACE"/> <logger name="java.sql.Connection" level="DEBUG"/> <logger name="java.sql.Statement" level="DEBUG"/> <logger name="java.sql.PreparedStatement" level="DEBUG"/> <logger name="com.bool.order.meeting.core.mapper" level="DEBUG"></logger> <!-- 日志输出级别 --> <root level="INFO"> <appender-ref ref="STDOUT"/> <appender-ref ref="FILE"/> <appender-ref ref="ERROR_FILE" /> </root> </configuration>
4.添加controller、service、mapper层级代码代码
添加各个层级的代码,建议自己手扣好一点。修改字段生成的代码:
1.实体类中跟数据库tinyint位数为一的字段会是布尔类型改为Integer类型。
2.在时间字段上加上@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")==>用于处理时间格式
3.controller类上加上@Slf4j日志 采用lombok提供的@RequiredArgsConstructor(onConstructor = @__(@Autowired) 并使用(final)的方式注入 private final RoleService roleService;
4.@MapperScan和yml中mybatis-plus下的mapper-lications、type-aliases-package一定要指向正确,不然就会出现xxx找不到的问题。
5.如果确定配置没有写错还是出现xxx找不到的问题(作者是自动生成的所以不存在层级关系和代码名称的问题),那看一下xml文件的位置,如果不在resources下springboot是没有办法识别 的,在maven添加
<build> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> </resource> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> </resources> </build>
6.写一个hellWord接口测试访问(security的maven依赖注释掉,不然就会跳转到登录页面。用户名:user 密码:项目启动在控制台中生成)
5.security认证授权
Swagger
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.ParameterBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.schema.ModelRef; import springfox.documentation.service.ApiInfo; import springfox.documentation.service.Contact; import springfox.documentation.service.Parameter; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; import java.util.ArrayList; import java.util.List; /** * @author YinXiaoWei * @date 2020/11/24 14:26 * swagger */ @Configuration @EnableSwagger2 @EnableKnife4j public class SwaggerConfig { @Value("${jwt.tokenHeader}") private String tokenHeader; @Value("${jwt.tokenHead}") private String tokenHead; @Bean public Docket docket() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) .paths(PathSelectors.any()) .build() .globalOperationParameters(getParameterList()); } private ApiInfo apiInfo() { Contact contact = new Contact("spring-security", "http://www.spring-security.com/", "15289@qq.com"); return new ApiInfoBuilder() .title("com.dome.spring") .description("前端API接口") .contact(contact) .version("1.0.0") .build(); } private List<Parameter> getParameterList(){ List<Parameter> parameters = new ArrayList<>(); parameters.add(new ParameterBuilder() .name(this.tokenHeader) .description("令牌") .defaultValue(this.tokenHead) .modelRef(new ModelRef("string")) .parameterType("header") .required(false) .build()); return parameters; } }
推荐Knife4j,在swagger-ui的基础上导入maven依赖在到SwaggerConfig文件上加上@EnableKnife4j注解就可以访问很好看的Knife4j页面了。
Knife4j访问地址:http://ip:host/doc.html#/home swagger-ui访问地址:http://ip:host/swagger-ui.html
<!--整合Knife4j--> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-boot-starter</artifactId> <version>2.0.4</version> </dependency>
认证授权
根据自己的要求自定义UserDetails实体类
package com.dome.provider.config.security.dto; import com.dome.model.po.role.Role; import com.dome.model.po.user.User; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.List; import java.util.stream.Collectors; /** * @author YinXiaoWei * @date 2020/11/26 9:33 */ @Data public class JwtUserDto implements UserDetails { /** * 用户数据 */ private User myUser; private List<Role> roleInfo; /** * 用户权限的集合 */ @JsonIgnore private List<GrantedAuthority> authorities; public List<String> getRoles() { return authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()); } /** * 加密后的密码 * @return */ @Override public String getPassword() { return myUser.getPassword(); } /** * 用户名 * @return */ @Override public String getUsername() { return myUser.getUserName(); } /** * 是否过期 * @return */ @Override public boolean isAccountNonExpired() { return true; } /** * 是否锁定 * @return */ @Override public boolean isAccountNonLocked() { return true; } /** * 凭证是否过期 * @return */ @Override public boolean isCredentialsNonExpired() { return true; } /** * 是否可用 * @return */ @Override public boolean isEnabled() { return myUser.getStatus() == 0 ? true : false; } public JwtUserDto(User myUser, List<GrantedAuthority> authorities) { this.myUser = myUser; this.authorities = authorities; } }
添加需要用到的dao方法,附上MenuMapper.xml代码和VO类,因为集成了mybatis-plus所以写了一个基本就够用了。
<?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="com.dome.biz.mapper.menu.MenuMapper"> <!-- 通用查询映射结果 --> <resultMap id="BaseResultMap" type="com.dome.model.po.menu.Menu"> <id column="id" property="id" /> <result column="type" property="type" /> <result column="parent" property="parent" /> <result column="name" property="name" /> <result column="url" property="url" /> <result column="icon" property="icon" /> <result column="permission" property="permission" /> <result column="sort" property="sort" /> <result column="create_time" property="createTime" /> <result column="create_name" property="createName" /> <result column="update_time" property="updateTime" /> <result column="update_name" property="updateName" /> <result column="version" property="version" /> <result column="del_flag" property="delFlag" /> </resultMap> <!-- 通用查询结果列 --> <sql id="Base_Column_List"> id, type, parent, name, url, icon, permission, sort, create_time, create_name, update_time, update_name, version, del_flag </sql> <!-- 菜单信息列--> <sql id="Menu_By_Id"> m.id as id, m.parent, m.name as title,m.icon,m.url as href, m.type, m.permission, m.sort </sql> <!-- 通过用户id返回菜单--> <select id="listByUserId" resultType="com.dome.model.vo.menu.MenuIndexVo"> SELECT DISTINCT <include refid="Menu_By_Id"></include> FROM sys_user_role ru INNER JOIN sys_role_menu rm ON rm.role_id = ru.role_id LEFT JOIN sys_menu m ON rm.menu_id = m.id WHERE 1 = 1 <if test="userId != null"> AND ru.user_id = #{userId} </if> ORDER BY ifnull(m.sort, 0) </select> </mapper>
package com.dome.model.vo.menu; import io.swagger.annotations.ApiModel; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; import java.io.Serializable; import java.util.List; /** * @author YinXiaoWei * @date 2020/11/26 10:34 */ @Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @ApiModel(value="MenuIndexVo对象", description="菜单信息返回表") public class MenuIndexVo implements Serializable { private Integer id; private Integer parentId; private String title; private String icon; private Integer type; private String href; private String permission; private List<MenuIndexVo> children; }
创建UserDetailsServiceImpl类,重写UserDetailsService方法,动态连接数据库
package com.dome.provider.config.security; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.dome.biz.service.menu.MenuService; import com.dome.biz.service.role.RoleService; import com.dome.biz.service.user.UserRoleService; import com.dome.biz.service.user.UserService; import com.dome.common.utils.RedisUtil; import com.dome.model.po.role.Role; import com.dome.model.po.user.User; import com.dome.model.po.user.UserRole; import com.dome.model.vo.menu.MenuIndexVo; import com.dome.provider.config.security.dto.JwtUserDto; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.LockedException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; /** * @author YinXiaoWei * @date 2020/11/26 10:25 * 重写登录实现类 */ @Service @Slf4j public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserService userService; @Autowired private UserRoleService userRoleService; @Autowired private RoleService roleService; @Autowired private MenuService menuService; @Override public JwtUserDto loadUserByUsername(String userName) { //根据用户名获取用户 User user = userService.existUser(userName); if (user == null ){ throw new BadCredentialsException("用户名或密码错误"); }else if (user.getStatus().equals(User.Status.VALID)) { throw new LockedException("用户被锁定,请联系管理员解锁"); } List<GrantedAuthority> grantedAuthorities = new ArrayList<>(15); List<MenuIndexVo> list = menuService.listByUserId(user.getId().toString()); List<String> collect = list.stream().map(MenuIndexVo::getPermission).collect(Collectors.toList()); for (String authority : collect){ if (!StringUtils.isEmpty(authority)){ GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(authority); grantedAuthorities.add(grantedAuthority); } } //将用户所拥有的权限加入GrantedAuthority集合中 JwtUserDto loginUser =new JwtUserDto(user,grantedAuthorities); loginUser.setRoleInfo(getRoleInfo(user)); return loginUser; } /** * 获得用户所有角色 * @param user * @return */ public List<Role> getRoleInfo(User user) { QueryWrapper<UserRole> wrapper = new QueryWrapper<>(); wrapper.lambda().eq(UserRole::getUserId, user.getId()); List<UserRole> userRoleList = userRoleService.getBaseMapper().selectList(wrapper); List<Role> roleList = new ArrayList<>(); for (UserRole item : userRoleList) { Role role = roleService.getBaseMapper().selectById(item.getRoleId()); roleList.add(role); } return roleList; } }
完成这一步就可以登录了,在securityConfig中添加UserDetailsServiceImpl和加密方法(数据库的密码现在好像必须要加密,有兴趣可以了解下CSDN密码泄露事件)。
package com.dome.provider.config.security.config; import com.dome.provider.config.security.UserDetailsServiceImpl; import com.dome.provider.config.security.filter.JwtAuthenticationTokenFilter; import com.dome.provider.config.security.handler.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.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.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; /** * @author YinXiaoWei * @date 2020/11/25 17:07 */ @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { /** * 登录逻辑 */ @Autowired private UserDetailsServiceImpl userDetailsService; /** * 登录成功逻辑 */ @Autowired private MyAuthenticationSuccessHandler authenticationSuccessHandler; /** * 登出成功逻辑 */ @Autowired private AjaxLogoutSuccessHandler logoutSuccessHandler; /** * 登录失败逻辑 */ @Autowired private MyAuthenticationFailureHandler authenticationFailureHandler; /** * 无权限拦截器 */ @Autowired private RestAuthenticationEntryPoint restAuthenticationEntryPoint; /** * 无权访问 JSON 格式的数据 */ @Autowired private RestfulAccessDeniedHandler accessDeniedHandler; /** * token认证过滤器 */ @Autowired private JwtAuthenticationTokenFilter authenticationTokenFilter; /** * 放行静态资源 * @param web * @throws Exception */ @Override public void configure(WebSecurity web) throws Exception { web.ignoring() .antMatchers(HttpMethod.GET, "/swagger-resources/**", "/PearAdmin/**", "/component/**", "/admin/**", "/**/*.html", "/**/*.css", "/**/*.js", "/swagger-ui.html", "/webjars/**", "/v2/**", "/druid/**"); } /** * 开启加密 * @return */ @Bean public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); } /** * 身份认证接口 * @param auth * @throws Exception */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder()); } /** * 核心配置 * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { //关闭csrf http.csrf().disable() // 基于token,所以不需要session .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() //未登陆时返回 JSON 格式的数据给前端 .httpBasic() .authenticationEntryPoint(restAuthenticationEntryPoint) .and() .authorizeRequests() // 对于登录login 验证码 允许匿名访问 .antMatchers("/authentication/getAuthCode", "/authentication/existAuthCode/**","/login") .anonymous() .antMatchers("/profile/**").permitAll() .antMatchers("/common/download**").anonymous() .antMatchers("/common/download/resource**").anonymous() .antMatchers("/swagger-ui.html").anonymous() .antMatchers("/swagger-resources/**").anonymous() .antMatchers("/webjars/**").anonymous() .antMatchers("/*/api-docs").anonymous() .antMatchers("/doc.html").permitAll() // 其余请求都需要认证 .anyRequest().authenticated() .and() .formLogin() // 登录页面 不设限访问 // .loginPage("/login.html") // .loginProcessingUrl("/login") // 登录成功 .successHandler(authenticationSuccessHandler) // 登录失败 .failureHandler(authenticationFailureHandler) .permitAll() .and() // 注销行为 .logout() .logoutUrl("/logout") .logoutSuccessHandler(logoutSuccessHandler) .and() // 允许通过remember-me登录的用户访问 .rememberMe() .rememberMeParameter("remember-me") .and() .headers() // 禁用缓存 .cacheControl(); // 防止iframe 造成跨域 http.headers().frameOptions().disable(); // 添加的token拦截器 http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); // 无权访问 JSON 格式的数据 http.exceptionHandling().accessDeniedHandler(accessDeniedHandler); } }
做到这一步可以尝试下登录,http://ip:host/login(将上一步中多余的代码删掉)
以下是自定义的一些拦截器,把这些添加代码里面基本都有注释我就不介绍了。
package com.dome.provider.config.security.handler; import com.alibaba.fastjson.JSON; import com.dome.common.utils.RedisUtil; import com.dome.common.utils.model.ResponseModel; import com.dome.provider.config.security.dto.JwtUserDto; import com.dome.provider.config.security.jwt.JwtUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author YinXiaoWei * @date 2020/11/26 9:29 * 登录成功 */ @Slf4j @Component public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler { @Autowired private JwtUtils jwtUtils; @Value("${jwt.tokenHeader}") private String tokenHeader; @Value("${jwt.tokenHead}") private String tokenHead; @Value("${jwt.expiration}") private Long expiration; @Autowired private RedisUtil redisUtil; private static final String TOKEN_KEY = "USER_TOKEN"; private static final String USER_KEY = "USER_INFO"; @Override public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { //拿到登录用户信息 JwtUserDto userDetails = (JwtUserDto)authentication.getPrincipal(); //生成token String jwtToken = jwtUtils.generateToken(userDetails.getUsername()); // 存入redis中 if (redisUtil.hasKey(TOKEN_KEY + userDetails.getUsername())) { redisUtil.delKey(TOKEN_KEY + userDetails.getUsername()); } if (redisUtil.hasKey(USER_KEY + userDetails.getUsername())) { redisUtil.delKey(USER_KEY + userDetails.getUsername()); } redisUtil.setObject(USER_KEY + userDetails.getUsername(), userDetails); redisUtil.setObjectTime(TOKEN_KEY + userDetails.getUsername(), jwtToken, this.expiration); // 返回token信息 ResponseModel success = ResponseModel.success("登录成功", jwtToken); httpServletResponse.setCharacterEncoding("utf-8"); httpServletResponse.setContentType("application/json"); //输出结果 httpServletResponse.getWriter().write(JSON.toJSONString(success)); } }
package com.dome.provider.config.security.handler; import com.alibaba.fastjson.JSON; import com.dome.common.utils.model.ResponseModel; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author YinXiaoWei * @date 2020/11/26 10:12 * 登录失败 */ @Component public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { httpServletResponse.setCharacterEncoding("utf-8"); httpServletResponse.setContentType("application/json"); if (e instanceof BadCredentialsException){ httpServletResponse.getWriter().write(JSON.toJSONString(ResponseModel.error("用户名或密码错误"))); }else { httpServletResponse.getWriter().write(JSON.toJSONString(ResponseModel.error(e.getMessage()))); } } }
package com.dome.provider.config.security.handler; import com.alibaba.fastjson.JSON; import com.dome.common.utils.model.ResponseModel; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author YinXiaoWei * @date 2020/11/30 10:39 * 退出成功 */ @Component public class AjaxLogoutSuccessHandler implements LogoutSuccessHandler { @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { response.setCharacterEncoding("UTF-8"); response.setContentType("application/json"); response.getWriter().println(JSON.toJSONString(ResponseModel.success("登出成功!"))); response.getWriter().flush(); } }
package com.dome.provider.config.security.handler; import com.alibaba.fastjson.JSON; import com.dome.common.utils.model.ResponseModel; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author YinXiaoWei * @date 2020/11/26 10:19 * 当未登录或者token失效访问接口时,自定义的返回结果 */ @Component public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { response.setCharacterEncoding("UTF-8"); response.setContentType("application/json"); response.getWriter().println(JSON.toJSONString(ResponseModel.error("尚未登录,或者登录过期 " + authException.getMessage()))); response.getWriter().flush(); } }
package com.dome.provider.config.security.handler; import com.alibaba.fastjson.JSON; import com.dome.common.utils.model.ResponseModel; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author YinXiaoWei * @date 2020/11/26 10:23 * 当访问接口没有权限时,自定义的返回结果 */ @Component public class RestfulAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException { response.setCharacterEncoding("UTF-8"); response.setContentType("application/json"); response.getWriter().println(JSON.toJSONString(ResponseModel.error(e.getMessage()))); response.getWriter().flush(); } }
package com.dome.provider.config.security.filter; import com.dome.common.utils.RedisUtil; import com.dome.provider.config.security.UserDetailsServiceImpl; import com.dome.provider.config.security.jwt.JwtUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author YinXiaoWei * @date 2020/11/26 16:54 * token过滤器 */ @Slf4j @Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Autowired private UserDetailsServiceImpl userDetailsService; @Autowired private JwtUtils jwtUtils; @Autowired private RedisUtil redisUtil; @Value("${jwt.tokenHeader}") private String tokenHeader; @Value("${jwt.tokenHead}") private String tokenHead; private static final String TOKEN_KEY = "USER_TOKEN"; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 获取header令牌 String authHeader = request.getHeader(this.tokenHeader); if (authHeader != null && authHeader.startsWith(this.tokenHead)) { // token由"Bearer "开头 String authToken = authHeader.substring(this.tokenHead.length()); //解析token获取用户名 String username = jwtUtils.getUserNameFromToken(authToken); log.info("checking username:{}", username); // 判断redis有没有token if (redisUtil.hasKey(TOKEN_KEY + username) && authToken.equals(redisUtil.getObject(TOKEN_KEY + username))) { // 存放authentication到SecurityContextHolder if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); if (userDetails != null) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); log.info("authenticated user:{}", username); SecurityContextHolder.getContext().setAuthentication(authentication); } } } } filterChain.doFilter(request, response); } }
在接口上添加@PreAuthorize(value = "hasAnyAuthority('对应的权限')"),在SecurityConfig加上@EnableGlobalMethodSecurity(prePostEnabled = true)
在PostMan中测试如下:
测试其他接口是需要加上token才能访问,若是不需要token就在SecurityConfig中添加antMatchers()。token前要带上Bearer 字段(在swagger中配置了默认输入)