diff --git a/.gitignore b/.gitignore index 411fb0d..374488b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ node_modules/ src/.vuepress/.cache/ src/.vuepress/.temp/ src/.vuepress/dist/ +.idea/* diff --git a/.idea/blog.iml b/.idea/blog.iml new file mode 100644 index 0000000..24643cc --- /dev/null +++ b/.idea/blog.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..e81aaae --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/.vuepress/components/BlogHero.vue b/src/.vuepress/components/BlogHero.vue index e75b85a..411a71a 100644 --- a/src/.vuepress/components/BlogHero.vue +++ b/src/.vuepress/components/BlogHero.vue @@ -5,7 +5,7 @@ import HitokotoBlogHero from "vuepress-theme-hope/presets/HitokotoBlogHero.js"; - + diff --git a/src/.vuepress/sidebar.ts b/src/.vuepress/sidebar.ts index ff9ffc7..cee3537 100644 --- a/src/.vuepress/sidebar.ts +++ b/src/.vuepress/sidebar.ts @@ -26,6 +26,7 @@ export default sidebar({ { text: "Java", collapsible: true, + expanded: true, icon: "mdi:language-java", prefix: "java/", children: [ @@ -38,7 +39,7 @@ export default sidebar({ }, { text: "工具箱", - icon: "simple-icons:framework", + icon: "mdi:tools", collapsible: true, prefix: "工具箱/", children: "structure", diff --git a/src/intro.md b/src/intro.md index 4e4e74e..cd72b21 100644 --- a/src/intro.md +++ b/src/intro.md @@ -9,4 +9,4 @@ date: 2025-05-06 ## 自我介绍 -> 你好哇哈哈哈哈的武器恶趣味大苏打撒旦eqweqwesadasdwewcasdqweqwfc1231233dsczxcxzc \ No newline at end of file +> 一个瞎鼓捣得程序员 \ No newline at end of file diff --git a/src/programming/java/工具箱/WebSocket和HTTP关系.md b/src/programming/java/工具箱/WebSocket和HTTP关系.md new file mode 100644 index 0000000..3f7c3e0 --- /dev/null +++ b/src/programming/java/工具箱/WebSocket和HTTP关系.md @@ -0,0 +1,456 @@ +--- +icon: bi:arrows-expand +date: 2025-05-08 +category: + - JAVA + - 通信协议 +tag: + - websocket + - http +--- + +# WebSocket和HTTP关系 + +## 1. WebSocket简介 + +WebSocket 是 HTML5 提供的一种在单个 TCP 连接上进行全双工通讯的协议。它使客户端和服务器之间的数据交换变得更加简单高效,并允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需完成一次握手,便可创建持久性的连接,实现双向数据传输。 + +## 2. WebSocket与HTTP的关系 + +### 2.1 协议转换过程 + +WebSocket依赖于HTTP连接初始化,但随后会进行协议升级。具体转换过程如下: + +1. **初始HTTP请求**:每个WebSocket连接都始于一个HTTP请求。客户端发送标准的HTTP请求,但包含特殊的头信息,表明希望升级为WebSocket协议: + +``` +GET /chat HTTP/1.1 +Host: server.example.com +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== +Origin: http://example.com +Sec-WebSocket-Version: 13 +``` + +2. **服务器响应升级**:如果服务器支持WebSocket协议,会返回101状态码,表示协议正在切换: + +``` +HTTP/1.1 101 Switching Protocols +Upgrade: websocket +Connection: Upgrade +Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= +``` + +3. **协议切换完成**:此时HTTP请求已完成其使命,如果协议升级成功,客户端会触发`onopen`事件;否则触发`onerror`事件。此后的所有通信都使用WebSocket协议,不再依赖HTTP。 + +### 2.2 为什么WebSocket要依赖HTTP协议连接? + +WebSocket选择依赖HTTP协议有几个重要原因: + +1. **设计理念**:WebSocket设计之初就是为HTTP增强通信能力(尤其是全双工通信),因此在HTTP协议基础上扩展是自然的选择,能够复用HTTP的基础设施。 + +2. **最大兼容性**:基于HTTP连接可获得最广泛的兼容支持。即使服务器不支持WebSocket,也能建立HTTP通信并返回相应错误,这比完全无响应要好得多。 + +3. **防火墙友好**:大多数网络环境允许HTTP流量通过(80和443端口),基于HTTP的WebSocket更容易穿越防火墙和代理服务器。 + +4. **减少实现复杂度**:复用现有的HTTP基础设施,无需从零开始构建新的协议栈。 + +### 2.3 HTTP与WebSocket的主要区别 + +|特性|HTTP|WebSocket| +|---|---|---| +|连接类型|无状态、短连接|有状态、长连接| +|通信方式|单向(请求-响应)|双向(全双工)| +|数据交互模式|客户端主动请求|双方均可主动发送| +|数据传输量|每次请求都有HTTP头|握手后数据传输更轻量| +|实时性|弱(通常需轮询)|强(可即时推送)| +|使用场景|传统网页请求、RESTful API|聊天应用、实时数据更新、在线游戏等| + +## 3. WebSocket的优势与应用场景 + +相比传统HTTP,WebSocket具有以下优势: + +1. **降低延迟**:一旦建立连接,通信双方可随时发送数据,无需重复建立连接。 + +2. **减少网络流量**:WebSocket在握手后的通信中没有HTTP头的开销,数据传输更高效。 + +3. **实时双向通信**:服务器可以主动推送信息给客户端,无需客户端轮询。 + +4. **保持连接状态**:WebSocket是有状态协议,可维护连接上下文信息。 + +典型应用场景: +- 实时通讯应用(聊天室、即时通讯工具) +- 在线协作工具(协同编辑文档) +- 实时数据展示(股票行情、体育赛事直播) +- 游戏应用(在线多人游戏) +- 物联网设备通信 + +## 4. 总结 + +WebSocket与HTTP是相辅相成的关系,而非替代关系。WebSocket通过HTTP协议完成初始握手,随后升级为独立的协议,实现更高效的双向通信。两种协议各有优势,应根据应用场景选择合适的通信方式。在需要实时性、双向通信的场景中,WebSocket展现出明显优势;而对于简单的数据获取和传统网页浏览,HTTP仍然是最佳选择。 + +## 5. 案例(服务端) +### 5.1 项目结构 + + +### 5.2 依赖配置 +```xml + + 4.0.0 + + + + org.springframework.boot + spring-boot-starter-parent + 2.4.3 + + + + + websocket + jar + + websocket + http://maven.apache.org + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-websocket + + + org.projectlombok + lombok + true + + + +``` + +### 5.3 主应用类 +```java +package com.mangmang; + + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class WebSocketDemoApplication { + + public static void main(String[] args) { + SpringApplication.run(WebSocketDemoApplication.class, args); + } +} +``` +### 5.4 WebSocket配置类 +```java +package com.mangmang.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.simp.config.MessageBrokerRegistry; +import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; +import org.springframework.web.socket.config.annotation.StompEndpointRegistry; +import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; + +@Configuration +@EnableWebSocketMessageBroker +public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { + + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + // 注册STOMP协议的节点(endpoint) + registry.addEndpoint("/ws").setAllowedOrigins("http://10.6.212.39:5173/"); + } + + @Override + public void configureMessageBroker(MessageBrokerRegistry registry) { + // 配置消息代理,前缀为/topic的消息将会被路由到消息代理 + registry.enableSimpleBroker("/topic"); + // 以/app开头的消息将会被路由到@MessageMapping注解的方法中 + registry.setApplicationDestinationPrefixes("/app"); + } + +} +``` +### 5.5 消息实体类 +```java +package com.mangmang.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +import lombok.NoArgsConstructor; + + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Message { + private String content; + private String sender; + private MessageType type; + + + public enum MessageType { + CHAT, + JOIN, + LEAVE + } +} +``` + +### 5.6 消息控制类 +``` +package com.mangmang.controller; + +import com.mangmang.model.Message; +import lombok.extern.slf4j.Slf4j; +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.messaging.handler.annotation.SendTo; +import org.springframework.messaging.simp.SimpMessageHeaderAccessor; +import org.springframework.stereotype.Controller; + +import java.util.Objects; + +@Slf4j +@Controller +public class MessageController { + + @MessageMapping("/chat.sendMessage") + @SendTo("/topic/public") + public Message sendMessage(@Payload Message message) { + log.info(message.toString()); + return message; + } + + + @MessageMapping("/chat.addUser") + @SendTo("/topic/public") + public Message addUser(@Payload Message message, SimpMessageHeaderAccessor headerAccessor) { + log.info(message.toString()); + // 将用户名添加到WebSocket会话 + Objects.requireNonNull(headerAccessor.getSessionAttributes()).put("username", message.getSender()); + return message; + } +} +``` + +### 5.7 websocket断连通知类 +```java +package com.mangmang.listener; + +import com.mangmang.model.Message; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.event.EventListener; +import org.springframework.messaging.simp.SimpMessageHeaderAccessor; +import org.springframework.messaging.simp.SimpMessageSendingOperations; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.messaging.SessionDisconnectEvent; + +import java.util.Objects; + +@Slf4j +@Component +@RequiredArgsConstructor +public class WebSocketEventListener { + + private final SimpMessageSendingOperations messagingTemplate; + + @EventListener + public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) { + // 获取会话属性 + SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.wrap(event.getMessage()); + + // 从会话中获取用户名 + String username = (String) Objects.requireNonNull(headerAccessor.getSessionAttributes()).get("username"); + + if (username != null) { + log.info("用户断开连接: {}", username); + + // 创建一个离开消息 + Message message = Message.builder() + .type(Message.MessageType.LEAVE) + .sender(username) + .content("下线了") + .build(); + + // 发送消息到公共主题 + messagingTemplate.convertAndSend("/topic/public", message); + } + } +} +``` + +## 6. 案例(客户端) +### 6.1 封装websocket +```ts +import {Client} from '@stomp/stompjs'; + +interface ChatMessage { + content: string; + sender: string; + type: 'CHAT' | 'JOIN' | 'LEAVE'; +} + +class WebSocketService { + private stompClient: Client | null = null; + + connect(username: string, onMessageReceived: (msg: ChatMessage) => void) { + // 创建原生 WebSocket 连接 + const socket = new WebSocket('ws://10.6.212.39:8099/ws'); // 确保这里是 WebSocket 协议 + + this.stompClient = new Client({ + webSocketFactory: () => socket, + onConnect: () => { + console.log('STOMP connected'); + // 订阅公共主题 + this.stompClient?.subscribe('/topic/public', (message) => { + const chatMsg: ChatMessage = JSON.parse(message.body); + onMessageReceived(chatMsg); + }); + + // 发送用户加入消息 + this.sendAddUserMessage(username); + }, + onStompError: (frame) => { + console.error('Broker reported error: ', frame.headers['message']); + console.error('Additional details: ', frame.body); + }, + // 关闭时清理资源 + onDisconnect: () => { + console.log('Disconnected from STOMP'); + this.stompClient = null; + } + }); + + // 激活客户端 + this.stompClient.activate(); + } + + sendMessage(chatMessage: Omit) { + if (this.stompClient?.connected) { + this.stompClient.publish({ + destination: '/app/chat.sendMessage', + body: JSON.stringify(chatMessage), + }); + } else { + console.warn('WebSocket not connected'); + } + } + + sendAddUserMessage(username: string) { + if (this.stompClient?.connected) { + this.stompClient.publish({ + destination: '/app/chat.addUser', + body: JSON.stringify({content: "加入聊天", sender: username, type: 'JOIN'}), + }); + } + } + + disconnect() { + if (this.stompClient) { + this.stompClient.deactivate(); + this.stompClient = null; + } + } +} + +export default new WebSocketService(); +``` + +### 6.2 页面 +```vue + + + + + WebSocket 聊天室 + + + + {{ msg.sender }}: {{ msg.content }} + + + + + + + + + +``` \ No newline at end of file diff --git a/src/programming/java/工具箱/assert/1746684922168.png b/src/programming/java/工具箱/assert/1746684922168.png new file mode 100644 index 0000000..5c5240a Binary files /dev/null and b/src/programming/java/工具箱/assert/1746684922168.png differ diff --git a/src/programming/java/framework/SpringSecurity.md b/src/programming/java/框架/SpringSecurity.md similarity index 97% rename from src/programming/java/framework/SpringSecurity.md rename to src/programming/java/框架/SpringSecurity.md index 1868fb0..847fc48 100644 --- a/src/programming/java/framework/SpringSecurity.md +++ b/src/programming/java/框架/SpringSecurity.md @@ -1,3015 +1,3015 @@ ---- -icon: simple-icons:spring -title: SpringSecurity -date: 2025-05-07 -category: - - JAVA -tag: - - Spring - - SpringSecurity -star: 10 -description: 若依的SpringSecurity案例 -# 此页面会在文章列表置顶 -sticky: true ---- -若依的SpringSecurity案例 - - - -# 一、准备 - -## 1. 导入依赖 - -### 1. 父依赖 - -```xml - - - 4.0.0 - - 01-druid - 02-SpringSecurity - - - - org.springframework.boot - spring-boot-starter-parent - 2.3.0.RELEASE - - - - com.mangmang - learn-test - 0.0.1-SNAPSHOT - learn-test - pom - - 依赖版本控制 - - - 1.8 - 3.4.3.1 - 3.4.1 - 2.3 - 1.2.4 - 0.9.1 - 2.0.8 - 1.21 - 1.5.1-RELEASE - - - - - - - - eu.bitwalker - UserAgentUtils - ${userAgentUtils.version} - - - - com.github.xiaoymin - knife4j-spring-boot-starter - ${knife4j.version} - - - - - io.jsonwebtoken - jjwt - ${jjwt.version} - - - - - com.github.jeffreyning - mybatisplus-plus - ${mybatisplus-plus.version} - - - - - com.baomidou - mybatis-plus-boot-starter - ${mybatis-plus.version} - - - - - com.baomidou - mybatis-plus-generator - ${mybatis-plus-generator.version} - - - - - org.apache.velocity - velocity-engine-core - ${mybatis-velocity.version} - - - - - com.alibaba - druid-spring-boot-starter - ${druid.version} - - - - - - - - com.alibaba - fastjson - 1.2.75 - - - - - - cn.hutool - hutool-all - 5.5.8 - - - - - - org.springframework.boot - spring-boot-starter-data-redis - - - - - org.springframework.boot - spring-boot-starter-web - - - - - org.springframework.boot - spring-boot-devtools - runtime - true - - - - - mysql - mysql-connector-java - runtime - - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - - - org.projectlombok - lombok - true - - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - 2.3.0.RELEASE - - - - org.projectlombok - lombok - - - - - - - - - -``` - - - -### 2. 模块使用依赖 - -```xml - - - - learn-test - com.mangmang - 0.0.1-SNAPSHOT - - 4.0.0 - - 02-SpringSecurity - - - 8 - 8 - - - - - - com.github.jeffreyning - mybatisplus-plus - - - - - eu.bitwalker - UserAgentUtils - - - - - com.github.xiaoymin - knife4j-spring-boot-starter - - - - - io.jsonwebtoken - jjwt - - - - - org.springframework.boot - spring-boot-starter-security - - - - - com.baomidou - mybatis-plus-boot-starter - - - - - com.baomidou - mybatis-plus-generator - - - - - org.apache.velocity - velocity-engine-core - - - - - com.alibaba - druid-spring-boot-starter - - - - - - - - src/main/java - - **/*.xml - - false - - - - - -``` - -## 2. 配置YML - -### 1. application.yml - -```yml -spring: - profiles: - active: local - -server: - port: 8000 - -swagger: - enabled: true - pathMapping: - -token: - header: token - secret: asdaswqesdzxwr3123 - expire-time: 30 -``` - -### 2. application-local.yml - -```yml -spring: - datasource: - # 配置项目数据源为druid - type: com.alibaba.druid.pool.DruidDataSource - driver-class-name: com.mysql.cj.jdbc.Driver - druid: - url: jdbc:mysql://127.0.0.1:3306/book?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 - username: root - password: root - initial-size: 5 - # 最小连接池数量 - min-idle: 10 - # 最大连接池数量 - max-active: 20 - # 配置获取连接等待超时的时间 - max-wait: 60000 - # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 - time-between-eviction-runs-millis: 60000 - # 配置一个连接在池中最小生存的时间,单位是毫秒 - min-evictable-idle-time-millis: 300000 - # 配置一个连接在池中最大生存的时间,单位是毫秒 - max-evictable-idle-time-millis: 900000 - # 配置检测连接是否有效 - validation-query: SELECT 1 FROM DUAL - # 官方推荐配置 - test-while-idle: true - test-on-borrow: false - test-on-return: false - - #监控配置 - web-stat-filter: - # 是否启用StatFilter默认值true - enabled: true - stat-view-servlet: - enabled: true - # 设置白名单,不填则允许所有访问 - allow: - url-pattern: /druid/* - # 控制台管理用户名和密码 - login-username: root - login-password: liujing - filter: - stat: - enabled: true - # 慢SQL记录 - log-slow-sql: true - slow-sql-millis: 1000 - merge-sql: true - wall: - config: - multi-statement-allow: true - redis: - port: 6379 - host: 127.0.0.1 - - -# MyBatis Plus配置 -mybatis-plus: - # 搜索指定包别名 - typeAliasesPackage: com.mangmang.**.domain - # 配置mapper的扫描,找到所有的mapper.xml映射文件 - mapperLocations: classpath*:mapper/**/*Mapper.xml - configuration: - # SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING - log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl - #开启二级缓存 - cache-enabled: true - #配置默认的执行器 - default-executor-type: reuse - # 开启驼峰命名 - map-underscore-to-camel-case: true - # 允许 JDBC 支持自动生成主键 - use-generated-keys: true - #关闭logo - global-config: - banner: false - db-config: - # 全局逻辑删除的实体字段名 - logic-delete-field: isDeleted - # 逻辑已删除值(默认为 1) - logic-delete-value: 1 - # 逻辑未删除值(默认为 0) - logic-not-delete-value: 0 -``` - -## 3. 配置Mybatis-Plus - -```java -/** - * @Date: 2021-08-10-17:31 - * @Author lj - */ -@Configuration -@EnableTransactionManagement -@MapperScan("com.mangmang.security.mapper") -public class MybatisPlusConfig { - @Bean - public MybatisPlusInterceptor mybatisPlusInterceptor() { - MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); - // 分页插件 - interceptor.addInnerInterceptor(paginationInnerInterceptor()); - // 乐观锁插件 - interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor()); - // 阻断插件 - interceptor.addInnerInterceptor(blockAttackInnerInterceptor()); - return interceptor; - } - /** - * 分页插件,自动识别数据库类型 https://baomidou.com/guide/interceptor-pagination.html - */ - public PaginationInnerInterceptor paginationInnerInterceptor() { - PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(); - // 设置数据库类型为mysql - paginationInnerInterceptor.setDbType(DbType.MYSQL); - // 设置最大单页限制数量,默认 500 条,-1 不受限制 - paginationInnerInterceptor.setMaxLimit(-1L); - return paginationInnerInterceptor; - } - /** - * 乐观锁插件 https://baomidou.com/guide/interceptor-optimistic-locker.html - */ - public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() { - return new OptimisticLockerInnerInterceptor(); - } - /** - * 如果是对全表的删除或更新操作,就会终止该操作 https://baomidou.com/guide/interceptor-block-attack.html - */ - public BlockAttackInnerInterceptor blockAttackInnerInterceptor() { - return new BlockAttackInnerInterceptor(); - } - -} -``` - -## 4. 配置Knife4j - -```java -/** - * @Date: 2021-08-10-18:14 - * @Author lj - */ -@EnableSwagger2WebMvc -@Configuration -@EnableKnife4j -public class SwaggerConfig { - /** - * 是否开启swagger - */ - @Value("${swagger.enabled}") - private boolean enabled; - /** - * 设置请求的统一前缀 - */ - @Value("${swagger.pathMapping}") - private String pathMapping; - /** - * 创建API - * .pathMapping("test")设置访问路径统一前缀 - */ - @Bean - public Docket createRestApi() { - return new Docket(DocumentationType.SWAGGER_2) - // 是否启用Swagger - .enable(enabled) - // 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息) - .apiInfo(apiInfo()) - // 设置哪些接口暴露给Swagger展示 - .select() - // 扫描所有有注解的api,用这种方式更灵活 - .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) - .paths(PathSelectors.any()) - .build() - /* 设置安全模式,swagger可以设置访问token */ - .securitySchemes(securitySchemes()) - .securityContexts(securityContexts()) - .pathMapping(pathMapping); - } - - /** - * 安全模式,这里指定token通过Authorization头请求头传递 - */ - private List securitySchemes() { - List apiKeyList = new ArrayList<>(); - apiKeyList.add(new ApiKey("Authorization", "Authorization", "header")); - return apiKeyList; - } - - /** - * 安全上下文 - */ - private List securityContexts() { - List securityContexts = new ArrayList<>(); - securityContexts.add( - SecurityContext.builder() - .securityReferences(defaultAuth()) - .forPaths(PathSelectors.regex("^(?!auth).*$")) - .build()); - return securityContexts; - } - - /** - * 默认的安全上引用 - */ - private List defaultAuth() { - AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything"); - AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; - authorizationScopes[0] = authorizationScope; - List securityReferences = new ArrayList<>(); - securityReferences.add(new SecurityReference("Authorization", authorizationScopes)); - return securityReferences; - } - - /** - * 添加摘要信息 - */ - private ApiInfo apiInfo() { - // 用ApiInfoBuilder进行定制 - return new ApiInfoBuilder() - // 设置标题 - .title("标题:后端接口") - // 描述 - .description("描述:测试") - // 作者信息 - .contact(new Contact("氓氓编程", null, null)) - // 版本 - .version("版本号:" + "0.0.1") - .build(); - } -} -``` - -## 5. 配置RedisTemplate序列化规则 - -```java -/** - * @author a3621 - */ -@EnableCaching -@Configuration -public class RedisConfig extends CachingConfigurerSupport { - @Bean - public RedisTemplate redisTemplate(RedisConnectionFactory factory) { - // 创建一个模板 - RedisTemplate template = new RedisTemplate<>(); - //创建一个字符串序列化器 - RedisSerializer redisSerializer = new StringRedisSerializer(); - //创建Jackson 序列化器 - Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); - //Jackson得ObjectMapper对象 - ObjectMapper om = new ObjectMapper(); - //这将使所有成员字段无需进一步注释即可序列化,而不仅仅是公共字段(默认设置)。 - om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); - //开启后序列化得类会带上全类名 - om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); - //om配置给jackson2JsonRedisSerializer - jackson2JsonRedisSerializer.setObjectMapper(om); - //RedisTemplate配置factory - template.setConnectionFactory(factory); - //key序列化方式 - template.setKeySerializer(redisSerializer); - //value序列化 - template.setValueSerializer(jackson2JsonRedisSerializer); - //value hashmap序列化 - template.setHashValueSerializer(jackson2JsonRedisSerializer); - //返回该模板 - return template; - } - - @Bean - public CacheManager cacheManager(RedisConnectionFactory factory) { - //创建一个字符串序列化器 - RedisSerializer redisSerializer = new StringRedisSerializer(); - //创建Jackson 序列化器 - Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); - //解决查询缓存转换异常的问题 - ObjectMapper om = new ObjectMapper(); - //这将使所有成员字段无需进一步注释即可序列化,而不仅仅是公共字段(默认设置)。 - om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); - //开启后序列化得类会带上全类名 - om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); - //om配置给jackson2JsonRedisSerializer - jackson2JsonRedisSerializer.setObjectMapper(om); - // 配置序列化(解决乱码的问题),过期时间600秒 - RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() - .entryTtl(Duration.ofSeconds(600)) - .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)) - .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) - .disableCachingNullValues(); - return RedisCacheManager.builder(factory) - .cacheDefaults(config) - .build(); - } -} -``` - -## 6. sql(复制到文本命名为.sql,然后导入数据库) - -```sql -/* - Navicat Premium Data Transfer - - Source Server : 127.0.0.1 - Source Server Type : MySQL - Source Server Version : 50730 - Source Host : localhost:3306 - Source Schema : book - - Target Server Type : MySQL - Target Server Version : 50730 - File Encoding : 65001 - - Date: 29/08/2021 18:07:23 -*/ - -SET NAMES utf8mb4; -SET FOREIGN_KEY_CHECKS = 0; - --- ---------------------------- --- Table structure for sys_menu --- ---------------------------- -DROP TABLE IF EXISTS `sys_menu`; -CREATE TABLE `sys_menu` ( - `menu_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '菜单ID', - `menu_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '菜单名称', - `parent_id` bigint(20) NULL DEFAULT 0 COMMENT '父菜单ID', - `order_num` int(4) NULL DEFAULT 0 COMMENT '显示顺序', - `path` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '路由地址', - `component` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '组件路径', - `is_frame` int(1) NULL DEFAULT 1 COMMENT '是否为外链(0是 1否)', - `is_cache` int(1) NULL DEFAULT 0 COMMENT '是否缓存(0缓存 1不缓存)', - `menu_type` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '菜单类型(M目录 C菜单 F按钮)', - `visible` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '菜单状态(0显示 1隐藏)', - `status` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '菜单状态(0正常 1停用)', - `perms` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限标识', - `icon` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '#' COMMENT '菜单图标', - `create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '创建者', - `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', - `update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '更新者', - `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '备注', - PRIMARY KEY (`menu_id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1061 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '菜单权限表' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Records of sys_menu --- ---------------------------- -INSERT INTO `sys_menu` VALUES (1, '系统管理', 0, 1, 'system', NULL, 1, 0, 'M', '0', '0', '', 'system', 'admin', '2021-04-30 17:27:28', '', NULL, '系统管理目录'); -INSERT INTO `sys_menu` VALUES (2, '系统监控', 0, 2, 'monitor', NULL, 1, 0, 'M', '0', '0', '', 'monitor', 'admin', '2021-04-30 17:27:28', '', NULL, '系统监控目录'); -INSERT INTO `sys_menu` VALUES (3, '系统工具', 0, 3, 'tool', NULL, 1, 0, 'M', '0', '0', '', 'tool', 'admin', '2021-04-30 17:27:28', '', NULL, '系统工具目录'); -INSERT INTO `sys_menu` VALUES (4, '若依官网', 0, 4, 'http://ruoyi.vip', NULL, 0, 0, 'M', '0', '0', '', 'guide', 'admin', '2021-04-30 17:27:28', '', NULL, '若依官网地址'); -INSERT INTO `sys_menu` VALUES (100, '用户管理', 1, 1, 'user', 'system/user/index', 1, 0, 'C', '0', '0', 'system:user:list', 'user', 'admin', '2021-04-30 17:27:28', '', NULL, '用户管理菜单'); -INSERT INTO `sys_menu` VALUES (101, '角色管理', 1, 2, 'role', 'system/role/index', 1, 0, 'C', '0', '0', 'system:role:list', 'peoples', 'admin', '2021-04-30 17:27:28', '', NULL, '角色管理菜单'); -INSERT INTO `sys_menu` VALUES (102, '菜单管理', 1, 3, 'menu', 'system/menu/index', 1, 0, 'C', '0', '0', 'system:menu:list', 'tree-table', 'admin', '2021-04-30 17:27:28', '', NULL, '菜单管理菜单'); -INSERT INTO `sys_menu` VALUES (103, '部门管理', 1, 4, 'dept', 'system/dept/index', 1, 0, 'C', '0', '0', 'system:dept:list', 'tree', 'admin', '2021-04-30 17:27:28', '', NULL, '部门管理菜单'); -INSERT INTO `sys_menu` VALUES (104, '岗位管理', 1, 5, 'post', 'system/post/index', 1, 0, 'C', '0', '0', 'system:post:list', 'post', 'admin', '2021-04-30 17:27:28', '', NULL, '岗位管理菜单'); -INSERT INTO `sys_menu` VALUES (105, '字典管理', 1, 6, 'dict', 'system/dict/index', 1, 0, 'C', '0', '0', 'system:dict:list', 'dict', 'admin', '2021-04-30 17:27:28', '', NULL, '字典管理菜单'); -INSERT INTO `sys_menu` VALUES (106, '参数设置', 1, 7, 'config', 'system/config/index', 1, 0, 'C', '0', '0', 'system:config:list', 'edit', 'admin', '2021-04-30 17:27:28', '', NULL, '参数设置菜单'); -INSERT INTO `sys_menu` VALUES (107, '通知公告', 1, 8, 'notice', 'system/notice/index', 1, 0, 'C', '0', '0', 'system:notice:list', 'message', 'admin', '2021-04-30 17:27:28', '', NULL, '通知公告菜单'); -INSERT INTO `sys_menu` VALUES (108, '日志管理', 1, 9, 'log', '', 1, 0, 'M', '0', '0', '', 'log', 'admin', '2021-04-30 17:27:28', '', NULL, '日志管理菜单'); -INSERT INTO `sys_menu` VALUES (109, '在线用户', 2, 1, 'online', 'monitor/online/index', 1, 0, 'C', '0', '0', 'monitor:online:list', 'online', 'admin', '2021-04-30 17:27:28', '', NULL, '在线用户菜单'); -INSERT INTO `sys_menu` VALUES (110, '定时任务', 2, 2, 'job', 'monitor/job/index', 1, 0, 'C', '0', '0', 'monitor:job:list', 'job', 'admin', '2021-04-30 17:27:28', '', NULL, '定时任务菜单'); -INSERT INTO `sys_menu` VALUES (111, '数据监控', 2, 3, 'druid', 'monitor/druid/index', 1, 0, 'C', '0', '0', 'monitor:druid:list', 'druid', 'admin', '2021-04-30 17:27:28', '', NULL, '数据监控菜单'); -INSERT INTO `sys_menu` VALUES (112, '服务监控', 2, 4, 'server', 'monitor/server/index', 1, 0, 'C', '0', '0', 'monitor:server:list', 'server', 'admin', '2021-04-30 17:27:28', '', NULL, '服务监控菜单'); -INSERT INTO `sys_menu` VALUES (113, '缓存监控', 2, 5, 'cache', 'monitor/cache/index', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis', 'admin', '2021-04-30 17:27:28', '', NULL, '缓存监控菜单'); -INSERT INTO `sys_menu` VALUES (114, '表单构建', 3, 1, 'build', 'tool/build/index', 1, 0, 'C', '0', '0', 'tool:build:list', 'build', 'admin', '2021-04-30 17:27:28', '', NULL, '表单构建菜单'); -INSERT INTO `sys_menu` VALUES (115, '代码生成', 3, 2, 'gen', 'tool/gen/index', 1, 0, 'C', '0', '0', 'tool:gen:list', 'code', 'admin', '2021-04-30 17:27:28', '', NULL, '代码生成菜单'); -INSERT INTO `sys_menu` VALUES (116, '系统接口', 3, 3, 'swagger', 'tool/swagger/index', 1, 0, 'C', '0', '0', 'tool:swagger:list', 'swagger', 'admin', '2021-04-30 17:27:28', '', NULL, '系统接口菜单'); -INSERT INTO `sys_menu` VALUES (500, '操作日志', 108, 1, 'operlog', 'monitor/operlog/index', 1, 0, 'C', '0', '0', 'monitor:operlog:list', 'form', 'admin', '2021-04-30 17:27:28', '', NULL, '操作日志菜单'); -INSERT INTO `sys_menu` VALUES (501, '登录日志', 108, 2, 'logininfor', 'monitor/logininfor/index', 1, 0, 'C', '0', '0', 'monitor:logininfor:list', 'logininfor', 'admin', '2021-04-30 17:27:28', '', NULL, '登录日志菜单'); -INSERT INTO `sys_menu` VALUES (1001, '用户查询', 100, 1, '', '', 1, 0, 'F', '0', '0', 'system:user:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1002, '用户新增', 100, 2, '', '', 1, 0, 'F', '0', '0', 'system:user:add', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1003, '用户修改', 100, 3, '', '', 1, 0, 'F', '0', '0', 'system:user:edit', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1004, '用户删除', 100, 4, '', '', 1, 0, 'F', '0', '0', 'system:user:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1005, '用户导出', 100, 5, '', '', 1, 0, 'F', '0', '0', 'system:user:export', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1006, '用户导入', 100, 6, '', '', 1, 0, 'F', '0', '0', 'system:user:import', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1007, '重置密码', 100, 7, '', '', 1, 0, 'F', '0', '0', 'system:user:resetPwd', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1008, '角色查询', 101, 1, '', '', 1, 0, 'F', '0', '0', 'system:role:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1009, '角色新增', 101, 2, '', '', 1, 0, 'F', '0', '0', 'system:role:add', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1010, '角色修改', 101, 3, '', '', 1, 0, 'F', '0', '0', 'system:role:edit', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1011, '角色删除', 101, 4, '', '', 1, 0, 'F', '0', '0', 'system:role:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1012, '角色导出', 101, 5, '', '', 1, 0, 'F', '0', '0', 'system:role:export', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1013, '菜单查询', 102, 1, '', '', 1, 0, 'F', '0', '0', 'system:menu:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1014, '菜单新增', 102, 2, '', '', 1, 0, 'F', '0', '0', 'system:menu:add', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1015, '菜单修改', 102, 3, '', '', 1, 0, 'F', '0', '0', 'system:menu:edit', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1016, '菜单删除', 102, 4, '', '', 1, 0, 'F', '0', '0', 'system:menu:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1017, '部门查询', 103, 1, '', '', 1, 0, 'F', '0', '0', 'system:dept:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1018, '部门新增', 103, 2, '', '', 1, 0, 'F', '0', '0', 'system:dept:add', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1019, '部门修改', 103, 3, '', '', 1, 0, 'F', '0', '0', 'system:dept:edit', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1020, '部门删除', 103, 4, '', '', 1, 0, 'F', '0', '0', 'system:dept:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1021, '岗位查询', 104, 1, '', '', 1, 0, 'F', '0', '0', 'system:post:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1022, '岗位新增', 104, 2, '', '', 1, 0, 'F', '0', '0', 'system:post:add', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1023, '岗位修改', 104, 3, '', '', 1, 0, 'F', '0', '0', 'system:post:edit', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1024, '岗位删除', 104, 4, '', '', 1, 0, 'F', '0', '0', 'system:post:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1025, '岗位导出', 104, 5, '', '', 1, 0, 'F', '0', '0', 'system:post:export', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1026, '字典查询', 105, 1, '#', '', 1, 0, 'F', '0', '0', 'system:dict:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1027, '字典新增', 105, 2, '#', '', 1, 0, 'F', '0', '0', 'system:dict:add', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1028, '字典修改', 105, 3, '#', '', 1, 0, 'F', '0', '0', 'system:dict:edit', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1029, '字典删除', 105, 4, '#', '', 1, 0, 'F', '0', '0', 'system:dict:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1030, '字典导出', 105, 5, '#', '', 1, 0, 'F', '0', '0', 'system:dict:export', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1031, '参数查询', 106, 1, '#', '', 1, 0, 'F', '0', '0', 'system:config:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1032, '参数新增', 106, 2, '#', '', 1, 0, 'F', '0', '0', 'system:config:add', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1033, '参数修改', 106, 3, '#', '', 1, 0, 'F', '0', '0', 'system:config:edit', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1034, '参数删除', 106, 4, '#', '', 1, 0, 'F', '0', '0', 'system:config:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1035, '参数导出', 106, 5, '#', '', 1, 0, 'F', '0', '0', 'system:config:export', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1036, '公告查询', 107, 1, '#', '', 1, 0, 'F', '0', '0', 'system:notice:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1037, '公告新增', 107, 2, '#', '', 1, 0, 'F', '0', '0', 'system:notice:add', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1038, '公告修改', 107, 3, '#', '', 1, 0, 'F', '0', '0', 'system:notice:edit', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1039, '公告删除', 107, 4, '#', '', 1, 0, 'F', '0', '0', 'system:notice:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1040, '操作查询', 500, 1, '#', '', 1, 0, 'F', '0', '0', 'monitor:operlog:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1041, '操作删除', 500, 2, '#', '', 1, 0, 'F', '0', '0', 'monitor:operlog:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1042, '日志导出', 500, 4, '#', '', 1, 0, 'F', '0', '0', 'monitor:operlog:export', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1043, '登录查询', 501, 1, '#', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1044, '登录删除', 501, 2, '#', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1045, '日志导出', 501, 3, '#', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:export', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1046, '在线查询', 109, 1, '#', '', 1, 0, 'F', '0', '0', 'monitor:online:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1047, '批量强退', 109, 2, '#', '', 1, 0, 'F', '0', '0', 'monitor:online:batchLogout', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1048, '单条强退', 109, 3, '#', '', 1, 0, 'F', '0', '0', 'monitor:online:forceLogout', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1049, '任务查询', 110, 1, '#', '', 1, 0, 'F', '0', '0', 'monitor:job:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1050, '任务新增', 110, 2, '#', '', 1, 0, 'F', '0', '0', 'monitor:job:add', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1051, '任务修改', 110, 3, '#', '', 1, 0, 'F', '0', '0', 'monitor:job:edit', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1052, '任务删除', 110, 4, '#', '', 1, 0, 'F', '0', '0', 'monitor:job:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1053, '状态修改', 110, 5, '#', '', 1, 0, 'F', '0', '0', 'monitor:job:changeStatus', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1054, '任务导出', 110, 7, '#', '', 1, 0, 'F', '0', '0', 'monitor:job:export', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1055, '生成查询', 115, 1, '#', '', 1, 0, 'F', '0', '0', 'tool:gen:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1056, '生成修改', 115, 2, '#', '', 1, 0, 'F', '0', '0', 'tool:gen:edit', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1057, '生成删除', 115, 3, '#', '', 1, 0, 'F', '0', '0', 'tool:gen:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1058, '导入代码', 115, 2, '#', '', 1, 0, 'F', '0', '0', 'tool:gen:import', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1059, '预览代码', 115, 4, '#', '', 1, 0, 'F', '0', '0', 'tool:gen:preview', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); -INSERT INTO `sys_menu` VALUES (1060, '生成代码', 115, 5, '#', '', 1, 0, 'F', '0', '0', 'tool:gen:code', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); - --- ---------------------------- --- Table structure for sys_role --- ---------------------------- -DROP TABLE IF EXISTS `sys_role`; -CREATE TABLE `sys_role` ( - `role_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色ID', - `role_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色名称', - `role_key` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色权限字符串', - `role_sort` int(4) NOT NULL COMMENT '显示顺序', - `data_scope` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '1' COMMENT '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)', - `menu_check_strictly` tinyint(1) NULL DEFAULT 1 COMMENT '菜单树选择项是否关联显示', - `dept_check_strictly` tinyint(1) NULL DEFAULT 1 COMMENT '部门树选择项是否关联显示', - `status` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色状态(0正常 1停用)', - `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', - `create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '创建者', - `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', - `update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '更新者', - `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`role_id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色信息表' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Records of sys_role --- ---------------------------- -INSERT INTO `sys_role` VALUES (1, '超级管理员', 'admin', 1, '1', 1, 1, '0', '0', 'admin', '2021-04-30 17:27:28', '', NULL, '超级管理员'); -INSERT INTO `sys_role` VALUES (2, '普通角色', 'common', 2, '2', 1, 1, '0', '0', 'admin', '2021-04-30 17:27:28', '', NULL, '普通角色'); - --- ---------------------------- --- Table structure for sys_role_menu --- ---------------------------- -DROP TABLE IF EXISTS `sys_role_menu`; -CREATE TABLE `sys_role_menu` ( - `role_id` bigint(20) NOT NULL COMMENT '角色ID', - `menu_id` bigint(20) NOT NULL COMMENT '菜单ID', - PRIMARY KEY (`role_id`, `menu_id`) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色和菜单关联表' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Records of sys_role_menu --- ---------------------------- -INSERT INTO `sys_role_menu` VALUES (2, 1); -INSERT INTO `sys_role_menu` VALUES (2, 2); -INSERT INTO `sys_role_menu` VALUES (2, 3); -INSERT INTO `sys_role_menu` VALUES (2, 4); -INSERT INTO `sys_role_menu` VALUES (2, 100); -INSERT INTO `sys_role_menu` VALUES (2, 101); -INSERT INTO `sys_role_menu` VALUES (2, 102); -INSERT INTO `sys_role_menu` VALUES (2, 103); -INSERT INTO `sys_role_menu` VALUES (2, 104); -INSERT INTO `sys_role_menu` VALUES (2, 105); -INSERT INTO `sys_role_menu` VALUES (2, 106); -INSERT INTO `sys_role_menu` VALUES (2, 107); -INSERT INTO `sys_role_menu` VALUES (2, 108); -INSERT INTO `sys_role_menu` VALUES (2, 109); -INSERT INTO `sys_role_menu` VALUES (2, 110); -INSERT INTO `sys_role_menu` VALUES (2, 111); -INSERT INTO `sys_role_menu` VALUES (2, 112); -INSERT INTO `sys_role_menu` VALUES (2, 113); -INSERT INTO `sys_role_menu` VALUES (2, 114); -INSERT INTO `sys_role_menu` VALUES (2, 115); -INSERT INTO `sys_role_menu` VALUES (2, 116); -INSERT INTO `sys_role_menu` VALUES (2, 500); -INSERT INTO `sys_role_menu` VALUES (2, 501); -INSERT INTO `sys_role_menu` VALUES (2, 1000); -INSERT INTO `sys_role_menu` VALUES (2, 1001); -INSERT INTO `sys_role_menu` VALUES (2, 1002); -INSERT INTO `sys_role_menu` VALUES (2, 1003); -INSERT INTO `sys_role_menu` VALUES (2, 1004); -INSERT INTO `sys_role_menu` VALUES (2, 1005); -INSERT INTO `sys_role_menu` VALUES (2, 1006); -INSERT INTO `sys_role_menu` VALUES (2, 1007); -INSERT INTO `sys_role_menu` VALUES (2, 1008); -INSERT INTO `sys_role_menu` VALUES (2, 1009); -INSERT INTO `sys_role_menu` VALUES (2, 1010); -INSERT INTO `sys_role_menu` VALUES (2, 1011); -INSERT INTO `sys_role_menu` VALUES (2, 1012); -INSERT INTO `sys_role_menu` VALUES (2, 1013); -INSERT INTO `sys_role_menu` VALUES (2, 1014); -INSERT INTO `sys_role_menu` VALUES (2, 1015); -INSERT INTO `sys_role_menu` VALUES (2, 1016); -INSERT INTO `sys_role_menu` VALUES (2, 1017); -INSERT INTO `sys_role_menu` VALUES (2, 1018); -INSERT INTO `sys_role_menu` VALUES (2, 1019); -INSERT INTO `sys_role_menu` VALUES (2, 1020); -INSERT INTO `sys_role_menu` VALUES (2, 1021); -INSERT INTO `sys_role_menu` VALUES (2, 1022); -INSERT INTO `sys_role_menu` VALUES (2, 1023); -INSERT INTO `sys_role_menu` VALUES (2, 1024); -INSERT INTO `sys_role_menu` VALUES (2, 1025); -INSERT INTO `sys_role_menu` VALUES (2, 1026); -INSERT INTO `sys_role_menu` VALUES (2, 1027); -INSERT INTO `sys_role_menu` VALUES (2, 1028); -INSERT INTO `sys_role_menu` VALUES (2, 1029); -INSERT INTO `sys_role_menu` VALUES (2, 1030); -INSERT INTO `sys_role_menu` VALUES (2, 1031); -INSERT INTO `sys_role_menu` VALUES (2, 1032); -INSERT INTO `sys_role_menu` VALUES (2, 1033); -INSERT INTO `sys_role_menu` VALUES (2, 1034); -INSERT INTO `sys_role_menu` VALUES (2, 1035); -INSERT INTO `sys_role_menu` VALUES (2, 1036); -INSERT INTO `sys_role_menu` VALUES (2, 1037); -INSERT INTO `sys_role_menu` VALUES (2, 1038); -INSERT INTO `sys_role_menu` VALUES (2, 1039); -INSERT INTO `sys_role_menu` VALUES (2, 1040); -INSERT INTO `sys_role_menu` VALUES (2, 1041); -INSERT INTO `sys_role_menu` VALUES (2, 1042); -INSERT INTO `sys_role_menu` VALUES (2, 1043); -INSERT INTO `sys_role_menu` VALUES (2, 1044); -INSERT INTO `sys_role_menu` VALUES (2, 1045); -INSERT INTO `sys_role_menu` VALUES (2, 1046); -INSERT INTO `sys_role_menu` VALUES (2, 1047); -INSERT INTO `sys_role_menu` VALUES (2, 1048); -INSERT INTO `sys_role_menu` VALUES (2, 1049); -INSERT INTO `sys_role_menu` VALUES (2, 1050); -INSERT INTO `sys_role_menu` VALUES (2, 1051); -INSERT INTO `sys_role_menu` VALUES (2, 1052); -INSERT INTO `sys_role_menu` VALUES (2, 1053); -INSERT INTO `sys_role_menu` VALUES (2, 1054); -INSERT INTO `sys_role_menu` VALUES (2, 1055); -INSERT INTO `sys_role_menu` VALUES (2, 1056); -INSERT INTO `sys_role_menu` VALUES (2, 1057); -INSERT INTO `sys_role_menu` VALUES (2, 1058); -INSERT INTO `sys_role_menu` VALUES (2, 1059); -INSERT INTO `sys_role_menu` VALUES (2, 1060); - --- ---------------------------- --- Table structure for sys_user --- ---------------------------- -DROP TABLE IF EXISTS `sys_user`; -CREATE TABLE `sys_user` ( - `user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID', - `dept_id` bigint(20) NULL DEFAULT NULL COMMENT '部门ID', - `user_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户账号', - `nick_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户昵称', - `user_type` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '00' COMMENT '用户类型(00系统用户)', - `email` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '用户邮箱', - `phonenumber` varchar(11) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '手机号码', - `sex` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)', - `avatar` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '头像地址', - `password` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '密码', - `status` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '帐号状态(0正常 1停用)', - `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', - `login_ip` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '最后登录IP', - `login_date` datetime NULL DEFAULT NULL COMMENT '最后登录时间', - `create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '创建者', - `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', - `update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '更新者', - `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', - `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注', - PRIMARY KEY (`user_id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户信息表' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Records of sys_user --- ---------------------------- -INSERT INTO `sys_user` VALUES (1, 103, 'admin', '若依', '00', 'ry@163.com', '15888888888', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', '2021-04-30 17:27:28', 'admin', '2021-04-30 17:27:28', '', NULL, '管理员'); -INSERT INTO `sys_user` VALUES (2, 105, 'ry', '若依', '00', 'ry@qq.com', '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', '2021-04-30 17:27:28', 'admin', '2021-04-30 17:27:28', '', NULL, '测试员'); - --- ---------------------------- --- Table structure for sys_user_role --- ---------------------------- -DROP TABLE IF EXISTS `sys_user_role`; -CREATE TABLE `sys_user_role` ( - `user_id` bigint(20) NOT NULL COMMENT '用户ID', - `role_id` bigint(20) NOT NULL COMMENT '角色ID', - PRIMARY KEY (`user_id`, `role_id`) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户和角色关联表' ROW_FORMAT = Dynamic; - --- ---------------------------- --- Records of sys_user_role --- ---------------------------- -INSERT INTO `sys_user_role` VALUES (1, 1); -INSERT INTO `sys_user_role` VALUES (2, 2); - --- ---------------------------- --- Table structure for t_user --- ---------------------------- -DROP TABLE IF EXISTS `t_user`; -CREATE TABLE `t_user` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `username` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, - `password` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, - `email` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `username`(`username`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; - --- ---------------------------- --- Records of t_user --- ---------------------------- -INSERT INTO `t_user` VALUES (1, 'admin', 'admin', 'admin@atguigu.com'); -INSERT INTO `t_user` VALUES (2, 'test', 'test', 'test@test.com'); -INSERT INTO `t_user` VALUES (3, 'wzg168', '123456', 'wzg168@qq.com'); -INSERT INTO `t_user` VALUES (4, 'bbj168', '666666', 'bbj168@qq.com'); -INSERT INTO `t_user` VALUES (5, 'abc168', '666666', 'abc168@qq.com'); -INSERT INTO `t_user` VALUES (6, 'admin250', '123456', '362165265@qq.com'); -INSERT INTO `t_user` VALUES (7, 'admin235', '123456', '362165265@qq.com'); -INSERT INTO `t_user` VALUES (8, 'aaaaaaaaa', 'aaaaaa', '362165265@qq.com'); -INSERT INTO `t_user` VALUES (9, 'aaaaa', 'aaaaa', '362165265@qq.com'); - --- ---------------------------- --- Table structure for test --- ---------------------------- -DROP TABLE IF EXISTS `test`; -CREATE TABLE `test` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `seqno` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, - `update_time` datetime NULL DEFAULT NULL, - `create_time` datetime NULL DEFAULT NULL, - `some` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; - --- ---------------------------- --- Records of test --- ---------------------------- -INSERT INTO `test` VALUES (1, NULL, '2021-08-27 15:12:29', '2021-08-27 15:12:29', 'some'); - --- ---------------------------- --- Table structure for test07 --- ---------------------------- -DROP TABLE IF EXISTS `test07`; -CREATE TABLE `test07` ( - `k1` int(11) NOT NULL, - `k2` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, - `col1` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, - `col2` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, - PRIMARY KEY (`k1`, `k2`) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; - --- ---------------------------- --- Records of test07 --- ---------------------------- -INSERT INTO `test07` VALUES (1, '111', 'xxxx', NULL); - --- ---------------------------- --- Table structure for test2 --- ---------------------------- -DROP TABLE IF EXISTS `test2`; -CREATE TABLE `test2` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `refid` int(11) NOT NULL, - `some2` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; - --- ---------------------------- --- Records of test2 --- ---------------------------- - -SET FOREIGN_KEY_CHECKS = 1; -``` - -## 7. mybatis-plus 自动生成代码 - -# 二、SpringSecurity认证 - -## 1. SpringSecurity的工作流程 - ->**一个web请求会经过一条过滤器链,在经过过滤器链的过程中会完成认证与授权,如果中间发现这条请求未认证或者未授权,会根据被保护API的权限去抛出异常,然后由异常处理器去处理这些异常。** - -图片显示流程 - -[//]: # () - -[//]: # () -[//]: # () - -[//]: # () -[//]: # () - -```java -/** -如上图: - 1. 一个请求想要访问到API就会以从左到右的形式经过蓝线框框里面的过滤器 - - 其中绿色部分是我们本篇主要讲的负责认证的过滤器 - - 蓝色部分负责异常处理 - - 橙色部分则是负责授权 - 2. 图中的这两个绿色过滤器是Spring Security对form表单认证和Basic认证内置的两个Filter,JWT认证方式用不上。 - 3. Spring Security配置中有两个叫formLogin和httpBasic的配置项,在配置中打开了它俩就对应着打开了上面的过滤器。 - 4. formLogin对应着你form表单认证方式,即UsernamePasswordAuthenticationFilter。 - 5. httpBasic对应着Basic认证方式,即BasicAuthenticationFilter。 - 6. 换言之,你配置了这两种认证方式,过滤器链中才会加入它们,否则它们是不会被加到过滤器链中去的。 - 7. 因为Spring Security自带的过滤器中是没有针对JWT这种认证方式的,所以我们会写一个JWT的认证过滤器,然后放在绿色的位置进行认证工作。 -``` - -## 2. SpringSecurity的重要概念 - -```apl -1. SecurityContext:上下文对象,Authentication对象会放在里面。 -2. SecurityContextHolder:用于拿到上下文对象的静态工具类。 -3. Authentication:认证接口,定义了认证对象的数据形式。 -4. AuthenticationManager:用于校验Authentication,返回一个认证完成后• Authentication对象。 -``` - -### 1. SecurityContext - -> 上下文对象,认证后的数据就放在这里面,接口定义如下: - -```java -public interface SecurityContext extends Serializable { - // 获取Authentication对象 - Authentication getAuthentication(); - // 放入Authentication对象 - void setAuthentication(Authentication authentication); -} -``` - -### 2. SecurityContextHolder - -> 可以说是SecurityContext的工具类,用于get 或 set 或者 clear SecurityContext,**默认会把数据都存储到当前线程中**。 - -```java -public class SecurityContextHolder { - public static void clearContext() { - strategy.clearContext(); - } - public static SecurityContext getContext() { - return strategy.getContext(); - } - public static void setContext(SecurityContext context) { - strategy.setContext(context); - } -} -``` - -### 3. Authentication - -> Authentication只是定义了一种在SpringSecurity进行认证过的数据的数据形式应该是怎么样的,要有权限,要有密码,要有身份信息,要有额外信息。 - -```java -public interface Authentication extends Principal, Serializable { - Collection extends GrantedAuthority> getAuthorities(); - Object getCredentials(); - Object getDetails(); - Object getPrincipal(); - boolean isAuthenticated(); - void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException; -} -``` - -```java -/** -1. getAuthorities: 获取用户权限,一般情况下获取到的是用户的角色信息。 -2. getCredentials: 获取证明用户认证的信息,通常情况下获取到的是密码等信息。 -3. getDetails: 获取用户的额外信息,(这部分信息可以是我们的用户表中的信息)。 -4. getPrincipal: 获取用户身份信息,在未认证的情况下获取到的是用户名,在已认证的情况下获取到的是 UserDetails。 -6. isAuthenticated: 获取当前 Authentication 是否已认证。 -7. setAuthenticated: 设置当前 Authentication 是否已认证(true or false)。 -``` - -### 4. AuthenticationManager - -> AuthenticationManager定义了一个认证方法,它将一个未认证的Authentication传入,返回一个已认证的Authentication,默认使用的实现类为:ProviderManager。 - -```java -public interface AuthenticationManager { - // 认证方法 - Authentication authenticate(Authentication authentication)throws AuthenticationException; -} -``` - -### 5. Spring Security进行认证的流程: - -```java -/** -1. 先是一个请求带着身份信息进来 -2. 经过AuthenticationManager的认证, -3. 再通过SecurityContextHolder获取SecurityContext, -4. 最后将认证后的信息放入到SecurityContext。 -``` - -## 3. 必要组件 - -### 1. 定义加密器Bean - -> 这个Bean是不必可少的,Spring Security在认证操作时会使用我们定义的这个加密器,如果没有则会出现异常。 - -```java - /** - * spring容器中注入加密算法 - * - * @return 强散列哈希加密实现 - */ - @Bean - public BCryptPasswordEncoder bCryptPasswordEncoder() { - return new BCryptPasswordEncoder(); - } -``` - -### 2.定义AuthenticationManager - ->这里将Spring Security自带的authenticationManager声明成Bean,声明它的作用是用它帮我们进行认证操作,调用这个Bean的authenticate方法会由Spring Security自动帮我们做认证。 - -```java - /** - * 解决 无法直接注入 AuthenticationManager - * - * @return return - * @throws Exception e - */ - @Bean - @Override - protected AuthenticationManager authenticationManager() throws Exception { - return super.authenticationManager(); - } -``` - - - -### 3. 实现UserDetailsService - ->实现UserDetailsService的抽象方法并返回一个UserDetails对象,认证过程中SpringSecurity会调用这个方法访问数据库进行对用户的搜索,逻辑什么都可以自定义,无论是从数据库中还是从缓存中,但是我们需要将我们查询出来的用户信息和权限信息组装成一个UserDetails返回。 -> ->UserDetails也是一个定义了数据形式的接口,用于保存我们从数据库中查出来的数据,其功能主要是验证账号状态和获取权限 - -```java -/** - * 自定义用户认证逻辑并交给spring管理 - * - * @Date: 2021-08-27-13:50 - * @Author lj - */ -@Slf4j -@Service("userDetailsService") -@RequiredArgsConstructor -public class UserDetailsServiceImpl implements UserDetailsService { - - private final UserService userService; - - private final PermissionService permissionService; - - @Override - public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - log.info("②-用户信息认证"); - //认证 判断用户是否存在于数据库内 - User user = userService.getOne(new QueryWrapper().eq("user_name", username)); - if (Validator.isNull(user)) { - log.info("登录用户:{} 不存在.", username); - throw new RuntimeException("登录用户:" + username + "不存在"); - } else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) { - log.info("登录用户:{} 已被删除.", username); - throw new RuntimeException("登录用户:" + username + "已被删除"); - } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) { - log.info("登录用户:{} 已被停用.", username); - throw new RuntimeException("登录用户:" + username + "已被停用"); - } - //满足以上信息认证通过 - //设置用户权限和当前用户基本信息 - Set permissions = permissionService.getMenuPermission(user); - //角色权限信息 - Set rolePermission = permissionService.getRolePermission(user); - user.setRoles(rolePermission); - //返回安全框架需要得用户信息和自定义得一些信息 - return new SecurityUser(permissions, user); - } -} -``` - -### 4. TokenUtil - -> 由于我们是JWT的认证模式,所以我们也需要一个帮我们操作Token的工具类,一般来说它具有以下三个方法就够了 -> -> - 创建token -> - 验证token -> - 反解析token中的信息 - -```java -package com.mangmang.security.securities.jwt; - -import cn.hutool.core.lang.Validator; -import com.mangmang.security.securities.domain.SecurityUser; -import com.mangmang.security.utils.AddressUtils; -import com.mangmang.security.utils.Constants; -import com.mangmang.security.utils.IpUtils; -import eu.bitwalker.useragentutils.UserAgent; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import lombok.Data; -import lombok.RequiredArgsConstructor; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Component; - -import javax.servlet.http.HttpServletRequest; -import java.util.HashMap; -import java.util.UUID; -import java.util.concurrent.TimeUnit; - -/** - * @Date: 2021-08-26-15:00 - * @Author lj - */ -@Data -@Component -@ConfigurationProperties("token") -@RequiredArgsConstructor -public class TokenService { - - private final RedisTemplate redisTemplate; - - /** - * 令牌自定义标识 - */ - private String header; - - /** - * 令牌密钥 - */ - private String secret; - - /** - * 令牌有效期(默认30分钟) - */ - private int expireTime; - - - /** - * 根据token获取用户信息 - * - * @param request 请求 - * @return 用户信息 - */ - public SecurityUser getSecurityUser(HttpServletRequest request) { - //获取token - String token = request.getHeader(this.header); - if (Validator.isNotEmpty(token)) { - //获取用户唯一标识 - String uniquelyIdentifies = getUniquelyIdentifiesByToken(token); - //返回该用户信息 - return (SecurityUser) redisTemplate.opsForValue().get(Constants.LOGIN_TOKEN_KEY + uniquelyIdentifies); - } - return null; - } - - /** - * 生成一个token - * - * @param securityUser 安全框架需要得用户信息 - * @param request 请求 - * @return token - */ - public String createToken(SecurityUser securityUser, HttpServletRequest request) { - //设置用户唯一标识 - securityUser.setUniquelyIdentifies(UUID.randomUUID().toString()); - //获取用户ip地址 - String ipAddr = IpUtils.getIpAddr(request); - securityUser.setIpaddr(ipAddr); - //获取客户端信息 - UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent")); - //根据ip获取用户真实地址 - securityUser.setLoginLocation(AddressUtils.getRealAddressByIp(ipAddr)); - //设置登录浏览器信息 - securityUser.setBrowser(userAgent.getBrowser().getName()); - //设置操作系统信息 - securityUser.setOs(userAgent.getOperatingSystem().getName()); - //用户信息存入redis - refreshRedisTokenTime(securityUser); - //token得主体信息; - HashMap tokenInfo = new HashMap<>(64); - tokenInfo.put(Constants.LOGIN_USER_KEY, securityUser.getUniquelyIdentifies()); - //生成token - return Jwts.builder() - //jwt头使用默认 - //有效载荷 - .setClaims(tokenInfo) - //签名哈希 - //让上面两部分数据签名通过HS256算法生成哈希,确保数据不会被篡改 - .signWith(SignatureAlgorithm.HS256, secret) - .compact(); - } - - - /** - * 刷新存放在redis中用户登录相关信息时间 - */ - public void refreshRedisTokenTime(SecurityUser securityUser) { - //设置当前时间为登录时间 - securityUser.setLoginTime(System.currentTimeMillis()); - //登录时间+30分钟为过期时间 - securityUser.setExpireTime(securityUser.getLoginTime() + expireTime * Constants.MILLIS_MINUTE); - //把用户登录信息存入缓存expireTime分钟后过期 - redisTemplate - .opsForValue() - .set(Constants.LOGIN_TOKEN_KEY + securityUser.getUniquelyIdentifies(), securityUser, expireTime, TimeUnit.MINUTES); - } - - /** - * 小于10分钟更新存放在redis中token过期时间 - * 判断redis中token过期时间是否小于10分钟 - * - * @param securityUser 安全用户信息 - */ - public void verifyToken(SecurityUser securityUser) { - if (securityUser.getExpireTime() - securityUser.getLoginTime() <= Constants.MILLIS_MINUTE_TEN) { - refreshRedisTokenTime(securityUser); - } - } - - /** - * 通过token获取用户唯一标识 - * - * @param token token - * @return 用户唯一标识 - */ - public String getUniquelyIdentifiesByToken(String token) { - return (String) Jwts.parser() - .setSigningKey(secret) - .parseClaimsJws(token) - .getBody() - .get(Constants.LOGIN_USER_KEY); - } - - /** - * 根据用户唯一标识删除用户信息 - * - * @param uniquelyIdentifies 用户唯一标识; - */ - public void deleteSecurity(String uniquelyIdentifies) { - if (Validator.isNotEmpty(uniquelyIdentifies)) { - //删除redis中数据 - redisTemplate.delete(Constants.LOGIN_TOKEN_KEY + uniquelyIdentifies); - } - } -} -``` - -### 5. 自定义UserDetails - -```java -package com.mangmang.security.securities.domain; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.mangmang.security.entity.User; -import lombok.*; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; - -import java.io.Serializable; -import java.util.Collection; -import java.util.Set; - -/** - * security框架得用户信息 - * - * @Date: 2021-08-26-16:18 - * @Author lj - */ -@NoArgsConstructor -@Data -@ToString -public class SecurityUser implements UserDetails, Serializable { - - private static final long serialVersionUID = 1L; - - /** - * 用户唯一标识 - */ - private String uniquelyIdentifies; - - /** - * 登录时间 - */ - private Long loginTime; - - /** - * 过期时间 - */ - private Long expireTime; - - /** - * 登录IP地址 - */ - private String ipaddr; - - /** - * 登录地点 - */ - private String loginLocation; - - /** - * 浏览器类型 - */ - private String browser; - - /** - * 操作系统 - */ - private String os; - - /** - * 权限列表 - */ - private Set permissions; - - /** - * 用户信息 - */ - private User user; - - @Getter(AccessLevel.NONE) - private String username; - - /** - * 用户信息和权限信息 - * - * @param permissions 权限信息 - * @param user 用户信息 - */ - public SecurityUser(Set permissions, User user) { - this.permissions = permissions; - this.user = user; - } - - @Override - public Collection extends GrantedAuthority> getAuthorities() { - return null; - } - - @JsonIgnore - @Override - public String getPassword() { - return user.getPassword(); - } - - - @Override - public String getUsername() { - return user.getUserName(); - } - - - /** - * 账户是否未过期,过期无法验证 - * - * @return 未过期 - */ - @JsonIgnore - @Override - public boolean isAccountNonExpired() { - return true; - } - - /** - * 指定用户是否解锁,锁定的用户无法进行身份验证 - * - * @return 已解锁 - */ - @JsonIgnore - @Override - public boolean isAccountNonLocked() { - return true; - } - - /** - * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证 - * - * @return 未过期 - */ - @JsonIgnore - @Override - public boolean isCredentialsNonExpired() { - return true; - } - - /** - * 是否可用 ,禁用的用户不能身份验证 - * - * @return 可用 - */ - @JsonIgnore - @Override - public boolean isEnabled() { - return true; - } -} - -``` - - - -## 4. 具体实现 - -### 1. 认证方法和认证源码分析 - -> 访问一个系统,一般最先访问的是认证方法,这里我写了最简略的认证需要的几个步骤,因为实际系统中我们还要写登录记录啊,前台密码解密啊这些操作。 - -```java -/** - * security登录接口 - * - * @Date: 2021-08-29-17:05 - * @Author lj - */ -@Slf4j -@Component -@RequiredArgsConstructor -public class LoginService { - - private final AuthenticationManager authenticationManager; - - private final TokenService tokenService; - - private RedisTemplate redisTemplate; - - public String login(String username, String password, HttpServletRequest request) { - Authentication authentication = null; - try { - //1. 开始认证 - authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password)); - } catch (Exception e) { - if (e instanceof BadCredentialsException) { - log.error("用户密码不匹配"); - throw new RuntimeException("用户密码不匹配"); - } else { - e.printStackTrace(); - } - } - assert authentication != null; - //2. 获取认证成功后得用户信息 - SecurityUser securityUser = (SecurityUser) authentication.getPrincipal(); - System.out.println(securityUser); - //3. 缓存用户信息并生成token - return tokenService.createToken(securityUser, request); - } -} -``` - -```java -/** -1. 传入用户名和密码创建了一个UsernamePasswordAuthenticationToken对象,这是我们前面说过的Authentication的实现类,传入用户名和密码做构造参数,这个对象就是我们创建出来的未认证的Authentication对象。使用我们先前已经声明过的Bean-authenticationManager调用它的authenticate方法进行认证,返回一个认证完成的Authentication对象。 -2. 从Authentication对象中拿到我们的UserDetails对象,之前我们说过,认证后的Authentication对象调用它的getPrincipal()方法就可以拿到我们先前数据库查询后组装出来的UserDetails对象, -3. 然后创建token。把UserDetails对象放入缓存中,方便后面过滤器使用。 - -``` - -```java - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> { - return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported"); - }); - //还未认证成功过去用户名 - String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName(); - boolean cacheWasUsed = true; - //从缓存中去查用户名为XXX的对象 - UserDetails user = this.userCache.getUserFromCache(username); - //为空 - if (user == null) { - cacheWasUsed = false; - try { - //调用我们重写UserDetailsService的loadUserByUsername方法 - // 拿到我们自己组装好的UserDetails对象 - //AbstractUserDetailsAuthenticationProvider得实现类为DaoAuthenticationProvider由于retrieveUser为抽象方法所以调用子类方法 - user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication); - } catch (UsernameNotFoundException var6) { - this.logger.debug("User '" + username + "' not found"); - if (this.hideUserNotFoundExceptions) { - throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); - } - throw var6; - } - Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract"); - } - try { - // 校验账号是否禁用 - this.preAuthenticationChecks.check(user); - // 校验数据库查出来的密码,和我们传入的密码是否一致 - this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication); - } catch (AuthenticationException var7) { - if (!cacheWasUsed) { - throw var7; - } - - cacheWasUsed = false; - user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication); - this.preAuthenticationChecks.check(user); - this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication); - } - this.postAuthenticationChecks.check(user); - if (!cacheWasUsed) { - this.userCache.putUserInCache(user); - } - Object principalToReturn = user; - if (this.forcePrincipalAsString) { - principalToReturn = user.getUsername(); - } - return this.createSuccessAuthentication(principalToReturn, authentication, user); - } - -``` - - - -### 2. jwt认证过滤器 - ->有了token之后,我们要把过滤器放在过滤器链中,用于解析token,因为我们没有session,所以我们每次去辨别这是哪个用户的请求的时候,都是根据请求中的token来解析出来当前是哪个用户。 -> ->addFilterBefore的语义是添加一个Filter到XXXFilter之前,放在这里就是把`JwtAuthenticationTokenFilter`放在UsernamePasswordAuthenticationFilter之前,因为filter的执行也是有顺序的,我们必须要把我们的filter放在过滤器链中绿色的部分才会起到自动认证的效果。 -> ->具体实现如下: - -```java -/** - * token过滤器 验证token有效性 - * - * @Date: 2021-08-29-14:08 - * @Author lj - */ -@Slf4j -@Component -@RequiredArgsConstructor -public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { - - private final TokenService tokenService; - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - log.info("①=========jwt认证"); - //1.之前登录过 所以这里会获取到redis中完整得用户信息 - //1. 根据token获取用户唯一标识查询用户信息 - SecurityUser securityUser = tokenService.getSecurityUser(request); - //2. 获取上下文中是否有 authentication认证接口,定义了认证对象的数据形式。(本次请求得认证接口,包含当前访问接口的用户信息) - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (Validator.isNotNull(securityUser) && Validator.isNull(authentication)) { - //3 如果redis中数据小于10分钟刷新 - tokenService.verifyToken(securityUser); - //4. 组装一个authentication对象,把它放在上下文对象中,这样后面的过滤器看到我们上下文对象中有authentication对象,就相当于我们已经认证过了 - UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(securityUser, securityUser.getPassword(), securityUser.getAuthorities()); - //5. 设置网页详情,ip地址,sessionId等信息 - authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - //6. 将authentication信息放入到上下文对象中 - SecurityContextHolder.getContext().setAuthentication(authenticationToken); - } - //放行 - filterChain.doFilter(request, response); - } -} - -``` - -> 这样的话,每一个带有正确token的请求进来之后,都会找到它的账号信息,并放在上下文对象中,我们可以使用`SecurityContextHolder`很方便的拿到上下文对象中的`Authentication`对象。完成之后,启动我们的demo,可以看到过滤器链中有以下过滤器,其中我们自定义的是第5个 -> -> 登录👉拿到token👉请求带上token👉JWT过滤器拦截👉校验token👉将从缓存中查出来的对象放到上下文中 - -## 5. 代码优化 - -### 1.认证失败处理器 - -> 当用户未登录或者token解析失败时会触发这个处理器,返回一个非法访问的结果。 - -```java -/** - * 认证失败处理类 返回未授权 - * - * @Date: 2021-08-29-13:10 - * @Author lj - */ -@Component -public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable { - @Override - public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) { - String msg = String.format("请求访问:%s,认证失败,无法访问系统资源", request.getRequestURI()); - try { - // 设置返回的数据内容的数据类型和编码 - response.setContentType("text/html; charset=utf-8"); - response.getWriter().write(msg); - } catch (IOException ioException) { - ioException.printStackTrace(); - } - } -} - -``` - -### 2. 自定义退出处理 - -```java -/** - * 自定义退出处理类 返回成功 - * - * @Date: 2021-08-29-13:51 - * @Author lj - */ -@Slf4j -@Component -@RequiredArgsConstructor -public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler { - - private final TokenService tokenService; - - @Override - public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth) throws IOException { - //获取认证得用户 - SecurityUser securityUser = tokenService.getSecurityUser(request); - //不为空 - if (Validator.isNotNull(securityUser)) { - //获取用户姓名 - String userName = securityUser.getUser().getUserName(); - //删除redis缓存数据 - tokenService.deleteSecurity(securityUser.getUniquelyIdentifies()); - //删除上下文中的用户信息,只删除当前线程用户数据 - SecurityContextHolder.clearContext(); - //记录退出日志 - log.info("用户 {} 退出了系统", userName); - } - // 设置返回的数据内容的数据类型和编码 - response.setContentType("text/html; charset=utf-8"); - response.getWriter().write("用户为空"); - } -} - -``` - -### 3. 权限不足处理器 - -```java -/** - * 权限不足处理器 - * - * @Date: 2021-08-31-13:50 - * @Author lj - */ -@Slf4j -@Component -public class InsufficientPermissionsHandler implements AccessDeniedHandler { - @Override - public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) { - String msg = String.format("请求访问:%s,失败,无权限,请联系管理员", request.getRequestURI()); - response.setHeader("Cache-Control", "no-cache"); - response.setCharacterEncoding("UTF-8"); - response.setContentType("application/json"); - response.setStatus(HttpServletResponse.SC_FORBIDDEN); - try { - response.getWriter().println(msg); - response.getWriter().flush(); - } catch (IOException ioException) { - ioException.printStackTrace(); - } - } -} -``` - - - -## 6. security完整配置 - -```java -/** - * 自动配置时会判断是否存在WebSecurityConfigurerAdapter类, - * - * @Date: 2021-08-27-13:48 - * @Author lj - */ -@RequiredArgsConstructor -@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) -public class SecurityConfig extends WebSecurityConfigurerAdapter { - - /** - * 用户认证逻辑spring自动注入加入容器实现该接口得方法 - */ - private final UserDetailsService userDetailsService; - - /** - * 认证失败处理类 - */ - private final AuthenticationEntryPointImpl authenticationEntryPoint; - - - /** - * 无权限处理器 - */ - private final InsufficientPermissionsHandler insufficientPermissionsHandler; - - /** - * 退出成功处理 - */ - private final LogoutSuccessHandlerImpl logoutSuccessHandler; - - /** - * jwt认证过滤器 - */ - private final JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; - - - /** - * 配置security密码加密方式和自定义认证流程 - * - * @param auth 认证 - * @throws Exception 异常 - */ - @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder()); - } - - - /** - * spring容器中注入加密算法 - * - * @return 强散列哈希加密实现 - */ - @Bean - public BCryptPasswordEncoder bCryptPasswordEncoder() { - return new BCryptPasswordEncoder(); - } - - /** - * 解决 无法直接注入 AuthenticationManager - * - * @return return - * @throws Exception e - */ - @Bean - @Override - protected AuthenticationManager authenticationManager() throws Exception { - return super.authenticationManager(); - } - - - /** - * 跨域配置 - */ - @Bean - public CorsFilter corsFilter() { - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - CorsConfiguration config = new CorsConfiguration(); - config.setAllowCredentials(true); - // 设置访问源地址 - config.addAllowedOrigin("*"); - // 设置访问源请求头 - config.addAllowedHeader("*"); - // 设置访问源请求方法 - config.addAllowedMethod("*"); - // 设置访问源请求方法 - source.registerCorsConfiguration("/**", config); - return new CorsFilter(source); - } - - /** - * anyRequest | 匹配所有请求路径 - * access | SpringEl表达式结果为true时可以访问 - * anonymous | 匿名可以访问 - * denyAll | 用户不能访问 - * fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录) - * hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问 - * hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问 - * hasAuthority | 如果有参数,参数表示权限,则其权限可以访问 - * hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问 - * hasRole | 如果有参数,参数表示角色,则其角色可以访问 - * permitAll | 用户可以任意访问 - * rememberMe | 允许通过remember-me登录的用户访问 - * authenticated | 用户登录后可访问 - * - * @param http HttpSecurity - * @throws Exception e - */ - @Override - protected void configure(HttpSecurity http) throws Exception { - http // CSRF禁用,因为不使用session - .csrf().disable() - - //认证失败处理 - .exceptionHandling() - //没认证 - .authenticationEntryPoint(authenticationEntryPoint) - //无权限 - .accessDeniedHandler(insufficientPermissionsHandler) - - //session管理 - .and() - .sessionManagement() - //基于token,所以不需要session,设置为无状态 - .sessionCreationPolicy(SessionCreationPolicy.STATELESS) - - - //过滤请求 - .and() - .authorizeRequests() - //静态资源用户可以任意访问 - .antMatchers(HttpMethod.GET, URL_STATIC).permitAll() - //允许匿名访问得url - .antMatchers(URL_WHITELIST).anonymous() - //除以上配置路径外全部需要鉴权认证 - .anyRequest().authenticated() - - //配置完全开启x-frame-options - .and() - .headers() - .frameOptions() - .disable() - - //退出成功处理 - .and() - .logout() - .logoutUrl("/logout") - .logoutSuccessHandler(logoutSuccessHandler) - - //添加滤器 - .and() - //添加token认证过滤器到表单认证过滤器得前面 - .addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class) - //添加跨域配置 - .addFilterBefore(corsFilter(), JwtAuthenticationTokenFilter.class) - .addFilterBefore(corsFilter(), LogoutFilter.class); - } - - /** - * 需要放行的静态资源 - */ - private static final String[] URL_STATIC = { - "/*.html", - "/**/*.html", - "/**/*.css", - "/**/*.js" - }; - - /** - * 需要放行的url地址 - */ - private static final String[] URL_WHITELIST = { - "/swagger-ui.html", - "/swagger-resources/**", - "/webjars/**", - "/*/api-docs", - "/druid/**", - "/login" - }; -} - -``` - -## 7. 其它工具类 - -### 1. 枚举 - -```java -/** - * 用户状态 - * - * @Date: 2021-08-27-17:55 - * @Author lj - */ -public enum UserStatus { - /** - * 相当于创建UserStatus对象 - * 正常 - */ - OK("0", "正常"), - - /** - * 禁用 - */ - DISABLE("1", "停用"), - - /** - * 删除 - */ - DELETED("2", "删除"); - - - /** - * 代号 - */ - private final String code; - - /** - * 信息 - */ - private final String info; - - - UserStatus(String code, String info) { - this.code = code; - this.info = info; - } - - public String getCode() { - return code; - } - - public String getInfo() { - return info; - } -} -``` - -### 2. 静态常量 - -```java -/** - * 静态常量 - * - * @Date: 2021-08-26-15:35 - * @Author lj - */ -public class Constants { - /** - * 毫秒 - */ - public static final long MILLIS_SECOND = 1000; - - /** - * 1分钟 - */ - public static final long MILLIS_MINUTE = 60 * MILLIS_SECOND; - - /** - * 10分钟 - */ - public static final Long MILLIS_MINUTE_TEN = 10 * 60 * 1000L; - - /** - * 未知 - */ - public static final String UNKNOWN = "unknown"; - - /** - * 2 - */ - public static final Integer TWO = 2; - - /** - * 4 - */ - public static final Integer FOUR = 4; - - /** - * GBK - */ - public static final String GBK = "GBK"; - - /** - * UTF-8 - */ - public static final String UTF8 = "UTF-8"; - - - /** - * redis中登录用户唯一标识得key - */ - public static final String LOGIN_TOKEN_KEY = "login_tokens:"; - - /** - * token中用户唯一标识 - */ - public static final String LOGIN_USER_KEY = "login_user_key"; - - /** - * 401 未授权 - */ - public static final Integer UNAUTHORIZED = 401; -} -``` - -### 3. ip解析 - -```java -/** - * @Date: 2021-08-27-8:43 - * @Author lj - */ -@Slf4j -public class IpUtils { - /** - * 获取请求得ip地址 - * - * @param request 请求 - * @return ip地址 - */ - public static String getIpAddr(HttpServletRequest request) { - if (request == null) { - return "unknown"; - } - String ip = request.getHeader("x-forwarded-for"); - if (ip == null || ip.length() == 0) { - ip = request.getHeader("Proxy-Client-IP"); - } - if (ip == null || ip.length() == 0 || Constants.UNKNOWN.equalsIgnoreCase(ip)) { - ip = request.getHeader("X-Forwarded-For"); - } - if (ip == null || ip.length() == 0 || Constants.UNKNOWN.equalsIgnoreCase(ip)) { - ip = request.getHeader("WL-Proxy-Client-IP"); - } - if (ip == null || ip.length() == 0 || Constants.UNKNOWN.equalsIgnoreCase(ip)) { - ip = request.getHeader("X-Real-IP"); - } - - if (ip == null || ip.length() == 0 || Constants.UNKNOWN.equalsIgnoreCase(ip)) { - ip = request.getRemoteAddr(); - } - return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : EscapeUtil.clean(ip); - } - - /** - * 判断ip是否为内网ip - * - * @param ip ip - * @return 是或否 - */ - public static boolean internalIp(String ip) { - //将IPv4地址转换成字节 - byte[] addr = textToNumericFormatV4(ip); - - return internalIp(addr) || "127.0.0.1".equals(ip); - } - - /** - * 判断该该字节是否为内网ip - * - * @param addr 字节内网ip - * @return 是或否 - */ - private static boolean internalIp(byte[] addr) { - if (Validator.isNull(addr) || addr.length < Constants.TWO) { - return true; - } - final byte b0 = addr[0]; - final byte b1 = addr[1]; - // 10.x.x.x/8 - final byte section1 = 0x0A; - // 172.16.x.x/12 - final byte section2 = (byte) 0xAC; - final byte section3 = (byte) 0x10; - final byte section4 = (byte) 0x1F; - // 192.168.x.x/16 - final byte section5 = (byte) 0xC0; - final byte section6 = (byte) 0xA8; - switch (b0) { - case section1: - return true; - case section2: - if (b1 >= section3 && b1 <= section4) { - return true; - } - case section5: - if (b1 == section6) { - return true; - } - default: - return false; - } - } - - /** - * 将IPv4地址转换成字节 - * - * @param text IPv4地址 - * @return byte 字节 - */ - public static byte[] textToNumericFormatV4(String text) { - long number1 = 4294967295L; - long number2 = 255L; - long number3 = 16777215L; - long number4 = 65535L; - - if (text.length() == 0) { - return null; - } - byte[] bytes = new byte[4]; - String[] elements = text.split("\\.", -1); - try { - long l; - int i; - switch (elements.length) { - case 1: - l = Long.parseLong(elements[0]); - if ((l < 0L) || (l > number1)) { - return null; - } - bytes[0] = (byte) (int) (l >> 24 & 0xFF); - bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF); - bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); - bytes[3] = (byte) (int) (l & 0xFF); - break; - case 2: - l = Integer.parseInt(elements[0]); - if ((l < 0L) || (l > number2)) { - return null; - } - bytes[0] = (byte) (int) (l & 0xFF); - l = Integer.parseInt(elements[1]); - if ((l < 0L) || (l > number3)) { - return null; - } - bytes[1] = (byte) (int) (l >> 16 & 0xFF); - bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); - bytes[3] = (byte) (int) (l & 0xFF); - break; - case 3: - for (i = 0; i < Constants.TWO; ++i) { - l = Integer.parseInt(elements[i]); - if ((l < 0L) || (l > 255L)) { - return null; - } - bytes[i] = (byte) (int) (l & 0xFF); - } - l = Integer.parseInt(elements[2]); - if ((l < 0L) || (l > number4)) { - return null; - } - bytes[2] = (byte) (int) (l >> 8 & 0xFF); - bytes[3] = (byte) (int) (l & 0xFF); - break; - case 4: - for (i = 0; i < Constants.FOUR; ++i) { - l = Integer.parseInt(elements[i]); - if ((l < 0L) || (l > 255L)) { - return null; - } - bytes[i] = (byte) (int) (l & 0xFF); - } - break; - default: - return null; - } - } catch (NumberFormatException e) { - return null; - } - return bytes; - } - - /** - * 获取本机ip地址 - * - * @return ip地址 - */ - public static String getHostIp() { - try { - return InetAddress.getLocalHost().getHostAddress(); - } catch (UnknownHostException e) { - log.error(e.getMessage()); - } - return "127.0.0.1"; - } - - /** - * 获取本机名称 - * - * @return 主机名称 - */ - public static String getHostName() { - try { - return InetAddress.getLocalHost().getHostName(); - } catch (UnknownHostException e) { - log.error("UnknownHostException", e); - } - return "未知"; - } -} - -``` - -```java -package com.mangmang.security.utils; - -import cn.hutool.http.HTMLFilter; - -/** - * @Date: 2021-08-27-8:44 - * @Author lj - */ -public class EscapeUtil { - public static final String RE_HTML_MARK = "(<[^<]*?>)|(<[\\s]*?/[^<]*?>)|(<[^<]*?/[\\s]*?>)"; - - private static final char[][] TEXT = new char[64][]; - - /** - * 清除所有HTML标签,但是不删除标签内的内容 - * - * @param content 文本 - * @return 清除标签后的文本 - */ - public static String clean(String content) { - return new HTMLFilter().filter(content); - } -} -``` - -### 4. HTTP请求发送 - -```java -package com.mangmang.security.utils; - -import lombok.extern.slf4j.Slf4j; - -import javax.net.ssl.*; -import java.io.*; -import java.net.ConnectException; -import java.net.SocketTimeoutException; -import java.net.URL; -import java.net.URLConnection; -import java.nio.charset.StandardCharsets; -import java.security.cert.X509Certificate; - -/** - * @Date: 2021-08-27-9:22 - * @Author lj - */ -@Slf4j -public class HttpUtils { - /** - * 向指定 URL 发送GET方法的请求 - * - * @param url 发送请求的 URL - * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 - * @return 所代表远程资源的响应结果 - */ - public static String sendGet(String url, String param) { - return sendGet(url, param, Constants.UTF8); - } - - - /** - * 向指定 URL 发送GET方法的请求 - * - * @param url 发送请求的 URL - * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 - * @param contentType 编码类型 - * @return 所代表远程资源的响应结果 - */ - public static String sendGet(String url, String param, String contentType) { - StringBuilder result = new StringBuilder(); - BufferedReader in = null; - try { - String urlNameString = url + "?" + param; - log.info("sendGet - {}", urlNameString); - URL realUrl = new URL(urlNameString); - URLConnection connection = realUrl.openConnection(); - connection.setRequestProperty("accept", "*/*"); - connection.setRequestProperty("connection", "Keep-Alive"); - connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); - connection.connect(); - in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType)); - String line; - while ((line = in.readLine()) != null) { - result.append(line); - } - log.info("recv - {}", result); - } catch (ConnectException e) { - log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e); - } catch (SocketTimeoutException e) { - log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e); - } catch (IOException e) { - log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e); - } catch (Exception e) { - log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e); - } finally { - try { - if (in != null) { - in.close(); - } - } catch (Exception ex) { - log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); - } - } - return result.toString(); - } - - - /** - * 向指定 URL 发送POST方法的请求 - * - * @param url 发送请求的 URL - * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 - * @return 所代表远程资源的响应结果 - */ - public static String sendPost(String url, String param) { - PrintWriter out = null; - BufferedReader in = null; - StringBuilder result = new StringBuilder(); - try { - log.info("sendPost - {}", url); - URL realUrl = new URL(url); - URLConnection conn = realUrl.openConnection(); - conn.setRequestProperty("accept", "*/*"); - conn.setRequestProperty("connection", "Keep-Alive"); - conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); - conn.setRequestProperty("Accept-Charset", "utf-8"); - conn.setRequestProperty("contentType", "utf-8"); - conn.setDoOutput(true); - conn.setDoInput(true); - out = new PrintWriter(conn.getOutputStream()); - out.print(param); - out.flush(); - in = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8)); - String line; - while ((line = in.readLine()) != null) { - result.append(line); - } - log.info("recv - {}", result); - } catch (ConnectException e) { - result.setLength(0); - result.append("fail"); - log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e); - } catch (SocketTimeoutException e) { - result.setLength(0); - result.append("fail"); - log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e); - } catch (IOException e) { - result.setLength(0); - result.append("fail"); - log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e); - } catch (Exception e) { - result.setLength(0); - result.append("fail"); - log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e); - } finally { - try { - if (out != null) { - out.close(); - } - if (in != null) { - in.close(); - } - } catch (IOException ex) { - log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); - } - } - return result.toString(); - } - - public static String sendSslPost(String url, String param) { - StringBuilder result = new StringBuilder(); - String urlNameString = url + "?" + param; - try { - log.info("sendSSLPost - {}", urlNameString); - SSLContext sc = SSLContext.getInstance("SSL"); - sc.init(null, new TrustManager[]{new TrustAnyTrustManager()}, new java.security.SecureRandom()); - URL console = new URL(urlNameString); - HttpsURLConnection conn = (HttpsURLConnection) console.openConnection(); - conn.setRequestProperty("accept", "*/*"); - conn.setRequestProperty("connection", "Keep-Alive"); - conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); - conn.setRequestProperty("Accept-Charset", "utf-8"); - conn.setRequestProperty("contentType", "utf-8"); - conn.setDoOutput(true); - conn.setDoInput(true); - - conn.setSSLSocketFactory(sc.getSocketFactory()); - conn.setHostnameVerifier(new TrustAnyHostnameVerifier()); - conn.connect(); - InputStream is = conn.getInputStream(); - BufferedReader br = new BufferedReader(new InputStreamReader(is)); - String ret; - while ((ret = br.readLine()) != null) { - if (!"".equals(ret.trim())) { - result.append(new String(ret.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8)); - } - } - log.info("recv - {}", result); - conn.disconnect(); - br.close(); - } catch (ConnectException e) { - log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e); - } catch (SocketTimeoutException e) { - log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e); - } catch (IOException e) { - log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e); - } catch (Exception e) { - log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e); - } - return result.toString(); - } - - private static class TrustAnyTrustManager implements X509TrustManager { - @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) { - } - - @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) { - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[]{}; - } - } - - private static class TrustAnyHostnameVerifier implements HostnameVerifier { - @Override - public boolean verify(String hostname, SSLSession session) { - return true; - } - } -} -``` - -# 三 、SpringSecurity授权 - -## 1. 获取用户权限信息 - -```java -package com.mangmang.security.securities.service; - -import com.mangmang.security.entity.User; -import com.mangmang.security.securities.domain.SecurityUser; -import com.mangmang.security.service.MenuService; -import com.mangmang.security.service.RoleService; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; - -import java.util.HashSet; -import java.util.Set; - -/** - * 用户权限处理 - * - * @Date: 2021-08-30-17:26 - * @Author lj - */ -@Component -@RequiredArgsConstructor -public class PermissionService { - private final RoleService roleService; - - private final MenuService menuService; - - /** - * 获取角色权限 - * - * @param user 用户信息 - * @return 角色权限集合 - */ - public Set getRolePermission(User user) { - // 需要返回得权限集合 - Set roles = new HashSet<>(); - //如果为超级用户添加超级用户 - if (user.judgeIsAdmin()) { - roles.add("admin"); - } else { - //否则添加其它角色 - roles.addAll(roleService.findRolePermissionByUserId(user.getUserId())); - } - return roles; - } - - /** - * 获取菜单权限 - * - * @param user 用户户信息 - * @return 菜单权限集合 - */ - public Set getMenuPermission(User user) { - // 需要返回得权限集合 - Set menus = new HashSet<>(); - //如果为超级用户添加超级用户 - if (user.judgeIsAdmin()) { - menus.add("*:*:*"); - } else { - //添加两个集合得并集 - menus.addAll(menuService.findAllMenu(user.getUserId())); - } - return menus; - } -} -``` - -## 2. 自定义权限判断 - -```java -package com.mangmang.security.securities.service; - -import cn.hutool.core.lang.Validator; -import cn.hutool.core.util.StrUtil; -import com.mangmang.security.entity.Role; -import com.mangmang.security.securities.domain.SecurityUser; -import com.mangmang.security.securities.jwt.TokenService; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.web.context.request.RequestAttributes; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; - -import javax.servlet.http.HttpServletRequest; -import java.util.Set; - -/** - * 判断是否具有某个权限 - * 自定义权限实现,ss取自SpringSecurity首字母 - * - * @Date: 2021-08-30-18:21 - * @Author lj - */ -@Service("ss") -@RequiredArgsConstructor -public class PermissionJudgeService { - - /** - * 拥有所有菜单权限标识 - */ - private static final String ALL_PERMISSION = "*:*:*"; - - - /** - * 拥有管理员角色权限标识 - */ - private static final String SUPER_ADMIN = "admin"; - - - /** - * 分隔符 - */ - private static final String DELIMETER = ","; - - - /** - * token处理 - */ - private final TokenService tokenService; - - - /** - * 获取request - */ - public static HttpServletRequest getRequest() { - //获取当前线程得request - RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); - ServletRequestAttributes requestAttributes = (ServletRequestAttributes) attributes; - assert requestAttributes != null; - return requestAttributes.getRequest(); - } - - - /** - * 验证用户是否具备某权限 - * - * @param permission 需要判断的权限字符串 - * @return 是或否 - */ - public boolean hasPermi(String permission) { - //如果为null拒绝访问 - if (Validator.isEmpty(permission)) { - return false; - } - //获取用户信息 - SecurityUser securityUser = tokenService.getSecurityUser(getRequest()); - //如果用户或权限为空拒绝访问 - if (Validator.isNull(securityUser) || Validator.isNull(securityUser.getPermissions())) { - return false; - } - return hasPermissions(securityUser.getPermissions(), permission); - } - - /** - * 验证不具备某个权限 - * - * @param permission 需要判断的权限字符串 - * @return true=没有该权限 false=有该权限 - */ - public boolean lacksPermi(String permission) { - return !hasPermi(permission); - } - - - /** - * 验证用户是否具备以下任意权限 - * - * @param permissions 以逗号为分隔符的权限列表 - * @return true=有该权限 false=无该权限 - */ - public boolean hasAnyPermi(String permissions) { - if (Validator.isEmpty(permissions)) { - return false; - } - //获取用户信息 - SecurityUser securityUser = tokenService.getSecurityUser(getRequest()); - //如果用户或权限为空拒绝访问 - if (Validator.isNull(securityUser) || Validator.isNull(securityUser.getPermissions())) { - return false; - } - //获取权限列表 - Set authorities = securityUser.getPermissions(); - //遍历权限判断是否具有某一个权限 - for (String permission : permissions.split(DELIMETER)) { - if (permission != null && hasPermissions(authorities, permission)) { - return true; - } - } - return false; - } - - /** - * 公用判断是否拥有某个权限 - */ - private boolean hasPermissions(Set permissions, String permission) { - return permissions.contains(ALL_PERMISSION) || permissions.contains(StrUtil.trim(permission)); - } - - /** - * 判断用户是否具有某一角色 - * - * @param role 需要判断得角色 - * @return true=有这个权限 false=没有这个权限 - */ - public boolean hasRole(String role) { - if (Validator.isEmpty(role)) { - return false; - } - SecurityUser securityUser = tokenService.getSecurityUser(getRequest()); - if (Validator.isNull(securityUser) || Validator.isNull(securityUser.getUser().getRoles())) { - return false; - } - - for (String rolePerm : securityUser.getUser().getRoles()) { - if (SUPER_ADMIN.equals(rolePerm) || rolePerm.equals(StrUtil.trim(role))) { - return true; - } - } - return false; - } - - /** - * 验证用户是否不具备某角色,与hasRole逻辑相反。 - * - * @param role 角色名称 - * @return 用户是否不具备某角色 - */ - public boolean lacksRole(String role) { - return !hasRole(role); - } - - /** - * 验证用户是否具有以下任意一个角色 - * - * @param roles 以逗号为分隔符的角色列表 - * @return 用户是否具有以下任意一个角色 - */ - public boolean hasAnyRoles(String roles) { - if (Validator.isEmpty(roles)) { - return false; - } - SecurityUser securityUser = tokenService.getSecurityUser(getRequest()); - if (Validator.isNull(securityUser) || Validator.isEmpty(securityUser.getUser().getRoles())) { - return false; - } - //roles以逗号分割后循环有匹配得数据直接返回true - for (String role : roles.split(DELIMETER)) { - if (hasRole(role)) { - return true; - } - } - return false; - } -} -``` - -## 3. 测试类 - -```java -package com.mangmang.security.controller; - -import com.mangmang.security.securities.service.LoginService; -import io.swagger.annotations.ApiOperation; -import lombok.RequiredArgsConstructor; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import javax.servlet.http.HttpServletRequest; - -/** - * @Date: 2021-08-29-16:51 - * @Author lj - */ -@RestController -@RequiredArgsConstructor -public class LoginController { - - private final LoginService loginService; - - @ApiOperation("登录接口") - @GetMapping("/login") - public String login(String username, String password, HttpServletRequest request) { - return loginService.login(username, password, request); - } - - @PreAuthorize("@ss.hasPermi('system:menu:list')") - @ApiOperation("test是否具有system:menu:list权限") - @GetMapping("/vip1") - public String test() { - return "访问成功"; - } - - @PreAuthorize("@ss.hasPermi('system:menu:test')") - @ApiOperation("test是否具有system:menu:test权限") - @GetMapping("/vip2") - public String test2() { - return "访问成功"; - } -} -``` - -## 4. 查询权限得2个方法 - -```java -package com.mangmang.security.service.impl; - - -import cn.hutool.core.lang.Validator; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.extension.api.R; -import com.mangmang.security.entity.Menu; -import com.mangmang.security.entity.Role; -import com.mangmang.security.entity.RoleMenu; -import com.mangmang.security.entity.UserRole; -import com.mangmang.security.mapper.MenuMapper; -import com.mangmang.security.mapper.RoleMapper; -import com.mangmang.security.mapper.RoleMenuMapper; -import com.mangmang.security.mapper.UserRoleMapper; -import com.mangmang.security.service.MenuService; -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * - * 菜单权限表 服务实现类 - * - * - * @author 氓氓编程 - * @since 2021-08-26 - */ -@Slf4j -@Service -@RequiredArgsConstructor -public class MenuServiceImpl extends ServiceImpl implements MenuService { - - - private final RoleMapper roleMapper; - - private final UserRoleMapper userRoleMapper; - - private final RoleMenuMapper roleMenuMapper; - - @Override - public Set findAllMenu(Long userId) { - //查询该用户所有角色 - List userRoleList = userRoleMapper.selectList(new QueryWrapper().eq("user_id", userId)); - if (userRoleList.size() <= 0) { - return null; - } - Set roleIdSet = userRoleList.stream().map(UserRole::getRoleId).collect(Collectors.toSet()); - - //查询该角色中未被禁用角色 - QueryWrapper roleWrapper = new QueryWrapper<>(); - roleWrapper.in("role_id", roleIdSet); - roleWrapper.eq("status", 0); - List rolesIdList = roleMapper.selectList(roleWrapper); - Set roleIds = rolesIdList.stream().map(Role::getRoleId).collect(Collectors.toSet()); - - //查询用户角色所有菜单 - List roleMenuList = roleMenuMapper.selectList(new QueryWrapper().in("role_id", roleIds)); - if (roleMenuList.size() <= 0) { - return null; - } - - Set menuIdSet = roleMenuList.stream().map(RoleMenu::getMenuId).collect(Collectors.toSet()); - //查询用户所有菜单中未禁用菜单 - QueryWrapper menuWrapper = new QueryWrapper<>(); - menuWrapper.select("perms"); - menuWrapper.eq("status", 0); - menuWrapper.in("menu_id", menuIdSet); - List menus = baseMapper.selectList(menuWrapper); - - //创建权限集合 - Set permsSet = new HashSet<>(); - for (Menu menu : menus) { - if (Validator.isNotEmpty(menu.getPerms())) { - permsSet.addAll(Arrays.asList(menu.getPerms().split(","))); - } - } - log.info("{}", permsSet); - return permsSet; - } -} - -``` - -```java -package com.mangmang.security.service.impl; - -import cn.hutool.core.lang.Validator; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.mangmang.security.entity.Role; -import com.mangmang.security.entity.UserRole; -import com.mangmang.security.mapper.RoleMapper; -import com.mangmang.security.mapper.UserRoleMapper; -import com.mangmang.security.service.RoleService; -import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; -import com.mangmang.security.service.UserRoleService; -import lombok.RequiredArgsConstructor; -import org.apache.commons.lang3.StringUtils; -import org.springframework.stereotype.Service; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -/** - * - * 角色信息表 服务实现类 - * - * - * @author 氓氓编程 - * @since 2021-08-26 - */ -@Service -@RequiredArgsConstructor -public class RoleServiceImpl extends ServiceImpl implements RoleService { - - private final UserRoleMapper userRoleMapper; - - @Override - public Set findRolePermissionByUserId(Long userId) { - //获取该用户所有角色信息 - List userRoleList = userRoleMapper.selectList(new QueryWrapper().eq("user_id", userId)); - if (userRoleList.size() <= 0) { - return null; - } - - //获取角色id - Set roleIdSet = userRoleList.stream().map(UserRole::getRoleId).collect(Collectors.toSet()); - - //查询该角色中未被禁用角色 - QueryWrapper roleWrapper = new QueryWrapper<>(); - roleWrapper.in("role_id", roleIdSet); - roleWrapper.eq("status", 0); - List perms = baseMapper.selectList(roleWrapper); - - //遍历角色 - Set permsSet = new HashSet<>(); - for (Role perm : perms) { - if (Validator.isNotNull(perm)) { - permsSet.addAll(Arrays.asList(perm.getRoleKey().trim().split(","))); - } - } - return permsSet; - } -} -``` - +--- +icon: simple-icons:spring +title: SpringSecurity +date: 2025-05-07 +category: + - JAVA +tag: + - Spring + - SpringSecurity +star: 10 +description: 若依的SpringSecurity案例 +# 此页面会在文章列表置顶 +sticky: true +--- +若依的SpringSecurity案例 + + + +# 一、准备 + +## 1. 导入依赖 + +### 1. 父依赖 + +```xml + + + 4.0.0 + + 01-druid + 02-SpringSecurity + + + + org.springframework.boot + spring-boot-starter-parent + 2.3.0.RELEASE + + + + com.mangmang + learn-test + 0.0.1-SNAPSHOT + learn-test + pom + + 依赖版本控制 + + + 1.8 + 3.4.3.1 + 3.4.1 + 2.3 + 1.2.4 + 0.9.1 + 2.0.8 + 1.21 + 1.5.1-RELEASE + + + + + + + + eu.bitwalker + UserAgentUtils + ${userAgentUtils.version} + + + + com.github.xiaoymin + knife4j-spring-boot-starter + ${knife4j.version} + + + + + io.jsonwebtoken + jjwt + ${jjwt.version} + + + + + com.github.jeffreyning + mybatisplus-plus + ${mybatisplus-plus.version} + + + + + com.baomidou + mybatis-plus-boot-starter + ${mybatis-plus.version} + + + + + com.baomidou + mybatis-plus-generator + ${mybatis-plus-generator.version} + + + + + org.apache.velocity + velocity-engine-core + ${mybatis-velocity.version} + + + + + com.alibaba + druid-spring-boot-starter + ${druid.version} + + + + + + + + com.alibaba + fastjson + 1.2.75 + + + + + + cn.hutool + hutool-all + 5.5.8 + + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + + + mysql + mysql-connector-java + runtime + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + + org.projectlombok + lombok + true + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + 2.3.0.RELEASE + + + + org.projectlombok + lombok + + + + + + + + + +``` + + + +### 2. 模块使用依赖 + +```xml + + + + learn-test + com.mangmang + 0.0.1-SNAPSHOT + + 4.0.0 + + 02-SpringSecurity + + + 8 + 8 + + + + + + com.github.jeffreyning + mybatisplus-plus + + + + + eu.bitwalker + UserAgentUtils + + + + + com.github.xiaoymin + knife4j-spring-boot-starter + + + + + io.jsonwebtoken + jjwt + + + + + org.springframework.boot + spring-boot-starter-security + + + + + com.baomidou + mybatis-plus-boot-starter + + + + + com.baomidou + mybatis-plus-generator + + + + + org.apache.velocity + velocity-engine-core + + + + + com.alibaba + druid-spring-boot-starter + + + + + + + + src/main/java + + **/*.xml + + false + + + + + +``` + +## 2. 配置YML + +### 1. application.yml + +```yml +spring: + profiles: + active: local + +server: + port: 8000 + +swagger: + enabled: true + pathMapping: + +token: + header: token + secret: asdaswqesdzxwr3123 + expire-time: 30 +``` + +### 2. application-local.yml + +```yml +spring: + datasource: + # 配置项目数据源为druid + type: com.alibaba.druid.pool.DruidDataSource + driver-class-name: com.mysql.cj.jdbc.Driver + druid: + url: jdbc:mysql://127.0.0.1:3306/book?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + username: root + password: root + initial-size: 5 + # 最小连接池数量 + min-idle: 10 + # 最大连接池数量 + max-active: 20 + # 配置获取连接等待超时的时间 + max-wait: 60000 + # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 + time-between-eviction-runs-millis: 60000 + # 配置一个连接在池中最小生存的时间,单位是毫秒 + min-evictable-idle-time-millis: 300000 + # 配置一个连接在池中最大生存的时间,单位是毫秒 + max-evictable-idle-time-millis: 900000 + # 配置检测连接是否有效 + validation-query: SELECT 1 FROM DUAL + # 官方推荐配置 + test-while-idle: true + test-on-borrow: false + test-on-return: false + + #监控配置 + web-stat-filter: + # 是否启用StatFilter默认值true + enabled: true + stat-view-servlet: + enabled: true + # 设置白名单,不填则允许所有访问 + allow: + url-pattern: /druid/* + # 控制台管理用户名和密码 + login-username: root + login-password: liujing + filter: + stat: + enabled: true + # 慢SQL记录 + log-slow-sql: true + slow-sql-millis: 1000 + merge-sql: true + wall: + config: + multi-statement-allow: true + redis: + port: 6379 + host: 127.0.0.1 + + +# MyBatis Plus配置 +mybatis-plus: + # 搜索指定包别名 + typeAliasesPackage: com.mangmang.**.domain + # 配置mapper的扫描,找到所有的mapper.xml映射文件 + mapperLocations: classpath*:mapper/**/*Mapper.xml + configuration: + # SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING + log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl + #开启二级缓存 + cache-enabled: true + #配置默认的执行器 + default-executor-type: reuse + # 开启驼峰命名 + map-underscore-to-camel-case: true + # 允许 JDBC 支持自动生成主键 + use-generated-keys: true + #关闭logo + global-config: + banner: false + db-config: + # 全局逻辑删除的实体字段名 + logic-delete-field: isDeleted + # 逻辑已删除值(默认为 1) + logic-delete-value: 1 + # 逻辑未删除值(默认为 0) + logic-not-delete-value: 0 +``` + +## 3. 配置Mybatis-Plus + +```java +/** + * @Date: 2021-08-10-17:31 + * @Author lj + */ +@Configuration +@EnableTransactionManagement +@MapperScan("com.mangmang.security.mapper") +public class MybatisPlusConfig { + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + // 分页插件 + interceptor.addInnerInterceptor(paginationInnerInterceptor()); + // 乐观锁插件 + interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor()); + // 阻断插件 + interceptor.addInnerInterceptor(blockAttackInnerInterceptor()); + return interceptor; + } + /** + * 分页插件,自动识别数据库类型 https://baomidou.com/guide/interceptor-pagination.html + */ + public PaginationInnerInterceptor paginationInnerInterceptor() { + PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(); + // 设置数据库类型为mysql + paginationInnerInterceptor.setDbType(DbType.MYSQL); + // 设置最大单页限制数量,默认 500 条,-1 不受限制 + paginationInnerInterceptor.setMaxLimit(-1L); + return paginationInnerInterceptor; + } + /** + * 乐观锁插件 https://baomidou.com/guide/interceptor-optimistic-locker.html + */ + public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() { + return new OptimisticLockerInnerInterceptor(); + } + /** + * 如果是对全表的删除或更新操作,就会终止该操作 https://baomidou.com/guide/interceptor-block-attack.html + */ + public BlockAttackInnerInterceptor blockAttackInnerInterceptor() { + return new BlockAttackInnerInterceptor(); + } + +} +``` + +## 4. 配置Knife4j + +```java +/** + * @Date: 2021-08-10-18:14 + * @Author lj + */ +@EnableSwagger2WebMvc +@Configuration +@EnableKnife4j +public class SwaggerConfig { + /** + * 是否开启swagger + */ + @Value("${swagger.enabled}") + private boolean enabled; + /** + * 设置请求的统一前缀 + */ + @Value("${swagger.pathMapping}") + private String pathMapping; + /** + * 创建API + * .pathMapping("test")设置访问路径统一前缀 + */ + @Bean + public Docket createRestApi() { + return new Docket(DocumentationType.SWAGGER_2) + // 是否启用Swagger + .enable(enabled) + // 用来创建该API的基本信息,展示在文档的页面中(自定义展示的信息) + .apiInfo(apiInfo()) + // 设置哪些接口暴露给Swagger展示 + .select() + // 扫描所有有注解的api,用这种方式更灵活 + .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) + .paths(PathSelectors.any()) + .build() + /* 设置安全模式,swagger可以设置访问token */ + .securitySchemes(securitySchemes()) + .securityContexts(securityContexts()) + .pathMapping(pathMapping); + } + + /** + * 安全模式,这里指定token通过Authorization头请求头传递 + */ + private List securitySchemes() { + List apiKeyList = new ArrayList<>(); + apiKeyList.add(new ApiKey("Authorization", "Authorization", "header")); + return apiKeyList; + } + + /** + * 安全上下文 + */ + private List securityContexts() { + List securityContexts = new ArrayList<>(); + securityContexts.add( + SecurityContext.builder() + .securityReferences(defaultAuth()) + .forPaths(PathSelectors.regex("^(?!auth).*$")) + .build()); + return securityContexts; + } + + /** + * 默认的安全上引用 + */ + private List defaultAuth() { + AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything"); + AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; + authorizationScopes[0] = authorizationScope; + List securityReferences = new ArrayList<>(); + securityReferences.add(new SecurityReference("Authorization", authorizationScopes)); + return securityReferences; + } + + /** + * 添加摘要信息 + */ + private ApiInfo apiInfo() { + // 用ApiInfoBuilder进行定制 + return new ApiInfoBuilder() + // 设置标题 + .title("标题:后端接口") + // 描述 + .description("描述:测试") + // 作者信息 + .contact(new Contact("氓氓编程", null, null)) + // 版本 + .version("版本号:" + "0.0.1") + .build(); + } +} +``` + +## 5. 配置RedisTemplate序列化规则 + +```java +/** + * @author a3621 + */ +@EnableCaching +@Configuration +public class RedisConfig extends CachingConfigurerSupport { + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory factory) { + // 创建一个模板 + RedisTemplate template = new RedisTemplate<>(); + //创建一个字符串序列化器 + RedisSerializer redisSerializer = new StringRedisSerializer(); + //创建Jackson 序列化器 + Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); + //Jackson得ObjectMapper对象 + ObjectMapper om = new ObjectMapper(); + //这将使所有成员字段无需进一步注释即可序列化,而不仅仅是公共字段(默认设置)。 + om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + //开启后序列化得类会带上全类名 + om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); + //om配置给jackson2JsonRedisSerializer + jackson2JsonRedisSerializer.setObjectMapper(om); + //RedisTemplate配置factory + template.setConnectionFactory(factory); + //key序列化方式 + template.setKeySerializer(redisSerializer); + //value序列化 + template.setValueSerializer(jackson2JsonRedisSerializer); + //value hashmap序列化 + template.setHashValueSerializer(jackson2JsonRedisSerializer); + //返回该模板 + return template; + } + + @Bean + public CacheManager cacheManager(RedisConnectionFactory factory) { + //创建一个字符串序列化器 + RedisSerializer redisSerializer = new StringRedisSerializer(); + //创建Jackson 序列化器 + Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); + //解决查询缓存转换异常的问题 + ObjectMapper om = new ObjectMapper(); + //这将使所有成员字段无需进一步注释即可序列化,而不仅仅是公共字段(默认设置)。 + om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + //开启后序列化得类会带上全类名 + om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); + //om配置给jackson2JsonRedisSerializer + jackson2JsonRedisSerializer.setObjectMapper(om); + // 配置序列化(解决乱码的问题),过期时间600秒 + RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() + .entryTtl(Duration.ofSeconds(600)) + .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)) + .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) + .disableCachingNullValues(); + return RedisCacheManager.builder(factory) + .cacheDefaults(config) + .build(); + } +} +``` + +## 6. sql(复制到文本命名为.sql,然后导入数据库) + +```sql +/* + Navicat Premium Data Transfer + + Source Server : 127.0.0.1 + Source Server Type : MySQL + Source Server Version : 50730 + Source Host : localhost:3306 + Source Schema : book + + Target Server Type : MySQL + Target Server Version : 50730 + File Encoding : 65001 + + Date: 29/08/2021 18:07:23 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for sys_menu +-- ---------------------------- +DROP TABLE IF EXISTS `sys_menu`; +CREATE TABLE `sys_menu` ( + `menu_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '菜单ID', + `menu_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '菜单名称', + `parent_id` bigint(20) NULL DEFAULT 0 COMMENT '父菜单ID', + `order_num` int(4) NULL DEFAULT 0 COMMENT '显示顺序', + `path` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '路由地址', + `component` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '组件路径', + `is_frame` int(1) NULL DEFAULT 1 COMMENT '是否为外链(0是 1否)', + `is_cache` int(1) NULL DEFAULT 0 COMMENT '是否缓存(0缓存 1不缓存)', + `menu_type` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '菜单类型(M目录 C菜单 F按钮)', + `visible` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '菜单状态(0显示 1隐藏)', + `status` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '菜单状态(0正常 1停用)', + `perms` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限标识', + `icon` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '#' COMMENT '菜单图标', + `create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '备注', + PRIMARY KEY (`menu_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1061 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '菜单权限表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_menu +-- ---------------------------- +INSERT INTO `sys_menu` VALUES (1, '系统管理', 0, 1, 'system', NULL, 1, 0, 'M', '0', '0', '', 'system', 'admin', '2021-04-30 17:27:28', '', NULL, '系统管理目录'); +INSERT INTO `sys_menu` VALUES (2, '系统监控', 0, 2, 'monitor', NULL, 1, 0, 'M', '0', '0', '', 'monitor', 'admin', '2021-04-30 17:27:28', '', NULL, '系统监控目录'); +INSERT INTO `sys_menu` VALUES (3, '系统工具', 0, 3, 'tool', NULL, 1, 0, 'M', '0', '0', '', 'tool', 'admin', '2021-04-30 17:27:28', '', NULL, '系统工具目录'); +INSERT INTO `sys_menu` VALUES (4, '若依官网', 0, 4, 'http://ruoyi.vip', NULL, 0, 0, 'M', '0', '0', '', 'guide', 'admin', '2021-04-30 17:27:28', '', NULL, '若依官网地址'); +INSERT INTO `sys_menu` VALUES (100, '用户管理', 1, 1, 'user', 'system/user/index', 1, 0, 'C', '0', '0', 'system:user:list', 'user', 'admin', '2021-04-30 17:27:28', '', NULL, '用户管理菜单'); +INSERT INTO `sys_menu` VALUES (101, '角色管理', 1, 2, 'role', 'system/role/index', 1, 0, 'C', '0', '0', 'system:role:list', 'peoples', 'admin', '2021-04-30 17:27:28', '', NULL, '角色管理菜单'); +INSERT INTO `sys_menu` VALUES (102, '菜单管理', 1, 3, 'menu', 'system/menu/index', 1, 0, 'C', '0', '0', 'system:menu:list', 'tree-table', 'admin', '2021-04-30 17:27:28', '', NULL, '菜单管理菜单'); +INSERT INTO `sys_menu` VALUES (103, '部门管理', 1, 4, 'dept', 'system/dept/index', 1, 0, 'C', '0', '0', 'system:dept:list', 'tree', 'admin', '2021-04-30 17:27:28', '', NULL, '部门管理菜单'); +INSERT INTO `sys_menu` VALUES (104, '岗位管理', 1, 5, 'post', 'system/post/index', 1, 0, 'C', '0', '0', 'system:post:list', 'post', 'admin', '2021-04-30 17:27:28', '', NULL, '岗位管理菜单'); +INSERT INTO `sys_menu` VALUES (105, '字典管理', 1, 6, 'dict', 'system/dict/index', 1, 0, 'C', '0', '0', 'system:dict:list', 'dict', 'admin', '2021-04-30 17:27:28', '', NULL, '字典管理菜单'); +INSERT INTO `sys_menu` VALUES (106, '参数设置', 1, 7, 'config', 'system/config/index', 1, 0, 'C', '0', '0', 'system:config:list', 'edit', 'admin', '2021-04-30 17:27:28', '', NULL, '参数设置菜单'); +INSERT INTO `sys_menu` VALUES (107, '通知公告', 1, 8, 'notice', 'system/notice/index', 1, 0, 'C', '0', '0', 'system:notice:list', 'message', 'admin', '2021-04-30 17:27:28', '', NULL, '通知公告菜单'); +INSERT INTO `sys_menu` VALUES (108, '日志管理', 1, 9, 'log', '', 1, 0, 'M', '0', '0', '', 'log', 'admin', '2021-04-30 17:27:28', '', NULL, '日志管理菜单'); +INSERT INTO `sys_menu` VALUES (109, '在线用户', 2, 1, 'online', 'monitor/online/index', 1, 0, 'C', '0', '0', 'monitor:online:list', 'online', 'admin', '2021-04-30 17:27:28', '', NULL, '在线用户菜单'); +INSERT INTO `sys_menu` VALUES (110, '定时任务', 2, 2, 'job', 'monitor/job/index', 1, 0, 'C', '0', '0', 'monitor:job:list', 'job', 'admin', '2021-04-30 17:27:28', '', NULL, '定时任务菜单'); +INSERT INTO `sys_menu` VALUES (111, '数据监控', 2, 3, 'druid', 'monitor/druid/index', 1, 0, 'C', '0', '0', 'monitor:druid:list', 'druid', 'admin', '2021-04-30 17:27:28', '', NULL, '数据监控菜单'); +INSERT INTO `sys_menu` VALUES (112, '服务监控', 2, 4, 'server', 'monitor/server/index', 1, 0, 'C', '0', '0', 'monitor:server:list', 'server', 'admin', '2021-04-30 17:27:28', '', NULL, '服务监控菜单'); +INSERT INTO `sys_menu` VALUES (113, '缓存监控', 2, 5, 'cache', 'monitor/cache/index', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis', 'admin', '2021-04-30 17:27:28', '', NULL, '缓存监控菜单'); +INSERT INTO `sys_menu` VALUES (114, '表单构建', 3, 1, 'build', 'tool/build/index', 1, 0, 'C', '0', '0', 'tool:build:list', 'build', 'admin', '2021-04-30 17:27:28', '', NULL, '表单构建菜单'); +INSERT INTO `sys_menu` VALUES (115, '代码生成', 3, 2, 'gen', 'tool/gen/index', 1, 0, 'C', '0', '0', 'tool:gen:list', 'code', 'admin', '2021-04-30 17:27:28', '', NULL, '代码生成菜单'); +INSERT INTO `sys_menu` VALUES (116, '系统接口', 3, 3, 'swagger', 'tool/swagger/index', 1, 0, 'C', '0', '0', 'tool:swagger:list', 'swagger', 'admin', '2021-04-30 17:27:28', '', NULL, '系统接口菜单'); +INSERT INTO `sys_menu` VALUES (500, '操作日志', 108, 1, 'operlog', 'monitor/operlog/index', 1, 0, 'C', '0', '0', 'monitor:operlog:list', 'form', 'admin', '2021-04-30 17:27:28', '', NULL, '操作日志菜单'); +INSERT INTO `sys_menu` VALUES (501, '登录日志', 108, 2, 'logininfor', 'monitor/logininfor/index', 1, 0, 'C', '0', '0', 'monitor:logininfor:list', 'logininfor', 'admin', '2021-04-30 17:27:28', '', NULL, '登录日志菜单'); +INSERT INTO `sys_menu` VALUES (1001, '用户查询', 100, 1, '', '', 1, 0, 'F', '0', '0', 'system:user:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1002, '用户新增', 100, 2, '', '', 1, 0, 'F', '0', '0', 'system:user:add', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1003, '用户修改', 100, 3, '', '', 1, 0, 'F', '0', '0', 'system:user:edit', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1004, '用户删除', 100, 4, '', '', 1, 0, 'F', '0', '0', 'system:user:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1005, '用户导出', 100, 5, '', '', 1, 0, 'F', '0', '0', 'system:user:export', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1006, '用户导入', 100, 6, '', '', 1, 0, 'F', '0', '0', 'system:user:import', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1007, '重置密码', 100, 7, '', '', 1, 0, 'F', '0', '0', 'system:user:resetPwd', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1008, '角色查询', 101, 1, '', '', 1, 0, 'F', '0', '0', 'system:role:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1009, '角色新增', 101, 2, '', '', 1, 0, 'F', '0', '0', 'system:role:add', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1010, '角色修改', 101, 3, '', '', 1, 0, 'F', '0', '0', 'system:role:edit', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1011, '角色删除', 101, 4, '', '', 1, 0, 'F', '0', '0', 'system:role:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1012, '角色导出', 101, 5, '', '', 1, 0, 'F', '0', '0', 'system:role:export', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1013, '菜单查询', 102, 1, '', '', 1, 0, 'F', '0', '0', 'system:menu:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1014, '菜单新增', 102, 2, '', '', 1, 0, 'F', '0', '0', 'system:menu:add', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1015, '菜单修改', 102, 3, '', '', 1, 0, 'F', '0', '0', 'system:menu:edit', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1016, '菜单删除', 102, 4, '', '', 1, 0, 'F', '0', '0', 'system:menu:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1017, '部门查询', 103, 1, '', '', 1, 0, 'F', '0', '0', 'system:dept:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1018, '部门新增', 103, 2, '', '', 1, 0, 'F', '0', '0', 'system:dept:add', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1019, '部门修改', 103, 3, '', '', 1, 0, 'F', '0', '0', 'system:dept:edit', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1020, '部门删除', 103, 4, '', '', 1, 0, 'F', '0', '0', 'system:dept:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1021, '岗位查询', 104, 1, '', '', 1, 0, 'F', '0', '0', 'system:post:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1022, '岗位新增', 104, 2, '', '', 1, 0, 'F', '0', '0', 'system:post:add', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1023, '岗位修改', 104, 3, '', '', 1, 0, 'F', '0', '0', 'system:post:edit', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1024, '岗位删除', 104, 4, '', '', 1, 0, 'F', '0', '0', 'system:post:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1025, '岗位导出', 104, 5, '', '', 1, 0, 'F', '0', '0', 'system:post:export', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1026, '字典查询', 105, 1, '#', '', 1, 0, 'F', '0', '0', 'system:dict:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1027, '字典新增', 105, 2, '#', '', 1, 0, 'F', '0', '0', 'system:dict:add', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1028, '字典修改', 105, 3, '#', '', 1, 0, 'F', '0', '0', 'system:dict:edit', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1029, '字典删除', 105, 4, '#', '', 1, 0, 'F', '0', '0', 'system:dict:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1030, '字典导出', 105, 5, '#', '', 1, 0, 'F', '0', '0', 'system:dict:export', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1031, '参数查询', 106, 1, '#', '', 1, 0, 'F', '0', '0', 'system:config:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1032, '参数新增', 106, 2, '#', '', 1, 0, 'F', '0', '0', 'system:config:add', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1033, '参数修改', 106, 3, '#', '', 1, 0, 'F', '0', '0', 'system:config:edit', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1034, '参数删除', 106, 4, '#', '', 1, 0, 'F', '0', '0', 'system:config:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1035, '参数导出', 106, 5, '#', '', 1, 0, 'F', '0', '0', 'system:config:export', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1036, '公告查询', 107, 1, '#', '', 1, 0, 'F', '0', '0', 'system:notice:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1037, '公告新增', 107, 2, '#', '', 1, 0, 'F', '0', '0', 'system:notice:add', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1038, '公告修改', 107, 3, '#', '', 1, 0, 'F', '0', '0', 'system:notice:edit', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1039, '公告删除', 107, 4, '#', '', 1, 0, 'F', '0', '0', 'system:notice:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1040, '操作查询', 500, 1, '#', '', 1, 0, 'F', '0', '0', 'monitor:operlog:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1041, '操作删除', 500, 2, '#', '', 1, 0, 'F', '0', '0', 'monitor:operlog:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1042, '日志导出', 500, 4, '#', '', 1, 0, 'F', '0', '0', 'monitor:operlog:export', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1043, '登录查询', 501, 1, '#', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1044, '登录删除', 501, 2, '#', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1045, '日志导出', 501, 3, '#', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:export', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1046, '在线查询', 109, 1, '#', '', 1, 0, 'F', '0', '0', 'monitor:online:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1047, '批量强退', 109, 2, '#', '', 1, 0, 'F', '0', '0', 'monitor:online:batchLogout', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1048, '单条强退', 109, 3, '#', '', 1, 0, 'F', '0', '0', 'monitor:online:forceLogout', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1049, '任务查询', 110, 1, '#', '', 1, 0, 'F', '0', '0', 'monitor:job:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1050, '任务新增', 110, 2, '#', '', 1, 0, 'F', '0', '0', 'monitor:job:add', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1051, '任务修改', 110, 3, '#', '', 1, 0, 'F', '0', '0', 'monitor:job:edit', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1052, '任务删除', 110, 4, '#', '', 1, 0, 'F', '0', '0', 'monitor:job:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1053, '状态修改', 110, 5, '#', '', 1, 0, 'F', '0', '0', 'monitor:job:changeStatus', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1054, '任务导出', 110, 7, '#', '', 1, 0, 'F', '0', '0', 'monitor:job:export', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1055, '生成查询', 115, 1, '#', '', 1, 0, 'F', '0', '0', 'tool:gen:query', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1056, '生成修改', 115, 2, '#', '', 1, 0, 'F', '0', '0', 'tool:gen:edit', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1057, '生成删除', 115, 3, '#', '', 1, 0, 'F', '0', '0', 'tool:gen:remove', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1058, '导入代码', 115, 2, '#', '', 1, 0, 'F', '0', '0', 'tool:gen:import', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1059, '预览代码', 115, 4, '#', '', 1, 0, 'F', '0', '0', 'tool:gen:preview', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1060, '生成代码', 115, 5, '#', '', 1, 0, 'F', '0', '0', 'tool:gen:code', '#', 'admin', '2021-04-30 17:27:28', '', NULL, ''); + +-- ---------------------------- +-- Table structure for sys_role +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role`; +CREATE TABLE `sys_role` ( + `role_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色ID', + `role_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色名称', + `role_key` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色权限字符串', + `role_sort` int(4) NOT NULL COMMENT '显示顺序', + `data_scope` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '1' COMMENT '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)', + `menu_check_strictly` tinyint(1) NULL DEFAULT 1 COMMENT '菜单树选择项是否关联显示', + `dept_check_strictly` tinyint(1) NULL DEFAULT 1 COMMENT '部门树选择项是否关联显示', + `status` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色状态(0正常 1停用)', + `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`role_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_role +-- ---------------------------- +INSERT INTO `sys_role` VALUES (1, '超级管理员', 'admin', 1, '1', 1, 1, '0', '0', 'admin', '2021-04-30 17:27:28', '', NULL, '超级管理员'); +INSERT INTO `sys_role` VALUES (2, '普通角色', 'common', 2, '2', 1, 1, '0', '0', 'admin', '2021-04-30 17:27:28', '', NULL, '普通角色'); + +-- ---------------------------- +-- Table structure for sys_role_menu +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role_menu`; +CREATE TABLE `sys_role_menu` ( + `role_id` bigint(20) NOT NULL COMMENT '角色ID', + `menu_id` bigint(20) NOT NULL COMMENT '菜单ID', + PRIMARY KEY (`role_id`, `menu_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色和菜单关联表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_role_menu +-- ---------------------------- +INSERT INTO `sys_role_menu` VALUES (2, 1); +INSERT INTO `sys_role_menu` VALUES (2, 2); +INSERT INTO `sys_role_menu` VALUES (2, 3); +INSERT INTO `sys_role_menu` VALUES (2, 4); +INSERT INTO `sys_role_menu` VALUES (2, 100); +INSERT INTO `sys_role_menu` VALUES (2, 101); +INSERT INTO `sys_role_menu` VALUES (2, 102); +INSERT INTO `sys_role_menu` VALUES (2, 103); +INSERT INTO `sys_role_menu` VALUES (2, 104); +INSERT INTO `sys_role_menu` VALUES (2, 105); +INSERT INTO `sys_role_menu` VALUES (2, 106); +INSERT INTO `sys_role_menu` VALUES (2, 107); +INSERT INTO `sys_role_menu` VALUES (2, 108); +INSERT INTO `sys_role_menu` VALUES (2, 109); +INSERT INTO `sys_role_menu` VALUES (2, 110); +INSERT INTO `sys_role_menu` VALUES (2, 111); +INSERT INTO `sys_role_menu` VALUES (2, 112); +INSERT INTO `sys_role_menu` VALUES (2, 113); +INSERT INTO `sys_role_menu` VALUES (2, 114); +INSERT INTO `sys_role_menu` VALUES (2, 115); +INSERT INTO `sys_role_menu` VALUES (2, 116); +INSERT INTO `sys_role_menu` VALUES (2, 500); +INSERT INTO `sys_role_menu` VALUES (2, 501); +INSERT INTO `sys_role_menu` VALUES (2, 1000); +INSERT INTO `sys_role_menu` VALUES (2, 1001); +INSERT INTO `sys_role_menu` VALUES (2, 1002); +INSERT INTO `sys_role_menu` VALUES (2, 1003); +INSERT INTO `sys_role_menu` VALUES (2, 1004); +INSERT INTO `sys_role_menu` VALUES (2, 1005); +INSERT INTO `sys_role_menu` VALUES (2, 1006); +INSERT INTO `sys_role_menu` VALUES (2, 1007); +INSERT INTO `sys_role_menu` VALUES (2, 1008); +INSERT INTO `sys_role_menu` VALUES (2, 1009); +INSERT INTO `sys_role_menu` VALUES (2, 1010); +INSERT INTO `sys_role_menu` VALUES (2, 1011); +INSERT INTO `sys_role_menu` VALUES (2, 1012); +INSERT INTO `sys_role_menu` VALUES (2, 1013); +INSERT INTO `sys_role_menu` VALUES (2, 1014); +INSERT INTO `sys_role_menu` VALUES (2, 1015); +INSERT INTO `sys_role_menu` VALUES (2, 1016); +INSERT INTO `sys_role_menu` VALUES (2, 1017); +INSERT INTO `sys_role_menu` VALUES (2, 1018); +INSERT INTO `sys_role_menu` VALUES (2, 1019); +INSERT INTO `sys_role_menu` VALUES (2, 1020); +INSERT INTO `sys_role_menu` VALUES (2, 1021); +INSERT INTO `sys_role_menu` VALUES (2, 1022); +INSERT INTO `sys_role_menu` VALUES (2, 1023); +INSERT INTO `sys_role_menu` VALUES (2, 1024); +INSERT INTO `sys_role_menu` VALUES (2, 1025); +INSERT INTO `sys_role_menu` VALUES (2, 1026); +INSERT INTO `sys_role_menu` VALUES (2, 1027); +INSERT INTO `sys_role_menu` VALUES (2, 1028); +INSERT INTO `sys_role_menu` VALUES (2, 1029); +INSERT INTO `sys_role_menu` VALUES (2, 1030); +INSERT INTO `sys_role_menu` VALUES (2, 1031); +INSERT INTO `sys_role_menu` VALUES (2, 1032); +INSERT INTO `sys_role_menu` VALUES (2, 1033); +INSERT INTO `sys_role_menu` VALUES (2, 1034); +INSERT INTO `sys_role_menu` VALUES (2, 1035); +INSERT INTO `sys_role_menu` VALUES (2, 1036); +INSERT INTO `sys_role_menu` VALUES (2, 1037); +INSERT INTO `sys_role_menu` VALUES (2, 1038); +INSERT INTO `sys_role_menu` VALUES (2, 1039); +INSERT INTO `sys_role_menu` VALUES (2, 1040); +INSERT INTO `sys_role_menu` VALUES (2, 1041); +INSERT INTO `sys_role_menu` VALUES (2, 1042); +INSERT INTO `sys_role_menu` VALUES (2, 1043); +INSERT INTO `sys_role_menu` VALUES (2, 1044); +INSERT INTO `sys_role_menu` VALUES (2, 1045); +INSERT INTO `sys_role_menu` VALUES (2, 1046); +INSERT INTO `sys_role_menu` VALUES (2, 1047); +INSERT INTO `sys_role_menu` VALUES (2, 1048); +INSERT INTO `sys_role_menu` VALUES (2, 1049); +INSERT INTO `sys_role_menu` VALUES (2, 1050); +INSERT INTO `sys_role_menu` VALUES (2, 1051); +INSERT INTO `sys_role_menu` VALUES (2, 1052); +INSERT INTO `sys_role_menu` VALUES (2, 1053); +INSERT INTO `sys_role_menu` VALUES (2, 1054); +INSERT INTO `sys_role_menu` VALUES (2, 1055); +INSERT INTO `sys_role_menu` VALUES (2, 1056); +INSERT INTO `sys_role_menu` VALUES (2, 1057); +INSERT INTO `sys_role_menu` VALUES (2, 1058); +INSERT INTO `sys_role_menu` VALUES (2, 1059); +INSERT INTO `sys_role_menu` VALUES (2, 1060); + +-- ---------------------------- +-- Table structure for sys_user +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user`; +CREATE TABLE `sys_user` ( + `user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID', + `dept_id` bigint(20) NULL DEFAULT NULL COMMENT '部门ID', + `user_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户账号', + `nick_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户昵称', + `user_type` varchar(2) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '00' COMMENT '用户类型(00系统用户)', + `email` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '用户邮箱', + `phonenumber` varchar(11) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '手机号码', + `sex` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)', + `avatar` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '头像地址', + `password` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '密码', + `status` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '帐号状态(0正常 1停用)', + `del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `login_ip` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '最后登录IP', + `login_date` datetime NULL DEFAULT NULL COMMENT '最后登录时间', + `create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`user_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_user +-- ---------------------------- +INSERT INTO `sys_user` VALUES (1, 103, 'admin', '若依', '00', 'ry@163.com', '15888888888', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', '2021-04-30 17:27:28', 'admin', '2021-04-30 17:27:28', '', NULL, '管理员'); +INSERT INTO `sys_user` VALUES (2, 105, 'ry', '若依', '00', 'ry@qq.com', '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', '2021-04-30 17:27:28', 'admin', '2021-04-30 17:27:28', '', NULL, '测试员'); + +-- ---------------------------- +-- Table structure for sys_user_role +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user_role`; +CREATE TABLE `sys_user_role` ( + `user_id` bigint(20) NOT NULL COMMENT '用户ID', + `role_id` bigint(20) NOT NULL COMMENT '角色ID', + PRIMARY KEY (`user_id`, `role_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户和角色关联表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_user_role +-- ---------------------------- +INSERT INTO `sys_user_role` VALUES (1, 1); +INSERT INTO `sys_user_role` VALUES (2, 2); + +-- ---------------------------- +-- Table structure for t_user +-- ---------------------------- +DROP TABLE IF EXISTS `t_user`; +CREATE TABLE `t_user` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `username` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `password` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `email` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `username`(`username`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of t_user +-- ---------------------------- +INSERT INTO `t_user` VALUES (1, 'admin', 'admin', 'admin@atguigu.com'); +INSERT INTO `t_user` VALUES (2, 'test', 'test', 'test@test.com'); +INSERT INTO `t_user` VALUES (3, 'wzg168', '123456', 'wzg168@qq.com'); +INSERT INTO `t_user` VALUES (4, 'bbj168', '666666', 'bbj168@qq.com'); +INSERT INTO `t_user` VALUES (5, 'abc168', '666666', 'abc168@qq.com'); +INSERT INTO `t_user` VALUES (6, 'admin250', '123456', '362165265@qq.com'); +INSERT INTO `t_user` VALUES (7, 'admin235', '123456', '362165265@qq.com'); +INSERT INTO `t_user` VALUES (8, 'aaaaaaaaa', 'aaaaaa', '362165265@qq.com'); +INSERT INTO `t_user` VALUES (9, 'aaaaa', 'aaaaa', '362165265@qq.com'); + +-- ---------------------------- +-- Table structure for test +-- ---------------------------- +DROP TABLE IF EXISTS `test`; +CREATE TABLE `test` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `seqno` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL, + `some` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of test +-- ---------------------------- +INSERT INTO `test` VALUES (1, NULL, '2021-08-27 15:12:29', '2021-08-27 15:12:29', 'some'); + +-- ---------------------------- +-- Table structure for test07 +-- ---------------------------- +DROP TABLE IF EXISTS `test07`; +CREATE TABLE `test07` ( + `k1` int(11) NOT NULL, + `k2` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `col1` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `col2` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`k1`, `k2`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of test07 +-- ---------------------------- +INSERT INTO `test07` VALUES (1, '111', 'xxxx', NULL); + +-- ---------------------------- +-- Table structure for test2 +-- ---------------------------- +DROP TABLE IF EXISTS `test2`; +CREATE TABLE `test2` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `refid` int(11) NOT NULL, + `some2` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of test2 +-- ---------------------------- + +SET FOREIGN_KEY_CHECKS = 1; +``` + +## 7. mybatis-plus 自动生成代码 + +# 二、SpringSecurity认证 + +## 1. SpringSecurity的工作流程 + +>**一个web请求会经过一条过滤器链,在经过过滤器链的过程中会完成认证与授权,如果中间发现这条请求未认证或者未授权,会根据被保护API的权限去抛出异常,然后由异常处理器去处理这些异常。** + +图片显示流程 + +[//]: # () + +[//]: # () +[//]: # () + +[//]: # () +[//]: # () + +```java +/** +如上图: + 1. 一个请求想要访问到API就会以从左到右的形式经过蓝线框框里面的过滤器 + - 其中绿色部分是我们本篇主要讲的负责认证的过滤器 + - 蓝色部分负责异常处理 + - 橙色部分则是负责授权 + 2. 图中的这两个绿色过滤器是Spring Security对form表单认证和Basic认证内置的两个Filter,JWT认证方式用不上。 + 3. Spring Security配置中有两个叫formLogin和httpBasic的配置项,在配置中打开了它俩就对应着打开了上面的过滤器。 + 4. formLogin对应着你form表单认证方式,即UsernamePasswordAuthenticationFilter。 + 5. httpBasic对应着Basic认证方式,即BasicAuthenticationFilter。 + 6. 换言之,你配置了这两种认证方式,过滤器链中才会加入它们,否则它们是不会被加到过滤器链中去的。 + 7. 因为Spring Security自带的过滤器中是没有针对JWT这种认证方式的,所以我们会写一个JWT的认证过滤器,然后放在绿色的位置进行认证工作。 +``` + +## 2. SpringSecurity的重要概念 + +```apl +1. SecurityContext:上下文对象,Authentication对象会放在里面。 +2. SecurityContextHolder:用于拿到上下文对象的静态工具类。 +3. Authentication:认证接口,定义了认证对象的数据形式。 +4. AuthenticationManager:用于校验Authentication,返回一个认证完成后• Authentication对象。 +``` + +### 1. SecurityContext + +> 上下文对象,认证后的数据就放在这里面,接口定义如下: + +```java +public interface SecurityContext extends Serializable { + // 获取Authentication对象 + Authentication getAuthentication(); + // 放入Authentication对象 + void setAuthentication(Authentication authentication); +} +``` + +### 2. SecurityContextHolder + +> 可以说是SecurityContext的工具类,用于get 或 set 或者 clear SecurityContext,**默认会把数据都存储到当前线程中**。 + +```java +public class SecurityContextHolder { + public static void clearContext() { + strategy.clearContext(); + } + public static SecurityContext getContext() { + return strategy.getContext(); + } + public static void setContext(SecurityContext context) { + strategy.setContext(context); + } +} +``` + +### 3. Authentication + +> Authentication只是定义了一种在SpringSecurity进行认证过的数据的数据形式应该是怎么样的,要有权限,要有密码,要有身份信息,要有额外信息。 + +```java +public interface Authentication extends Principal, Serializable { + Collection extends GrantedAuthority> getAuthorities(); + Object getCredentials(); + Object getDetails(); + Object getPrincipal(); + boolean isAuthenticated(); + void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException; +} +``` + +```java +/** +1. getAuthorities: 获取用户权限,一般情况下获取到的是用户的角色信息。 +2. getCredentials: 获取证明用户认证的信息,通常情况下获取到的是密码等信息。 +3. getDetails: 获取用户的额外信息,(这部分信息可以是我们的用户表中的信息)。 +4. getPrincipal: 获取用户身份信息,在未认证的情况下获取到的是用户名,在已认证的情况下获取到的是 UserDetails。 +6. isAuthenticated: 获取当前 Authentication 是否已认证。 +7. setAuthenticated: 设置当前 Authentication 是否已认证(true or false)。 +``` + +### 4. AuthenticationManager + +> AuthenticationManager定义了一个认证方法,它将一个未认证的Authentication传入,返回一个已认证的Authentication,默认使用的实现类为:ProviderManager。 + +```java +public interface AuthenticationManager { + // 认证方法 + Authentication authenticate(Authentication authentication)throws AuthenticationException; +} +``` + +### 5. Spring Security进行认证的流程: + +```java +/** +1. 先是一个请求带着身份信息进来 +2. 经过AuthenticationManager的认证, +3. 再通过SecurityContextHolder获取SecurityContext, +4. 最后将认证后的信息放入到SecurityContext。 +``` + +## 3. 必要组件 + +### 1. 定义加密器Bean + +> 这个Bean是不必可少的,Spring Security在认证操作时会使用我们定义的这个加密器,如果没有则会出现异常。 + +```java + /** + * spring容器中注入加密算法 + * + * @return 强散列哈希加密实现 + */ + @Bean + public BCryptPasswordEncoder bCryptPasswordEncoder() { + return new BCryptPasswordEncoder(); + } +``` + +### 2.定义AuthenticationManager + +>这里将Spring Security自带的authenticationManager声明成Bean,声明它的作用是用它帮我们进行认证操作,调用这个Bean的authenticate方法会由Spring Security自动帮我们做认证。 + +```java + /** + * 解决 无法直接注入 AuthenticationManager + * + * @return return + * @throws Exception e + */ + @Bean + @Override + protected AuthenticationManager authenticationManager() throws Exception { + return super.authenticationManager(); + } +``` + + + +### 3. 实现UserDetailsService + +>实现UserDetailsService的抽象方法并返回一个UserDetails对象,认证过程中SpringSecurity会调用这个方法访问数据库进行对用户的搜索,逻辑什么都可以自定义,无论是从数据库中还是从缓存中,但是我们需要将我们查询出来的用户信息和权限信息组装成一个UserDetails返回。 +> +>UserDetails也是一个定义了数据形式的接口,用于保存我们从数据库中查出来的数据,其功能主要是验证账号状态和获取权限 + +```java +/** + * 自定义用户认证逻辑并交给spring管理 + * + * @Date: 2021-08-27-13:50 + * @Author lj + */ +@Slf4j +@Service("userDetailsService") +@RequiredArgsConstructor +public class UserDetailsServiceImpl implements UserDetailsService { + + private final UserService userService; + + private final PermissionService permissionService; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + log.info("②-用户信息认证"); + //认证 判断用户是否存在于数据库内 + User user = userService.getOne(new QueryWrapper().eq("user_name", username)); + if (Validator.isNull(user)) { + log.info("登录用户:{} 不存在.", username); + throw new RuntimeException("登录用户:" + username + "不存在"); + } else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) { + log.info("登录用户:{} 已被删除.", username); + throw new RuntimeException("登录用户:" + username + "已被删除"); + } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) { + log.info("登录用户:{} 已被停用.", username); + throw new RuntimeException("登录用户:" + username + "已被停用"); + } + //满足以上信息认证通过 + //设置用户权限和当前用户基本信息 + Set permissions = permissionService.getMenuPermission(user); + //角色权限信息 + Set rolePermission = permissionService.getRolePermission(user); + user.setRoles(rolePermission); + //返回安全框架需要得用户信息和自定义得一些信息 + return new SecurityUser(permissions, user); + } +} +``` + +### 4. TokenUtil + +> 由于我们是JWT的认证模式,所以我们也需要一个帮我们操作Token的工具类,一般来说它具有以下三个方法就够了 +> +> - 创建token +> - 验证token +> - 反解析token中的信息 + +```java +package com.mangmang.security.securities.jwt; + +import cn.hutool.core.lang.Validator; +import com.mangmang.security.securities.domain.SecurityUser; +import com.mangmang.security.utils.AddressUtils; +import com.mangmang.security.utils.Constants; +import com.mangmang.security.utils.IpUtils; +import eu.bitwalker.useragentutils.UserAgent; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +/** + * @Date: 2021-08-26-15:00 + * @Author lj + */ +@Data +@Component +@ConfigurationProperties("token") +@RequiredArgsConstructor +public class TokenService { + + private final RedisTemplate redisTemplate; + + /** + * 令牌自定义标识 + */ + private String header; + + /** + * 令牌密钥 + */ + private String secret; + + /** + * 令牌有效期(默认30分钟) + */ + private int expireTime; + + + /** + * 根据token获取用户信息 + * + * @param request 请求 + * @return 用户信息 + */ + public SecurityUser getSecurityUser(HttpServletRequest request) { + //获取token + String token = request.getHeader(this.header); + if (Validator.isNotEmpty(token)) { + //获取用户唯一标识 + String uniquelyIdentifies = getUniquelyIdentifiesByToken(token); + //返回该用户信息 + return (SecurityUser) redisTemplate.opsForValue().get(Constants.LOGIN_TOKEN_KEY + uniquelyIdentifies); + } + return null; + } + + /** + * 生成一个token + * + * @param securityUser 安全框架需要得用户信息 + * @param request 请求 + * @return token + */ + public String createToken(SecurityUser securityUser, HttpServletRequest request) { + //设置用户唯一标识 + securityUser.setUniquelyIdentifies(UUID.randomUUID().toString()); + //获取用户ip地址 + String ipAddr = IpUtils.getIpAddr(request); + securityUser.setIpaddr(ipAddr); + //获取客户端信息 + UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent")); + //根据ip获取用户真实地址 + securityUser.setLoginLocation(AddressUtils.getRealAddressByIp(ipAddr)); + //设置登录浏览器信息 + securityUser.setBrowser(userAgent.getBrowser().getName()); + //设置操作系统信息 + securityUser.setOs(userAgent.getOperatingSystem().getName()); + //用户信息存入redis + refreshRedisTokenTime(securityUser); + //token得主体信息; + HashMap tokenInfo = new HashMap<>(64); + tokenInfo.put(Constants.LOGIN_USER_KEY, securityUser.getUniquelyIdentifies()); + //生成token + return Jwts.builder() + //jwt头使用默认 + //有效载荷 + .setClaims(tokenInfo) + //签名哈希 + //让上面两部分数据签名通过HS256算法生成哈希,确保数据不会被篡改 + .signWith(SignatureAlgorithm.HS256, secret) + .compact(); + } + + + /** + * 刷新存放在redis中用户登录相关信息时间 + */ + public void refreshRedisTokenTime(SecurityUser securityUser) { + //设置当前时间为登录时间 + securityUser.setLoginTime(System.currentTimeMillis()); + //登录时间+30分钟为过期时间 + securityUser.setExpireTime(securityUser.getLoginTime() + expireTime * Constants.MILLIS_MINUTE); + //把用户登录信息存入缓存expireTime分钟后过期 + redisTemplate + .opsForValue() + .set(Constants.LOGIN_TOKEN_KEY + securityUser.getUniquelyIdentifies(), securityUser, expireTime, TimeUnit.MINUTES); + } + + /** + * 小于10分钟更新存放在redis中token过期时间 + * 判断redis中token过期时间是否小于10分钟 + * + * @param securityUser 安全用户信息 + */ + public void verifyToken(SecurityUser securityUser) { + if (securityUser.getExpireTime() - securityUser.getLoginTime() <= Constants.MILLIS_MINUTE_TEN) { + refreshRedisTokenTime(securityUser); + } + } + + /** + * 通过token获取用户唯一标识 + * + * @param token token + * @return 用户唯一标识 + */ + public String getUniquelyIdentifiesByToken(String token) { + return (String) Jwts.parser() + .setSigningKey(secret) + .parseClaimsJws(token) + .getBody() + .get(Constants.LOGIN_USER_KEY); + } + + /** + * 根据用户唯一标识删除用户信息 + * + * @param uniquelyIdentifies 用户唯一标识; + */ + public void deleteSecurity(String uniquelyIdentifies) { + if (Validator.isNotEmpty(uniquelyIdentifies)) { + //删除redis中数据 + redisTemplate.delete(Constants.LOGIN_TOKEN_KEY + uniquelyIdentifies); + } + } +} +``` + +### 5. 自定义UserDetails + +```java +package com.mangmang.security.securities.domain; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.mangmang.security.entity.User; +import lombok.*; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Set; + +/** + * security框架得用户信息 + * + * @Date: 2021-08-26-16:18 + * @Author lj + */ +@NoArgsConstructor +@Data +@ToString +public class SecurityUser implements UserDetails, Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 用户唯一标识 + */ + private String uniquelyIdentifies; + + /** + * 登录时间 + */ + private Long loginTime; + + /** + * 过期时间 + */ + private Long expireTime; + + /** + * 登录IP地址 + */ + private String ipaddr; + + /** + * 登录地点 + */ + private String loginLocation; + + /** + * 浏览器类型 + */ + private String browser; + + /** + * 操作系统 + */ + private String os; + + /** + * 权限列表 + */ + private Set permissions; + + /** + * 用户信息 + */ + private User user; + + @Getter(AccessLevel.NONE) + private String username; + + /** + * 用户信息和权限信息 + * + * @param permissions 权限信息 + * @param user 用户信息 + */ + public SecurityUser(Set permissions, User user) { + this.permissions = permissions; + this.user = user; + } + + @Override + public Collection extends GrantedAuthority> getAuthorities() { + return null; + } + + @JsonIgnore + @Override + public String getPassword() { + return user.getPassword(); + } + + + @Override + public String getUsername() { + return user.getUserName(); + } + + + /** + * 账户是否未过期,过期无法验证 + * + * @return 未过期 + */ + @JsonIgnore + @Override + public boolean isAccountNonExpired() { + return true; + } + + /** + * 指定用户是否解锁,锁定的用户无法进行身份验证 + * + * @return 已解锁 + */ + @JsonIgnore + @Override + public boolean isAccountNonLocked() { + return true; + } + + /** + * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证 + * + * @return 未过期 + */ + @JsonIgnore + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + /** + * 是否可用 ,禁用的用户不能身份验证 + * + * @return 可用 + */ + @JsonIgnore + @Override + public boolean isEnabled() { + return true; + } +} + +``` + + + +## 4. 具体实现 + +### 1. 认证方法和认证源码分析 + +> 访问一个系统,一般最先访问的是认证方法,这里我写了最简略的认证需要的几个步骤,因为实际系统中我们还要写登录记录啊,前台密码解密啊这些操作。 + +```java +/** + * security登录接口 + * + * @Date: 2021-08-29-17:05 + * @Author lj + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class LoginService { + + private final AuthenticationManager authenticationManager; + + private final TokenService tokenService; + + private RedisTemplate redisTemplate; + + public String login(String username, String password, HttpServletRequest request) { + Authentication authentication = null; + try { + //1. 开始认证 + authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password)); + } catch (Exception e) { + if (e instanceof BadCredentialsException) { + log.error("用户密码不匹配"); + throw new RuntimeException("用户密码不匹配"); + } else { + e.printStackTrace(); + } + } + assert authentication != null; + //2. 获取认证成功后得用户信息 + SecurityUser securityUser = (SecurityUser) authentication.getPrincipal(); + System.out.println(securityUser); + //3. 缓存用户信息并生成token + return tokenService.createToken(securityUser, request); + } +} +``` + +```java +/** +1. 传入用户名和密码创建了一个UsernamePasswordAuthenticationToken对象,这是我们前面说过的Authentication的实现类,传入用户名和密码做构造参数,这个对象就是我们创建出来的未认证的Authentication对象。使用我们先前已经声明过的Bean-authenticationManager调用它的authenticate方法进行认证,返回一个认证完成的Authentication对象。 +2. 从Authentication对象中拿到我们的UserDetails对象,之前我们说过,认证后的Authentication对象调用它的getPrincipal()方法就可以拿到我们先前数据库查询后组装出来的UserDetails对象, +3. 然后创建token。把UserDetails对象放入缓存中,方便后面过滤器使用。 + +``` + +```java + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> { + return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported"); + }); + //还未认证成功过去用户名 + String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName(); + boolean cacheWasUsed = true; + //从缓存中去查用户名为XXX的对象 + UserDetails user = this.userCache.getUserFromCache(username); + //为空 + if (user == null) { + cacheWasUsed = false; + try { + //调用我们重写UserDetailsService的loadUserByUsername方法 + // 拿到我们自己组装好的UserDetails对象 + //AbstractUserDetailsAuthenticationProvider得实现类为DaoAuthenticationProvider由于retrieveUser为抽象方法所以调用子类方法 + user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication); + } catch (UsernameNotFoundException var6) { + this.logger.debug("User '" + username + "' not found"); + if (this.hideUserNotFoundExceptions) { + throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); + } + throw var6; + } + Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract"); + } + try { + // 校验账号是否禁用 + this.preAuthenticationChecks.check(user); + // 校验数据库查出来的密码,和我们传入的密码是否一致 + this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication); + } catch (AuthenticationException var7) { + if (!cacheWasUsed) { + throw var7; + } + + cacheWasUsed = false; + user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication); + this.preAuthenticationChecks.check(user); + this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication); + } + this.postAuthenticationChecks.check(user); + if (!cacheWasUsed) { + this.userCache.putUserInCache(user); + } + Object principalToReturn = user; + if (this.forcePrincipalAsString) { + principalToReturn = user.getUsername(); + } + return this.createSuccessAuthentication(principalToReturn, authentication, user); + } + +``` + + + +### 2. jwt认证过滤器 + +>有了token之后,我们要把过滤器放在过滤器链中,用于解析token,因为我们没有session,所以我们每次去辨别这是哪个用户的请求的时候,都是根据请求中的token来解析出来当前是哪个用户。 +> +>addFilterBefore的语义是添加一个Filter到XXXFilter之前,放在这里就是把`JwtAuthenticationTokenFilter`放在UsernamePasswordAuthenticationFilter之前,因为filter的执行也是有顺序的,我们必须要把我们的filter放在过滤器链中绿色的部分才会起到自动认证的效果。 +> +>具体实现如下: + +```java +/** + * token过滤器 验证token有效性 + * + * @Date: 2021-08-29-14:08 + * @Author lj + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { + + private final TokenService tokenService; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + log.info("①=========jwt认证"); + //1.之前登录过 所以这里会获取到redis中完整得用户信息 + //1. 根据token获取用户唯一标识查询用户信息 + SecurityUser securityUser = tokenService.getSecurityUser(request); + //2. 获取上下文中是否有 authentication认证接口,定义了认证对象的数据形式。(本次请求得认证接口,包含当前访问接口的用户信息) + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (Validator.isNotNull(securityUser) && Validator.isNull(authentication)) { + //3 如果redis中数据小于10分钟刷新 + tokenService.verifyToken(securityUser); + //4. 组装一个authentication对象,把它放在上下文对象中,这样后面的过滤器看到我们上下文对象中有authentication对象,就相当于我们已经认证过了 + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(securityUser, securityUser.getPassword(), securityUser.getAuthorities()); + //5. 设置网页详情,ip地址,sessionId等信息 + authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + //6. 将authentication信息放入到上下文对象中 + SecurityContextHolder.getContext().setAuthentication(authenticationToken); + } + //放行 + filterChain.doFilter(request, response); + } +} + +``` + +> 这样的话,每一个带有正确token的请求进来之后,都会找到它的账号信息,并放在上下文对象中,我们可以使用`SecurityContextHolder`很方便的拿到上下文对象中的`Authentication`对象。完成之后,启动我们的demo,可以看到过滤器链中有以下过滤器,其中我们自定义的是第5个 +> +> 登录👉拿到token👉请求带上token👉JWT过滤器拦截👉校验token👉将从缓存中查出来的对象放到上下文中 + +## 5. 代码优化 + +### 1.认证失败处理器 + +> 当用户未登录或者token解析失败时会触发这个处理器,返回一个非法访问的结果。 + +```java +/** + * 认证失败处理类 返回未授权 + * + * @Date: 2021-08-29-13:10 + * @Author lj + */ +@Component +public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable { + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) { + String msg = String.format("请求访问:%s,认证失败,无法访问系统资源", request.getRequestURI()); + try { + // 设置返回的数据内容的数据类型和编码 + response.setContentType("text/html; charset=utf-8"); + response.getWriter().write(msg); + } catch (IOException ioException) { + ioException.printStackTrace(); + } + } +} + +``` + +### 2. 自定义退出处理 + +```java +/** + * 自定义退出处理类 返回成功 + * + * @Date: 2021-08-29-13:51 + * @Author lj + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler { + + private final TokenService tokenService; + + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth) throws IOException { + //获取认证得用户 + SecurityUser securityUser = tokenService.getSecurityUser(request); + //不为空 + if (Validator.isNotNull(securityUser)) { + //获取用户姓名 + String userName = securityUser.getUser().getUserName(); + //删除redis缓存数据 + tokenService.deleteSecurity(securityUser.getUniquelyIdentifies()); + //删除上下文中的用户信息,只删除当前线程用户数据 + SecurityContextHolder.clearContext(); + //记录退出日志 + log.info("用户 {} 退出了系统", userName); + } + // 设置返回的数据内容的数据类型和编码 + response.setContentType("text/html; charset=utf-8"); + response.getWriter().write("用户为空"); + } +} + +``` + +### 3. 权限不足处理器 + +```java +/** + * 权限不足处理器 + * + * @Date: 2021-08-31-13:50 + * @Author lj + */ +@Slf4j +@Component +public class InsufficientPermissionsHandler implements AccessDeniedHandler { + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) { + String msg = String.format("请求访问:%s,失败,无权限,请联系管理员", request.getRequestURI()); + response.setHeader("Cache-Control", "no-cache"); + response.setCharacterEncoding("UTF-8"); + response.setContentType("application/json"); + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + try { + response.getWriter().println(msg); + response.getWriter().flush(); + } catch (IOException ioException) { + ioException.printStackTrace(); + } + } +} +``` + + + +## 6. security完整配置 + +```java +/** + * 自动配置时会判断是否存在WebSecurityConfigurerAdapter类, + * + * @Date: 2021-08-27-13:48 + * @Author lj + */ +@RequiredArgsConstructor +@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + /** + * 用户认证逻辑spring自动注入加入容器实现该接口得方法 + */ + private final UserDetailsService userDetailsService; + + /** + * 认证失败处理类 + */ + private final AuthenticationEntryPointImpl authenticationEntryPoint; + + + /** + * 无权限处理器 + */ + private final InsufficientPermissionsHandler insufficientPermissionsHandler; + + /** + * 退出成功处理 + */ + private final LogoutSuccessHandlerImpl logoutSuccessHandler; + + /** + * jwt认证过滤器 + */ + private final JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; + + + /** + * 配置security密码加密方式和自定义认证流程 + * + * @param auth 认证 + * @throws Exception 异常 + */ + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder()); + } + + + /** + * spring容器中注入加密算法 + * + * @return 强散列哈希加密实现 + */ + @Bean + public BCryptPasswordEncoder bCryptPasswordEncoder() { + return new BCryptPasswordEncoder(); + } + + /** + * 解决 无法直接注入 AuthenticationManager + * + * @return return + * @throws Exception e + */ + @Bean + @Override + protected AuthenticationManager authenticationManager() throws Exception { + return super.authenticationManager(); + } + + + /** + * 跨域配置 + */ + @Bean + public CorsFilter corsFilter() { + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + // 设置访问源地址 + config.addAllowedOrigin("*"); + // 设置访问源请求头 + config.addAllowedHeader("*"); + // 设置访问源请求方法 + config.addAllowedMethod("*"); + // 设置访问源请求方法 + source.registerCorsConfiguration("/**", config); + return new CorsFilter(source); + } + + /** + * anyRequest | 匹配所有请求路径 + * access | SpringEl表达式结果为true时可以访问 + * anonymous | 匿名可以访问 + * denyAll | 用户不能访问 + * fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录) + * hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问 + * hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问 + * hasAuthority | 如果有参数,参数表示权限,则其权限可以访问 + * hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问 + * hasRole | 如果有参数,参数表示角色,则其角色可以访问 + * permitAll | 用户可以任意访问 + * rememberMe | 允许通过remember-me登录的用户访问 + * authenticated | 用户登录后可访问 + * + * @param http HttpSecurity + * @throws Exception e + */ + @Override + protected void configure(HttpSecurity http) throws Exception { + http // CSRF禁用,因为不使用session + .csrf().disable() + + //认证失败处理 + .exceptionHandling() + //没认证 + .authenticationEntryPoint(authenticationEntryPoint) + //无权限 + .accessDeniedHandler(insufficientPermissionsHandler) + + //session管理 + .and() + .sessionManagement() + //基于token,所以不需要session,设置为无状态 + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + + + //过滤请求 + .and() + .authorizeRequests() + //静态资源用户可以任意访问 + .antMatchers(HttpMethod.GET, URL_STATIC).permitAll() + //允许匿名访问得url + .antMatchers(URL_WHITELIST).anonymous() + //除以上配置路径外全部需要鉴权认证 + .anyRequest().authenticated() + + //配置完全开启x-frame-options + .and() + .headers() + .frameOptions() + .disable() + + //退出成功处理 + .and() + .logout() + .logoutUrl("/logout") + .logoutSuccessHandler(logoutSuccessHandler) + + //添加滤器 + .and() + //添加token认证过滤器到表单认证过滤器得前面 + .addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class) + //添加跨域配置 + .addFilterBefore(corsFilter(), JwtAuthenticationTokenFilter.class) + .addFilterBefore(corsFilter(), LogoutFilter.class); + } + + /** + * 需要放行的静态资源 + */ + private static final String[] URL_STATIC = { + "/*.html", + "/**/*.html", + "/**/*.css", + "/**/*.js" + }; + + /** + * 需要放行的url地址 + */ + private static final String[] URL_WHITELIST = { + "/swagger-ui.html", + "/swagger-resources/**", + "/webjars/**", + "/*/api-docs", + "/druid/**", + "/login" + }; +} + +``` + +## 7. 其它工具类 + +### 1. 枚举 + +```java +/** + * 用户状态 + * + * @Date: 2021-08-27-17:55 + * @Author lj + */ +public enum UserStatus { + /** + * 相当于创建UserStatus对象 + * 正常 + */ + OK("0", "正常"), + + /** + * 禁用 + */ + DISABLE("1", "停用"), + + /** + * 删除 + */ + DELETED("2", "删除"); + + + /** + * 代号 + */ + private final String code; + + /** + * 信息 + */ + private final String info; + + + UserStatus(String code, String info) { + this.code = code; + this.info = info; + } + + public String getCode() { + return code; + } + + public String getInfo() { + return info; + } +} +``` + +### 2. 静态常量 + +```java +/** + * 静态常量 + * + * @Date: 2021-08-26-15:35 + * @Author lj + */ +public class Constants { + /** + * 毫秒 + */ + public static final long MILLIS_SECOND = 1000; + + /** + * 1分钟 + */ + public static final long MILLIS_MINUTE = 60 * MILLIS_SECOND; + + /** + * 10分钟 + */ + public static final Long MILLIS_MINUTE_TEN = 10 * 60 * 1000L; + + /** + * 未知 + */ + public static final String UNKNOWN = "unknown"; + + /** + * 2 + */ + public static final Integer TWO = 2; + + /** + * 4 + */ + public static final Integer FOUR = 4; + + /** + * GBK + */ + public static final String GBK = "GBK"; + + /** + * UTF-8 + */ + public static final String UTF8 = "UTF-8"; + + + /** + * redis中登录用户唯一标识得key + */ + public static final String LOGIN_TOKEN_KEY = "login_tokens:"; + + /** + * token中用户唯一标识 + */ + public static final String LOGIN_USER_KEY = "login_user_key"; + + /** + * 401 未授权 + */ + public static final Integer UNAUTHORIZED = 401; +} +``` + +### 3. ip解析 + +```java +/** + * @Date: 2021-08-27-8:43 + * @Author lj + */ +@Slf4j +public class IpUtils { + /** + * 获取请求得ip地址 + * + * @param request 请求 + * @return ip地址 + */ + public static String getIpAddr(HttpServletRequest request) { + if (request == null) { + return "unknown"; + } + String ip = request.getHeader("x-forwarded-for"); + if (ip == null || ip.length() == 0) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || Constants.UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("X-Forwarded-For"); + } + if (ip == null || ip.length() == 0 || Constants.UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || Constants.UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getHeader("X-Real-IP"); + } + + if (ip == null || ip.length() == 0 || Constants.UNKNOWN.equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : EscapeUtil.clean(ip); + } + + /** + * 判断ip是否为内网ip + * + * @param ip ip + * @return 是或否 + */ + public static boolean internalIp(String ip) { + //将IPv4地址转换成字节 + byte[] addr = textToNumericFormatV4(ip); + + return internalIp(addr) || "127.0.0.1".equals(ip); + } + + /** + * 判断该该字节是否为内网ip + * + * @param addr 字节内网ip + * @return 是或否 + */ + private static boolean internalIp(byte[] addr) { + if (Validator.isNull(addr) || addr.length < Constants.TWO) { + return true; + } + final byte b0 = addr[0]; + final byte b1 = addr[1]; + // 10.x.x.x/8 + final byte section1 = 0x0A; + // 172.16.x.x/12 + final byte section2 = (byte) 0xAC; + final byte section3 = (byte) 0x10; + final byte section4 = (byte) 0x1F; + // 192.168.x.x/16 + final byte section5 = (byte) 0xC0; + final byte section6 = (byte) 0xA8; + switch (b0) { + case section1: + return true; + case section2: + if (b1 >= section3 && b1 <= section4) { + return true; + } + case section5: + if (b1 == section6) { + return true; + } + default: + return false; + } + } + + /** + * 将IPv4地址转换成字节 + * + * @param text IPv4地址 + * @return byte 字节 + */ + public static byte[] textToNumericFormatV4(String text) { + long number1 = 4294967295L; + long number2 = 255L; + long number3 = 16777215L; + long number4 = 65535L; + + if (text.length() == 0) { + return null; + } + byte[] bytes = new byte[4]; + String[] elements = text.split("\\.", -1); + try { + long l; + int i; + switch (elements.length) { + case 1: + l = Long.parseLong(elements[0]); + if ((l < 0L) || (l > number1)) { + return null; + } + bytes[0] = (byte) (int) (l >> 24 & 0xFF); + bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF); + bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 2: + l = Integer.parseInt(elements[0]); + if ((l < 0L) || (l > number2)) { + return null; + } + bytes[0] = (byte) (int) (l & 0xFF); + l = Integer.parseInt(elements[1]); + if ((l < 0L) || (l > number3)) { + return null; + } + bytes[1] = (byte) (int) (l >> 16 & 0xFF); + bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 3: + for (i = 0; i < Constants.TWO; ++i) { + l = Integer.parseInt(elements[i]); + if ((l < 0L) || (l > 255L)) { + return null; + } + bytes[i] = (byte) (int) (l & 0xFF); + } + l = Integer.parseInt(elements[2]); + if ((l < 0L) || (l > number4)) { + return null; + } + bytes[2] = (byte) (int) (l >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 4: + for (i = 0; i < Constants.FOUR; ++i) { + l = Integer.parseInt(elements[i]); + if ((l < 0L) || (l > 255L)) { + return null; + } + bytes[i] = (byte) (int) (l & 0xFF); + } + break; + default: + return null; + } + } catch (NumberFormatException e) { + return null; + } + return bytes; + } + + /** + * 获取本机ip地址 + * + * @return ip地址 + */ + public static String getHostIp() { + try { + return InetAddress.getLocalHost().getHostAddress(); + } catch (UnknownHostException e) { + log.error(e.getMessage()); + } + return "127.0.0.1"; + } + + /** + * 获取本机名称 + * + * @return 主机名称 + */ + public static String getHostName() { + try { + return InetAddress.getLocalHost().getHostName(); + } catch (UnknownHostException e) { + log.error("UnknownHostException", e); + } + return "未知"; + } +} + +``` + +```java +package com.mangmang.security.utils; + +import cn.hutool.http.HTMLFilter; + +/** + * @Date: 2021-08-27-8:44 + * @Author lj + */ +public class EscapeUtil { + public static final String RE_HTML_MARK = "(<[^<]*?>)|(<[\\s]*?/[^<]*?>)|(<[^<]*?/[\\s]*?>)"; + + private static final char[][] TEXT = new char[64][]; + + /** + * 清除所有HTML标签,但是不删除标签内的内容 + * + * @param content 文本 + * @return 清除标签后的文本 + */ + public static String clean(String content) { + return new HTMLFilter().filter(content); + } +} +``` + +### 4. HTTP请求发送 + +```java +package com.mangmang.security.utils; + +import lombok.extern.slf4j.Slf4j; + +import javax.net.ssl.*; +import java.io.*; +import java.net.ConnectException; +import java.net.SocketTimeoutException; +import java.net.URL; +import java.net.URLConnection; +import java.nio.charset.StandardCharsets; +import java.security.cert.X509Certificate; + +/** + * @Date: 2021-08-27-9:22 + * @Author lj + */ +@Slf4j +public class HttpUtils { + /** + * 向指定 URL 发送GET方法的请求 + * + * @param url 发送请求的 URL + * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @return 所代表远程资源的响应结果 + */ + public static String sendGet(String url, String param) { + return sendGet(url, param, Constants.UTF8); + } + + + /** + * 向指定 URL 发送GET方法的请求 + * + * @param url 发送请求的 URL + * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @param contentType 编码类型 + * @return 所代表远程资源的响应结果 + */ + public static String sendGet(String url, String param, String contentType) { + StringBuilder result = new StringBuilder(); + BufferedReader in = null; + try { + String urlNameString = url + "?" + param; + log.info("sendGet - {}", urlNameString); + URL realUrl = new URL(urlNameString); + URLConnection connection = realUrl.openConnection(); + connection.setRequestProperty("accept", "*/*"); + connection.setRequestProperty("connection", "Keep-Alive"); + connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + connection.connect(); + in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType)); + String line; + while ((line = in.readLine()) != null) { + result.append(line); + } + log.info("recv - {}", result); + } catch (ConnectException e) { + log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e); + } catch (SocketTimeoutException e) { + log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e); + } catch (IOException e) { + log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e); + } catch (Exception e) { + log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e); + } finally { + try { + if (in != null) { + in.close(); + } + } catch (Exception ex) { + log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); + } + } + return result.toString(); + } + + + /** + * 向指定 URL 发送POST方法的请求 + * + * @param url 发送请求的 URL + * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @return 所代表远程资源的响应结果 + */ + public static String sendPost(String url, String param) { + PrintWriter out = null; + BufferedReader in = null; + StringBuilder result = new StringBuilder(); + try { + log.info("sendPost - {}", url); + URL realUrl = new URL(url); + URLConnection conn = realUrl.openConnection(); + conn.setRequestProperty("accept", "*/*"); + conn.setRequestProperty("connection", "Keep-Alive"); + conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + conn.setRequestProperty("Accept-Charset", "utf-8"); + conn.setRequestProperty("contentType", "utf-8"); + conn.setDoOutput(true); + conn.setDoInput(true); + out = new PrintWriter(conn.getOutputStream()); + out.print(param); + out.flush(); + in = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8)); + String line; + while ((line = in.readLine()) != null) { + result.append(line); + } + log.info("recv - {}", result); + } catch (ConnectException e) { + result.setLength(0); + result.append("fail"); + log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e); + } catch (SocketTimeoutException e) { + result.setLength(0); + result.append("fail"); + log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e); + } catch (IOException e) { + result.setLength(0); + result.append("fail"); + log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e); + } catch (Exception e) { + result.setLength(0); + result.append("fail"); + log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e); + } finally { + try { + if (out != null) { + out.close(); + } + if (in != null) { + in.close(); + } + } catch (IOException ex) { + log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); + } + } + return result.toString(); + } + + public static String sendSslPost(String url, String param) { + StringBuilder result = new StringBuilder(); + String urlNameString = url + "?" + param; + try { + log.info("sendSSLPost - {}", urlNameString); + SSLContext sc = SSLContext.getInstance("SSL"); + sc.init(null, new TrustManager[]{new TrustAnyTrustManager()}, new java.security.SecureRandom()); + URL console = new URL(urlNameString); + HttpsURLConnection conn = (HttpsURLConnection) console.openConnection(); + conn.setRequestProperty("accept", "*/*"); + conn.setRequestProperty("connection", "Keep-Alive"); + conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); + conn.setRequestProperty("Accept-Charset", "utf-8"); + conn.setRequestProperty("contentType", "utf-8"); + conn.setDoOutput(true); + conn.setDoInput(true); + + conn.setSSLSocketFactory(sc.getSocketFactory()); + conn.setHostnameVerifier(new TrustAnyHostnameVerifier()); + conn.connect(); + InputStream is = conn.getInputStream(); + BufferedReader br = new BufferedReader(new InputStreamReader(is)); + String ret; + while ((ret = br.readLine()) != null) { + if (!"".equals(ret.trim())) { + result.append(new String(ret.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8)); + } + } + log.info("recv - {}", result); + conn.disconnect(); + br.close(); + } catch (ConnectException e) { + log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e); + } catch (SocketTimeoutException e) { + log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e); + } catch (IOException e) { + log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e); + } catch (Exception e) { + log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e); + } + return result.toString(); + } + + private static class TrustAnyTrustManager implements X509TrustManager { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) { + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[]{}; + } + } + + private static class TrustAnyHostnameVerifier implements HostnameVerifier { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + } +} +``` + +# 三 、SpringSecurity授权 + +## 1. 获取用户权限信息 + +```java +package com.mangmang.security.securities.service; + +import com.mangmang.security.entity.User; +import com.mangmang.security.securities.domain.SecurityUser; +import com.mangmang.security.service.MenuService; +import com.mangmang.security.service.RoleService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.HashSet; +import java.util.Set; + +/** + * 用户权限处理 + * + * @Date: 2021-08-30-17:26 + * @Author lj + */ +@Component +@RequiredArgsConstructor +public class PermissionService { + private final RoleService roleService; + + private final MenuService menuService; + + /** + * 获取角色权限 + * + * @param user 用户信息 + * @return 角色权限集合 + */ + public Set getRolePermission(User user) { + // 需要返回得权限集合 + Set roles = new HashSet<>(); + //如果为超级用户添加超级用户 + if (user.judgeIsAdmin()) { + roles.add("admin"); + } else { + //否则添加其它角色 + roles.addAll(roleService.findRolePermissionByUserId(user.getUserId())); + } + return roles; + } + + /** + * 获取菜单权限 + * + * @param user 用户户信息 + * @return 菜单权限集合 + */ + public Set getMenuPermission(User user) { + // 需要返回得权限集合 + Set menus = new HashSet<>(); + //如果为超级用户添加超级用户 + if (user.judgeIsAdmin()) { + menus.add("*:*:*"); + } else { + //添加两个集合得并集 + menus.addAll(menuService.findAllMenu(user.getUserId())); + } + return menus; + } +} +``` + +## 2. 自定义权限判断 + +```java +package com.mangmang.security.securities.service; + +import cn.hutool.core.lang.Validator; +import cn.hutool.core.util.StrUtil; +import com.mangmang.security.entity.Role; +import com.mangmang.security.securities.domain.SecurityUser; +import com.mangmang.security.securities.jwt.TokenService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import java.util.Set; + +/** + * 判断是否具有某个权限 + * 自定义权限实现,ss取自SpringSecurity首字母 + * + * @Date: 2021-08-30-18:21 + * @Author lj + */ +@Service("ss") +@RequiredArgsConstructor +public class PermissionJudgeService { + + /** + * 拥有所有菜单权限标识 + */ + private static final String ALL_PERMISSION = "*:*:*"; + + + /** + * 拥有管理员角色权限标识 + */ + private static final String SUPER_ADMIN = "admin"; + + + /** + * 分隔符 + */ + private static final String DELIMETER = ","; + + + /** + * token处理 + */ + private final TokenService tokenService; + + + /** + * 获取request + */ + public static HttpServletRequest getRequest() { + //获取当前线程得request + RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); + ServletRequestAttributes requestAttributes = (ServletRequestAttributes) attributes; + assert requestAttributes != null; + return requestAttributes.getRequest(); + } + + + /** + * 验证用户是否具备某权限 + * + * @param permission 需要判断的权限字符串 + * @return 是或否 + */ + public boolean hasPermi(String permission) { + //如果为null拒绝访问 + if (Validator.isEmpty(permission)) { + return false; + } + //获取用户信息 + SecurityUser securityUser = tokenService.getSecurityUser(getRequest()); + //如果用户或权限为空拒绝访问 + if (Validator.isNull(securityUser) || Validator.isNull(securityUser.getPermissions())) { + return false; + } + return hasPermissions(securityUser.getPermissions(), permission); + } + + /** + * 验证不具备某个权限 + * + * @param permission 需要判断的权限字符串 + * @return true=没有该权限 false=有该权限 + */ + public boolean lacksPermi(String permission) { + return !hasPermi(permission); + } + + + /** + * 验证用户是否具备以下任意权限 + * + * @param permissions 以逗号为分隔符的权限列表 + * @return true=有该权限 false=无该权限 + */ + public boolean hasAnyPermi(String permissions) { + if (Validator.isEmpty(permissions)) { + return false; + } + //获取用户信息 + SecurityUser securityUser = tokenService.getSecurityUser(getRequest()); + //如果用户或权限为空拒绝访问 + if (Validator.isNull(securityUser) || Validator.isNull(securityUser.getPermissions())) { + return false; + } + //获取权限列表 + Set authorities = securityUser.getPermissions(); + //遍历权限判断是否具有某一个权限 + for (String permission : permissions.split(DELIMETER)) { + if (permission != null && hasPermissions(authorities, permission)) { + return true; + } + } + return false; + } + + /** + * 公用判断是否拥有某个权限 + */ + private boolean hasPermissions(Set permissions, String permission) { + return permissions.contains(ALL_PERMISSION) || permissions.contains(StrUtil.trim(permission)); + } + + /** + * 判断用户是否具有某一角色 + * + * @param role 需要判断得角色 + * @return true=有这个权限 false=没有这个权限 + */ + public boolean hasRole(String role) { + if (Validator.isEmpty(role)) { + return false; + } + SecurityUser securityUser = tokenService.getSecurityUser(getRequest()); + if (Validator.isNull(securityUser) || Validator.isNull(securityUser.getUser().getRoles())) { + return false; + } + + for (String rolePerm : securityUser.getUser().getRoles()) { + if (SUPER_ADMIN.equals(rolePerm) || rolePerm.equals(StrUtil.trim(role))) { + return true; + } + } + return false; + } + + /** + * 验证用户是否不具备某角色,与hasRole逻辑相反。 + * + * @param role 角色名称 + * @return 用户是否不具备某角色 + */ + public boolean lacksRole(String role) { + return !hasRole(role); + } + + /** + * 验证用户是否具有以下任意一个角色 + * + * @param roles 以逗号为分隔符的角色列表 + * @return 用户是否具有以下任意一个角色 + */ + public boolean hasAnyRoles(String roles) { + if (Validator.isEmpty(roles)) { + return false; + } + SecurityUser securityUser = tokenService.getSecurityUser(getRequest()); + if (Validator.isNull(securityUser) || Validator.isEmpty(securityUser.getUser().getRoles())) { + return false; + } + //roles以逗号分割后循环有匹配得数据直接返回true + for (String role : roles.split(DELIMETER)) { + if (hasRole(role)) { + return true; + } + } + return false; + } +} +``` + +## 3. 测试类 + +```java +package com.mangmang.security.controller; + +import com.mangmang.security.securities.service.LoginService; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; + +/** + * @Date: 2021-08-29-16:51 + * @Author lj + */ +@RestController +@RequiredArgsConstructor +public class LoginController { + + private final LoginService loginService; + + @ApiOperation("登录接口") + @GetMapping("/login") + public String login(String username, String password, HttpServletRequest request) { + return loginService.login(username, password, request); + } + + @PreAuthorize("@ss.hasPermi('system:menu:list')") + @ApiOperation("test是否具有system:menu:list权限") + @GetMapping("/vip1") + public String test() { + return "访问成功"; + } + + @PreAuthorize("@ss.hasPermi('system:menu:test')") + @ApiOperation("test是否具有system:menu:test权限") + @GetMapping("/vip2") + public String test2() { + return "访问成功"; + } +} +``` + +## 4. 查询权限得2个方法 + +```java +package com.mangmang.security.service.impl; + + +import cn.hutool.core.lang.Validator; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.api.R; +import com.mangmang.security.entity.Menu; +import com.mangmang.security.entity.Role; +import com.mangmang.security.entity.RoleMenu; +import com.mangmang.security.entity.UserRole; +import com.mangmang.security.mapper.MenuMapper; +import com.mangmang.security.mapper.RoleMapper; +import com.mangmang.security.mapper.RoleMenuMapper; +import com.mangmang.security.mapper.UserRoleMapper; +import com.mangmang.security.service.MenuService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * + * 菜单权限表 服务实现类 + * + * + * @author 氓氓编程 + * @since 2021-08-26 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class MenuServiceImpl extends ServiceImpl implements MenuService { + + + private final RoleMapper roleMapper; + + private final UserRoleMapper userRoleMapper; + + private final RoleMenuMapper roleMenuMapper; + + @Override + public Set findAllMenu(Long userId) { + //查询该用户所有角色 + List userRoleList = userRoleMapper.selectList(new QueryWrapper().eq("user_id", userId)); + if (userRoleList.size() <= 0) { + return null; + } + Set roleIdSet = userRoleList.stream().map(UserRole::getRoleId).collect(Collectors.toSet()); + + //查询该角色中未被禁用角色 + QueryWrapper roleWrapper = new QueryWrapper<>(); + roleWrapper.in("role_id", roleIdSet); + roleWrapper.eq("status", 0); + List rolesIdList = roleMapper.selectList(roleWrapper); + Set roleIds = rolesIdList.stream().map(Role::getRoleId).collect(Collectors.toSet()); + + //查询用户角色所有菜单 + List roleMenuList = roleMenuMapper.selectList(new QueryWrapper().in("role_id", roleIds)); + if (roleMenuList.size() <= 0) { + return null; + } + + Set menuIdSet = roleMenuList.stream().map(RoleMenu::getMenuId).collect(Collectors.toSet()); + //查询用户所有菜单中未禁用菜单 + QueryWrapper menuWrapper = new QueryWrapper<>(); + menuWrapper.select("perms"); + menuWrapper.eq("status", 0); + menuWrapper.in("menu_id", menuIdSet); + List menus = baseMapper.selectList(menuWrapper); + + //创建权限集合 + Set permsSet = new HashSet<>(); + for (Menu menu : menus) { + if (Validator.isNotEmpty(menu.getPerms())) { + permsSet.addAll(Arrays.asList(menu.getPerms().split(","))); + } + } + log.info("{}", permsSet); + return permsSet; + } +} + +``` + +```java +package com.mangmang.security.service.impl; + +import cn.hutool.core.lang.Validator; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.mangmang.security.entity.Role; +import com.mangmang.security.entity.UserRole; +import com.mangmang.security.mapper.RoleMapper; +import com.mangmang.security.mapper.UserRoleMapper; +import com.mangmang.security.service.RoleService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.mangmang.security.service.UserRoleService; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * + * 角色信息表 服务实现类 + * + * + * @author 氓氓编程 + * @since 2021-08-26 + */ +@Service +@RequiredArgsConstructor +public class RoleServiceImpl extends ServiceImpl implements RoleService { + + private final UserRoleMapper userRoleMapper; + + @Override + public Set findRolePermissionByUserId(Long userId) { + //获取该用户所有角色信息 + List userRoleList = userRoleMapper.selectList(new QueryWrapper().eq("user_id", userId)); + if (userRoleList.size() <= 0) { + return null; + } + + //获取角色id + Set roleIdSet = userRoleList.stream().map(UserRole::getRoleId).collect(Collectors.toSet()); + + //查询该角色中未被禁用角色 + QueryWrapper roleWrapper = new QueryWrapper<>(); + roleWrapper.in("role_id", roleIdSet); + roleWrapper.eq("status", 0); + List perms = baseMapper.selectList(roleWrapper); + + //遍历角色 + Set permsSet = new HashSet<>(); + for (Role perm : perms) { + if (Validator.isNotNull(perm)) { + permsSet.addAll(Arrays.asList(perm.getRoleKey().trim().split(","))); + } + } + return permsSet; + } +} +``` +
- * 菜单权限表 服务实现类 - *
- * 角色信息表 服务实现类 - *
+ * 菜单权限表 服务实现类 + *
+ * 角色信息表 服务实现类 + *