├── .gitignore ├── LICENSE ├── pom.xml ├── readme.md └── src ├── main ├── java │ └── com │ │ └── leigq │ │ └── www │ │ └── shiro │ │ ├── SpringBootShiroApplication.java │ │ ├── bean │ │ ├── CacheUser.java │ │ └── Response.java │ │ ├── config │ │ ├── DruidDataSourceConfig.java │ │ ├── DruidMonitorConfig.java │ │ ├── MyBatisPlusConfig.java │ │ ├── MySessionManager.java │ │ ├── MyShiroRealm.java │ │ └── ShiroConfig.java │ │ ├── controller │ │ ├── LoginController.java │ │ ├── PermissionController.java │ │ ├── RoleController.java │ │ ├── RolePermissionController.java │ │ ├── UserController.java │ │ └── UserRoleController.java │ │ ├── domain │ │ ├── entity │ │ │ ├── Permission.java │ │ │ ├── Role.java │ │ │ ├── RolePermission.java │ │ │ ├── User.java │ │ │ └── UserRole.java │ │ └── mapper │ │ │ ├── PermissionMapper.java │ │ │ ├── RoleMapper.java │ │ │ ├── RolePermissionMapper.java │ │ │ ├── UserMapper.java │ │ │ └── UserRoleMapper.java │ │ ├── service │ │ ├── IPermissionService.java │ │ ├── IRolePermissionService.java │ │ ├── IRoleService.java │ │ ├── IUserRoleService.java │ │ ├── IUserService.java │ │ └── impl │ │ │ ├── PermissionServiceImpl.java │ │ │ ├── RolePermissionServiceImpl.java │ │ │ ├── RoleServiceImpl.java │ │ │ ├── UserRoleServiceImpl.java │ │ │ └── UserServiceImpl.java │ │ ├── util │ │ └── CodeGeneratorUtils.java │ │ └── web │ │ ├── GlobalExceptionHand.java │ │ └── exception │ │ └── LoginException.java └── resources │ ├── application.yml │ ├── config │ ├── application-dev.yml │ ├── application-prod.yml │ └── application-test.yml │ ├── mappers │ ├── PermissionMapper.xml │ ├── RoleMapper.xml │ ├── RolePermissionMapper.xml │ ├── UserMapper.xml │ └── UserRoleMapper.xml │ └── sql │ ├── shiro-V1.0.0.sql │ └── shiro-V1.0.1.sql └── test └── java └── com └── leigq └── www └── shiro ├── base └── BaseApplicationTests.java └── test └── ShiroApplicationTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | 30 | ### VS Code ### 31 | .vscode/ 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.1.6.RELEASE 9 | 10 | 11 | com.leigq.www 12 | spring-boot-shiro 13 | 0.0.1-SNAPSHOT 14 | spring-boot-shiro 15 | shiro demo project for Spring Boot 16 | 17 | 18 | 1.8 19 | 1.0.29 20 | 4.1 21 | 3.1.1 22 | 1.4.0 23 | 3.1.0 24 | 25 | 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-actuator 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-web 34 | 35 | 36 | 37 | mysql 38 | mysql-connector-java 39 | runtime 40 | 41 | 42 | 43 | 44 | com.alibaba 45 | druid 46 | ${druid.version} 47 | 48 | 49 | 50 | com.baomidou 51 | mybatis-plus-boot-starter 52 | ${mybatis-plus.version} 53 | 54 | 55 | 56 | com.baomidou 57 | mybatis-plus-generator 58 | ${mybatis-plus.version} 59 | 60 | 61 | 62 | org.springframework.boot 63 | spring-boot-starter-freemarker 64 | 65 | 66 | 67 | org.projectlombok 68 | lombok 69 | true 70 | 71 | 72 | 73 | org.springframework.boot 74 | spring-boot-starter-test 75 | test 76 | 77 | 78 | 80 | 81 | 82 | org.apache.commons 83 | commons-lang3 84 | 85 | 86 | 87 | 88 | org.apache.commons 89 | commons-collections4 90 | ${commons-collections4.version} 91 | 92 | 93 | 95 | 96 | commons-codec 97 | commons-codec 98 | 99 | 100 | 101 | org.apache.shiro 102 | shiro-spring 103 | ${shiro-spring.version} 104 | 105 | 106 | 107 | org.crazycake 108 | shiro-redis 109 | ${shiro-redis.version} 110 | 111 | 112 | 113 | org.springframework.boot 114 | spring-boot-starter-thymeleaf 115 | 116 | 117 | 118 | 119 | 120 | 121 | org.springframework.boot 122 | spring-boot-maven-plugin 123 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## 主要框架 2 | 3 | ![SpringBoot version](https://img.shields.io/badge/SpringBoot-2.1.6.RELEASE-blue.svg)  4 | ![Shiro-Spring version](https://img.shields.io/badge/shiro--spring-1.4.0-orange.svg)  5 | ![druid version](https://img.shields.io/badge/druid-1.0.29-red.svg)  6 | ![mybatis plus version](https://img.shields.io/badge/mybatis--plus-3.1.1-green.svg)  7 | ![shiro-redis version](https://img.shields.io/badge/shiro--redis-3.1.0-yellow.svg)  8 | 9 | ## Shiro架构图与基本知识 10 | 11 | 1、Shiro是Apache下的一个开源项目,我们称之为Apache Shiro。它是一个很易用与Java项目的的安全框架,提供了认证、授权、加密、会话管理,与spring Security 一样都是做一个权限的安全框架,但是与Spring Security 相比,在于 Shiro 使用了比较简单易懂易于使用的授权方式。shiro属于轻量级框架,相对于security简单的多,也没有security那么复杂。所以我这里也是简单介绍一下shiro的使用。 12 | 13 | 2、非常简单;其基本功能点如下图所示: 14 | 15 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190706152247236.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM0ODQ1Mzk0,size_16,color_FFFFFF,t_70) 16 | 17 | **Authentication**:身份认证/登录,验证用户是不是拥有相应的身份; 18 | 19 | **Authorization**:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限; 20 | 21 | **Session Manager**:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的; 22 | 23 | **Cryptography**:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储; 24 | 25 | **Web Support**:Web支持,可以非常容易的集成到Web环境; 26 | 27 | Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率; 28 | 29 | **Concurrency**:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去; 30 | 31 | Testing:提供测试支持; 32 | 33 | **Run As**:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问; 34 | 35 | **Remember Me**:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。 36 | 37 | **记住一点,Shiro不会去维护用户、维护权限;这些需要我们自己去设计/提供;然后通过相应的接口注入给Shiro即可。** 38 | 39 | ## 源码地址 40 | https://github.com/MRLEILOVE/spring-boot-shiro 41 | 42 | ## 数据库结构 43 | 5张表,也就是现在流行的权限设计模型[RBAC](https://baike.baidu.com/item/RBAC/1328788?fr=aladdin),建表SQL已放在项目中。 44 | 45 | 用户、角色、权限、用户-角色、角色-权限,关系如下。 46 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190706150822192.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM0ODQ1Mzk0,size_16,color_FFFFFF,t_70) 47 | 48 | 49 | ## 项目结构 50 | 下面是整个项目结构,主要类已做注释。 51 | ``` 52 | ├─main 53 | │ ├─java 54 | │ │ └─com 55 | │ │ └─leigq 56 | │ │ └─www 57 | │ │ └─shiro 58 | │ │ │ SpringBootShiroApplication.java 59 | │ │ │ 60 | │ │ ├─bean 61 | │ │ │ CacheUser.java --- 缓存用户信息 62 | │ │ │ Response.java --- 统一返回结果 63 | │ │ │ 64 | │ │ ├─config 65 | │ │ │ DruidDataSourceConfig.java --- Druid数据源配置 66 | │ │ │ DruidMonitorConfig.java --- Druid监控配置 67 | │ │ │ MyBatisPlusConfig.java --- MyBatisPlus配置 68 | │ │ │ MySessionManager.java --- 自定义session管理 69 | │ │ │ MyShiroRealm.java --- 自定义 shiroRealm, 主要是重写其认证、授权 70 | │ │ │ ShiroConfig.java --- Shiro管理 71 | │ │ │ 72 | │ │ ├─controller 73 | │ │ │ LoginController.java 74 | │ │ │ PermissionController.java 75 | │ │ │ RoleController.java 76 | │ │ │ RolePermissionController.java 77 | │ │ │ UserController.java 78 | │ │ │ UserRoleController.java 79 | │ │ │ 80 | │ │ ├─domain 81 | │ │ │ ├─entity 82 | │ │ │ │ Permission.java 83 | │ │ │ │ Role.java 84 | │ │ │ │ RolePermission.java 85 | │ │ │ │ User.java 86 | │ │ │ │ UserRole.java 87 | │ │ │ │ 88 | │ │ │ └─mapper 89 | │ │ │ PermissionMapper.java 90 | │ │ │ RoleMapper.java 91 | │ │ │ RolePermissionMapper.java 92 | │ │ │ UserMapper.java 93 | │ │ │ UserRoleMapper.java 94 | │ │ │ 95 | │ │ ├─service 96 | │ │ │ │ IPermissionService.java 97 | │ │ │ │ IRolePermissionService.java 98 | │ │ │ │ IRoleService.java 99 | │ │ │ │ IUserRoleService.java 100 | │ │ │ │ IUserService.java 101 | │ │ │ │ 102 | │ │ │ └─impl 103 | │ │ │ PermissionServiceImpl.java 104 | │ │ │ RolePermissionServiceImpl.java 105 | │ │ │ RoleServiceImpl.java 106 | │ │ │ UserRoleServiceImpl.java 107 | │ │ │ UserServiceImpl.java 108 | │ │ │ 109 | │ │ ├─util 110 | │ │ │ CodeGeneratorUtils.java --- MyBatisPlus代码生成器 111 | │ │ │ 112 | │ │ └─web 113 | │ │ │ GlobalExceptionHand.java --- 全局异常处理 114 | │ │ │ 115 | │ │ └─exception 116 | │ │ LoginException.java 117 | │ │ 118 | │ └─resources 119 | │ │ application.yml 120 | │ │ 121 | │ ├─config 122 | │ │ application-dev.yml 123 | │ │ application-prod.yml 124 | │ │ application-test.yml 125 | │ │ 126 | │ ├─mappers 127 | │ │ PermissionMapper.xml 128 | │ │ RoleMapper.xml 129 | │ │ RolePermissionMapper.xml 130 | │ │ UserMapper.xml 131 | │ │ UserRoleMapper.xml 132 | │ │ 133 | │ ├─sql 134 | │ │ shiro-V1.0.0.sql 135 | │ │ shiro-V1.0.1.sql --- 最新版SQL 136 | │ │ 137 | │ ├─static 138 | │ └─templates 139 | └─test 140 | └─java 141 | └─com 142 | └─leigq 143 | └─www 144 | └─shiro 145 | ├─base 146 | │ BaseApplicationTests.java 147 | │ 148 | └─test 149 | ShiroApplicationTests.java 150 | ``` 151 | ## 详细搭建过程 152 | 建议直接将代码拉下来对照着文档看 153 | 154 | 1、将最新版SQL导入数据库,SQL我已经放入项目中 155 | 156 | 2、引入依赖。 157 | ```xml 158 | 159 | 161 | 4.0.0 162 | 163 | org.springframework.boot 164 | spring-boot-starter-parent 165 | 2.1.6.RELEASE 166 | 167 | 168 | com.leigq.www 169 | spring-boot-shiro 170 | 0.0.1-SNAPSHOT 171 | spring-boot-shiro 172 | shiro demo project for Spring Boot 173 | 174 | 175 | 1.8 176 | 1.0.29 177 | 4.1 178 | 3.1.1 179 | 1.4.0 180 | 3.1.0 181 | 182 | 183 | 184 | 185 | org.springframework.boot 186 | spring-boot-starter-actuator 187 | 188 | 189 | org.springframework.boot 190 | spring-boot-starter-web 191 | 192 | 193 | 194 | mysql 195 | mysql-connector-java 196 | runtime 197 | 198 | 199 | 200 | 201 | com.alibaba 202 | druid 203 | ${druid.version} 204 | 205 | 206 | 207 | com.baomidou 208 | mybatis-plus-boot-starter 209 | ${mybatis-plus.version} 210 | 211 | 212 | 213 | com.baomidou 214 | mybatis-plus-generator 215 | ${mybatis-plus.version} 216 | 217 | 218 | 219 | org.springframework.boot 220 | spring-boot-starter-freemarker 221 | 222 | 223 | 224 | org.projectlombok 225 | lombok 226 | true 227 | 228 | 229 | 230 | org.springframework.boot 231 | spring-boot-starter-test 232 | test 233 | 234 | 235 | 237 | 238 | 239 | org.apache.commons 240 | commons-lang3 241 | 242 | 243 | 244 | 245 | org.apache.commons 246 | commons-collections4 247 | ${commons-collections4.version} 248 | 249 | 250 | 252 | 253 | commons-codec 254 | commons-codec 255 | 256 | 257 | 258 | org.apache.shiro 259 | shiro-spring 260 | ${shiro-spring.version} 261 | 262 | 263 | 264 | org.crazycake 265 | shiro-redis 266 | ${shiro-redis.version} 267 | 268 | 269 | 270 | org.springframework.boot 271 | spring-boot-starter-thymeleaf 272 | 273 | 274 | 275 | 276 | 277 | 278 | org.springframework.boot 279 | spring-boot-maven-plugin 280 | 281 | 282 | 283 | 284 | 285 | ``` 286 | 3、编辑application.yml 287 | 288 | 我项目中使用了多环境配置,你们可根据自己情况修改 289 | 290 | ```yml 291 | mybatis-plus: 292 | configuration: 293 | map-underscore-to-camel-case: true 294 | use-generated-keys: true 295 | mapper-locations: classpath*:/mappers/**/*.xml 296 | type-aliases-package: com.leigq.www.shiro.domain.entity 297 | server: 298 | tomcat: 299 | uri-encoding: UTF-8 300 | spring: 301 | datasource: 302 | connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000;config.decrypt=true;config.decrypt.key=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKPYsCl3alwZlRb1vKoFdVu0LP3Nm/+vH5iOWxI83pkUbrQc13Lxz/VT3D+H+ziaUpUsA+ZjG4iZGTDJWZnP8kcCAwEAAQ== 303 | driver-class-name: com.mysql.cj.jdbc.Driver 304 | filters: config,stat,wall,slf4j 305 | initialSize: 5 306 | maxActive: 20 307 | maxPoolPreparedStatementPerConnectionSize: 20 308 | maxWait: 60000 309 | minEvictableIdleTimeMillis: 300000 310 | minIdle: 5 311 | password: kGJF6c+pzVsf49LGs01ss0yijBGXIpNEp20cMkNCQo3ONaeMNPeoW9M89v+nGeiWs95/D2Ms59uGyydDGUWpmg== 312 | poolPreparedStatements: true 313 | testOnBorrow: false 314 | testOnReturn: false 315 | testWhileIdle: true 316 | timeBetweenEvictionRunsMillis: 60000 317 | type: com.alibaba.druid.pool.DruidDataSource 318 | url: jdbc:mysql://localhost:3306/shiro?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=true&serverTimezone=UTC 319 | username: root 320 | validationQuery: SELECT 1 FROM DUAL 321 | thymeleaf: 322 | cache: false 323 | redis: 324 | host: localhost 325 | port: 6379 326 | timeout: 2000s 327 | password: 111111 328 | ``` 329 | 4、创建MySessionManager 330 | 331 | ```java 332 | package com.leigq.www.shiro.config; 333 | 334 | import org.apache.commons.lang3.StringUtils; 335 | import org.apache.shiro.web.servlet.ShiroHttpServletRequest; 336 | import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; 337 | import org.apache.shiro.web.util.WebUtils; 338 | 339 | import javax.servlet.ServletRequest; 340 | import javax.servlet.ServletResponse; 341 | import java.io.Serializable; 342 | 343 | /** 344 | * 自定义session管理 345 | *
346 | * 传统结构项目中,shiro从cookie中读取sessionId以此来维持会话,在前后端分离的项目中(也可在移动APP项目使用), 347 | * 我们选择在ajax的请求头中传递sessionId,因此需要重写shiro获取sessionId的方式。 348 | * 自定义MySessionManager类继承DefaultWebSessionManager类,重写getSessionId方法 349 | * @author :leigq 350 | * @date :2019/7/1 10:52 351 | */ 352 | public class MySessionManager extends DefaultWebSessionManager { 353 | 354 | private static final String AUTHORIZATION = "Authorization"; 355 | 356 | private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request"; 357 | 358 | @Override 359 | protected Serializable getSessionId(ServletRequest request, ServletResponse response) { 360 | String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION); 361 | //如果请求头中有 Authorization 则其值为sessionId 362 | if (!StringUtils.isEmpty(id)) { 363 | request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE); 364 | request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id); 365 | request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE); 366 | return id; 367 | } else { 368 | //否则按默认规则从cookie取sessionId 369 | return super.getSessionId(request, response); 370 | } 371 | } 372 | } 373 | 374 | ``` 375 | 376 | 5、创建MyShiroRealm 377 | ```java 378 | package com.leigq.www.shiro.config; 379 | 380 | import com.leigq.www.shiro.domain.entity.Permission; 381 | import com.leigq.www.shiro.domain.entity.Role; 382 | import com.leigq.www.shiro.domain.entity.User; 383 | import com.leigq.www.shiro.service.IPermissionService; 384 | import com.leigq.www.shiro.service.IRoleService; 385 | import com.leigq.www.shiro.service.IUserService; 386 | import lombok.extern.slf4j.Slf4j; 387 | import org.apache.shiro.authc.AuthenticationException; 388 | import org.apache.shiro.authc.AuthenticationInfo; 389 | import org.apache.shiro.authc.AuthenticationToken; 390 | import org.apache.shiro.authc.SimpleAuthenticationInfo; 391 | import org.apache.shiro.authz.AuthorizationInfo; 392 | import org.apache.shiro.authz.SimpleAuthorizationInfo; 393 | import org.apache.shiro.realm.AuthorizingRealm; 394 | import org.apache.shiro.subject.PrincipalCollection; 395 | import org.apache.shiro.util.ByteSource; 396 | 397 | import javax.annotation.Resource; 398 | import java.util.List; 399 | import java.util.Objects; 400 | 401 | /** 402 | * @author :leigq 403 | * @date :2019/6/28 16:31 404 | * @description:自定义 shiroRealm, 主要是重写其认证、授权 405 | */ 406 | @Slf4j 407 | public class MyShiroRealm extends AuthorizingRealm { 408 | 409 | @Resource 410 | private IUserService iUserService; 411 | 412 | @Resource 413 | private IRoleService iRoleService; 414 | 415 | @Resource 416 | private IPermissionService iPermissionService; 417 | 418 | 419 | /** 420 | * create by: leigq 421 | * description: 授权 422 | * create time: 2019/7/1 10:32 423 | * 424 | * @return 权限信息,包括角色以及权限 425 | */ 426 | @Override 427 | protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { 428 | log.warn("开始执行授权操作......."); 429 | 430 | SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); 431 | //如果身份认证的时候没有传入User对象,这里只能取到userName 432 | //也就是SimpleAuthenticationInfo构造的时候第一个参数传递需要User对象 433 | User user = (User) principals.getPrimaryPrincipal(); 434 | 435 | // 查询用户角色,一个用户可能有多个角色 436 | List roles = iRoleService.getUserRoles(user.getUserId()); 437 | 438 | for (Role role : roles) { 439 | authorizationInfo.addRole(role.getRole()); 440 | // 根据角色查询权限 441 | List permissions = iPermissionService.getRolePermissions(role.getRoleId()); 442 | for (Permission p : permissions) { 443 | authorizationInfo.addStringPermission(p.getPermission()); 444 | } 445 | } 446 | return authorizationInfo; 447 | } 448 | 449 | /** 450 | * create by: leigq 451 | * description: 主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。 452 | * create time: 2019/7/1 09:04 453 | * 454 | * @return 身份验证信息 455 | */ 456 | @Override 457 | protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { 458 | log.warn("开始进行身份认证......"); 459 | 460 | //获取用户的输入的账号. 461 | String userName = (String) token.getPrincipal(); 462 | 463 | //通过username从数据库中查找 User对象. 464 | //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法 465 | User user = iUserService.findByUsername(userName); 466 | if (Objects.isNull(user)) { 467 | return null; 468 | } 469 | 470 | return new SimpleAuthenticationInfo( 471 | // 这里传入的是user对象,比对的是用户名,直接传入用户名也没错,但是在授权部分就需要自己重新从数据库里取权限 472 | user, 473 | // 密码 474 | user.getPassword(), 475 | // salt = username + salt 476 | ByteSource.Util.bytes(user.getCredentialsSalt()), 477 | // realm name 478 | getName() 479 | ); 480 | } 481 | 482 | } 483 | 484 | ``` 485 | 6、创建ShiroConfig 486 | ```java 487 | package com.leigq.www.shiro.config; 488 | 489 | import lombok.Data; 490 | import org.apache.shiro.authc.credential.HashedCredentialsMatcher; 491 | import org.apache.shiro.mgt.SecurityManager; 492 | import org.apache.shiro.session.mgt.SessionManager; 493 | import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator; 494 | import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; 495 | import org.apache.shiro.spring.web.ShiroFilterFactoryBean; 496 | import org.apache.shiro.web.mgt.DefaultWebSecurityManager; 497 | import org.apache.shiro.web.servlet.SimpleCookie; 498 | import org.crazycake.shiro.RedisCacheManager; 499 | import org.crazycake.shiro.RedisManager; 500 | import org.crazycake.shiro.RedisSessionDAO; 501 | import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; 502 | import org.springframework.boot.context.properties.ConfigurationProperties; 503 | import org.springframework.context.annotation.Bean; 504 | import org.springframework.context.annotation.Configuration; 505 | 506 | import java.time.Duration; 507 | import java.util.LinkedHashMap; 508 | import java.util.Map; 509 | 510 | /** 511 | * @author :leigq 512 | * @date :2019/6/28 16:53 513 | * @description:shiro配置 514 | */ 515 | @Configuration 516 | @ConfigurationProperties( 517 | prefix = "spring.redis" 518 | ) 519 | @Data 520 | public class ShiroConfig { 521 | 522 | private String host = "localhost"; 523 | private int port = 6379; 524 | private String password; 525 | private Duration timeout; 526 | 527 | /** 528 | * Filter工厂,设置对应的过滤条件和跳转条件 529 | * create by: leigq 530 | * create time: 2019/7/3 14:29 531 | * 532 | * @return ShiroFilterFactoryBean 533 | */ 534 | @Bean 535 | public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { 536 | 537 | ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); 538 | shiroFilterFactoryBean.setSecurityManager(securityManager); 539 | 540 | // 过滤器链定义映射 541 | Map filterChainDefinitionMap = new LinkedHashMap<>(); 542 | 543 | /* 544 | * anon:所有url都都可以匿名访问,authc:所有url都必须认证通过才可以访问; 545 | * 过滤链定义,从上向下顺序执行,authc 应放在 anon 下面 546 | * */ 547 | filterChainDefinitionMap.put("/login", "anon"); 548 | // 配置不会被拦截的链接 顺序判断,因为前端模板采用了thymeleaf,这里不能直接使用 ("/static/**", "anon")来配置匿名访问,必须配置到每个静态目录 549 | filterChainDefinitionMap.put("/css/**", "anon"); 550 | filterChainDefinitionMap.put("/fonts/**", "anon"); 551 | filterChainDefinitionMap.put("/img/**", "anon"); 552 | filterChainDefinitionMap.put("/js/**", "anon"); 553 | filterChainDefinitionMap.put("/html/**", "anon"); 554 | // 所有url都必须认证通过才可以访问 555 | filterChainDefinitionMap.put("/**", "authc"); 556 | 557 | // 配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了, 位置放在 anon、authc下面 558 | filterChainDefinitionMap.put("/logout", "logout"); 559 | 560 | // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面 561 | // 配器shirot认登录累面地址,前后端分离中登录累面跳转应由前端路由控制,后台仅返回json数据, 对应LoginController中unauth请求 562 | shiroFilterFactoryBean.setLoginUrl("/un_auth"); 563 | 564 | // 登录成功后要跳转的链接, 此项目是前后端分离,故此行注释掉,登录成功之后返回用户基本信息及token给前端 565 | // shiroFilterFactoryBean.setSuccessUrl("/index"); 566 | 567 | // 未授权界面, 对应LoginController中 unauthorized 请求 568 | shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized"); 569 | shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); 570 | return shiroFilterFactoryBean; 571 | } 572 | 573 | 574 | /** 575 | * 凭证匹配器(由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了) 576 | * create by: leigq 577 | * create time: 2019/7/3 14:30 578 | * 579 | * @return HashedCredentialsMatcher 580 | */ 581 | @Bean 582 | public HashedCredentialsMatcher hashedCredentialsMatcher() { 583 | HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); 584 | // 散列算法:这里使用MD5算法; 585 | hashedCredentialsMatcher.setHashAlgorithmName("md5"); 586 | // 散列的次数,比如散列两次,相当于 md5(md5("")); 587 | hashedCredentialsMatcher.setHashIterations(2); 588 | return hashedCredentialsMatcher; 589 | } 590 | 591 | /** 592 | * 将自己的验证方式加入容器 593 | * create by: leigq 594 | * create time: 2019/7/3 14:30 595 | * 596 | * @return MyShiroRealm 597 | */ 598 | @Bean 599 | public MyShiroRealm myShiroRealm() { 600 | MyShiroRealm myShiroRealm = new MyShiroRealm(); 601 | myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); 602 | return myShiroRealm; 603 | } 604 | 605 | /** 606 | * RedisSessionDAO shiro sessionDao层的实现 通过redis, 使用的是shiro-redis开源插件 607 | * create by: leigq 608 | * create time: 2019/7/3 14:30 609 | * 610 | * @return RedisSessionDAO 611 | */ 612 | @Bean 613 | public RedisSessionDAO redisSessionDAO() { 614 | RedisSessionDAO redisSessionDAO = new RedisSessionDAO(); 615 | redisSessionDAO.setRedisManager(redisManager()); 616 | redisSessionDAO.setSessionIdGenerator(sessionIdGenerator()); 617 | redisSessionDAO.setExpire(1800); 618 | return redisSessionDAO; 619 | } 620 | 621 | /** 622 | * Session ID 生成器 623 | *
624 | * create by: leigq 625 | *
626 | * create time: 2019/7/3 16:08 627 | * 628 | * @return JavaUuidSessionIdGenerator 629 | */ 630 | @Bean 631 | public JavaUuidSessionIdGenerator sessionIdGenerator() { 632 | return new JavaUuidSessionIdGenerator(); 633 | } 634 | 635 | /** 636 | * 自定义sessionManager 637 | * create by: leigq 638 | * create time: 2019/7/3 14:31 639 | * 640 | * @return SessionManager 641 | */ 642 | @Bean 643 | public SessionManager sessionManager() { 644 | MySessionManager mySessionManager = new MySessionManager(); 645 | mySessionManager.setSessionDAO(redisSessionDAO()); 646 | return mySessionManager; 647 | } 648 | 649 | /** 650 | * 配置shiro redisManager, 使用的是shiro-redis开源插件 651 | *
652 | * create by: leigq 653 | *
654 | * create time: 2019/7/3 14:33 655 | * 656 | * @return RedisManager 657 | */ 658 | private RedisManager redisManager() { 659 | RedisManager redisManager = new RedisManager(); 660 | redisManager.setHost(host); 661 | redisManager.setPort(port); 662 | redisManager.setTimeout((int) timeout.toMillis()); 663 | redisManager.setPassword(password); 664 | return redisManager; 665 | } 666 | 667 | /** 668 | * cacheManager 缓存 redis实现, 使用的是shiro-redis开源插件 669 | *
670 | * create by: leigq 671 | *
672 | * create time: 2019/7/3 14:33 673 | * 674 | * @return RedisCacheManager 675 | */ 676 | @Bean 677 | public RedisCacheManager cacheManager() { 678 | RedisCacheManager redisCacheManager = new RedisCacheManager(); 679 | redisCacheManager.setRedisManager(redisManager()); 680 | // 必须要设置主键名称,shiro-redis 插件用过这个缓存用户信息 681 | redisCacheManager.setPrincipalIdFieldName("userId"); 682 | return redisCacheManager; 683 | } 684 | 685 | /** 686 | * create by: leigq 687 | * description: 权限管理,配置主要是Realm的管理认证 688 | * create time: 2019/7/1 10:09 689 | * 690 | * @return SecurityManager 691 | */ 692 | @Bean 693 | public SecurityManager securityManager() { 694 | DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); 695 | securityManager.setRealm(myShiroRealm()); 696 | // 自定义session管理 使用redis 697 | securityManager.setSessionManager(sessionManager()); 698 | // 自定义缓存实现 使用redis 699 | securityManager.setCacheManager(cacheManager()); 700 | return securityManager; 701 | } 702 | 703 | /* 704 | * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证 705 | * 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能 706 | */ 707 | @Bean 708 | public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() { 709 | DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); 710 | advisorAutoProxyCreator.setProxyTargetClass(true); 711 | return advisorAutoProxyCreator; 712 | } 713 | 714 | @Bean 715 | public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { 716 | AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); 717 | authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); 718 | return authorizationAttributeSourceAdvisor; 719 | } 720 | 721 | @Bean 722 | public SimpleCookie cookie() { 723 | // cookie的name,对应的默认是 JSESSIONID 724 | SimpleCookie cookie = new SimpleCookie("SHARE_JSESSIONID"); 725 | cookie.setHttpOnly(true); 726 | // path为 / 用于多个系统共享 JSESSIONID 727 | cookie.setPath("/"); 728 | return cookie; 729 | } 730 | 731 | /* 此项目使用 shiro 场景为前后端分离项目,这里先注释掉,统一异常处理已在 GlobalExceptionHand.java 中实现 */ 732 | /** 733 | * create by: leigq 734 | * description: 异常处理, 详见:https://www.cnblogs.com/libra0920/p/6289848.html 735 | * create time: 2019/7/1 10:28 736 | * @return SimpleMappingExceptionResolver 737 | */ 738 | // @Bean(name = "simpleMappingExceptionResolver") 739 | // public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() { 740 | // SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver(); 741 | // Properties mappings = new Properties(); 742 | // mappings.setProperty("DatabaseException", "databaseError");//数据库异常处理 743 | // mappings.setProperty("UnauthorizedException", "/user/403"); 744 | // r.setExceptionMappings(mappings); // None by default 745 | // r.setDefaultErrorView("error"); // No default 746 | // r.setExceptionAttribute("exception"); // Default is "exception" 747 | // //r.setWarnLogCategory("example.MvcLogger"); // No default 748 | // return r; 749 | // } 750 | } 751 | ``` 752 | 7、创建LoginController 753 | ```java 754 | package com.leigq.www.shiro.controller; 755 | 756 | import com.leigq.www.shiro.bean.CacheUser; 757 | import com.leigq.www.shiro.bean.Response; 758 | import com.leigq.www.shiro.domain.entity.User; 759 | import com.leigq.www.shiro.service.IUserService; 760 | import lombok.extern.slf4j.Slf4j; 761 | import org.apache.commons.lang3.StringUtils; 762 | import org.springframework.http.HttpStatus; 763 | import org.springframework.web.bind.annotation.GetMapping; 764 | import org.springframework.web.bind.annotation.PostMapping; 765 | import org.springframework.web.bind.annotation.RequestMapping; 766 | import org.springframework.web.bind.annotation.RestController; 767 | 768 | import javax.annotation.Resource; 769 | 770 | /** 771 | * @author :leigq 772 | * @date :2019/6/28 16:55 773 | * @description:登录Controller 774 | */ 775 | @Slf4j 776 | @RestController 777 | public class LoginController { 778 | 779 | @Resource 780 | private IUserService iUserService; 781 | 782 | @Resource 783 | private Response response; 784 | 785 | /** 786 | * create by: leigq 787 | * description: 登录 788 | * create time: 2019/6/28 17:11 789 | * 790 | * @return 登录结果 791 | */ 792 | @PostMapping("/login") 793 | public Response login(User user) { 794 | log.warn("进入登录....."); 795 | 796 | String userName = user.getUserName(); 797 | String password = user.getPassword(); 798 | 799 | if (StringUtils.isBlank(userName)) { 800 | return response.failure("用户名为空!"); 801 | } 802 | 803 | if (StringUtils.isBlank(password)) { 804 | return response.failure("密码为空!"); 805 | } 806 | 807 | CacheUser loginUser = iUserService.login(userName, password); 808 | // 登录成功返回用户信息 809 | return response.success("登录成功!", loginUser); 810 | } 811 | 812 | /** 813 | * create by: leigq 814 | * description: 登出 815 | * create time: 2019/6/28 17:37 816 | */ 817 | @GetMapping("/logout") 818 | public Response logOut() { 819 | iUserService.logout(); 820 | return response.success("登出成功!"); 821 | } 822 | 823 | /** 824 | * 未登录,shiro应重定向到登录界面,此处返回未登录状态信息由前端控制跳转页面 825 | *
826 | * create by: leigq 827 | *
828 | * create time: 2019/7/3 14:53 829 | * @return 830 | */ 831 | @RequestMapping("/un_auth") 832 | public Response unAuth() { 833 | return response.failure(HttpStatus.UNAUTHORIZED, "用户未登录!", null); 834 | } 835 | 836 | /** 837 | * 未授权,无权限,此处返回未授权状态信息由前端控制跳转页面 838 | *
839 | * create by: leigq 840 | *
841 | * create time: 2019/7/3 14:53 842 | * @return 843 | */ 844 | @RequestMapping("/unauthorized") 845 | public Response unauthorized() { 846 | return response.failure(HttpStatus.FORBIDDEN, "用户无权限!", null); 847 | } 848 | } 849 | 850 | ``` 851 | 8、具体登录方法 852 | ```java 853 | package com.leigq.www.shiro.service.impl; 854 | 855 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 856 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 857 | import com.leigq.www.shiro.bean.CacheUser; 858 | import com.leigq.www.shiro.domain.entity.User; 859 | import com.leigq.www.shiro.domain.mapper.UserMapper; 860 | import com.leigq.www.shiro.service.IUserService; 861 | import com.leigq.www.shiro.web.exception.LoginException; 862 | import lombok.extern.slf4j.Slf4j; 863 | import org.apache.shiro.SecurityUtils; 864 | import org.apache.shiro.authc.AuthenticationException; 865 | import org.apache.shiro.authc.IncorrectCredentialsException; 866 | import org.apache.shiro.authc.UnknownAccountException; 867 | import org.apache.shiro.authc.UsernamePasswordToken; 868 | import org.apache.shiro.subject.Subject; 869 | import org.springframework.beans.BeanUtils; 870 | import org.springframework.stereotype.Service; 871 | 872 | import java.util.List; 873 | 874 | /** 875 | *

876 | * 服务实现类 877 | *

878 | * 879 | * @author leigq 880 | * @since 2019-06-28 881 | */ 882 | @Service 883 | @Slf4j 884 | public class UserServiceImpl extends ServiceImpl implements IUserService { 885 | 886 | @Override 887 | public User findByUsername(String username) { 888 | return baseMapper.selectOne( 889 | new LambdaQueryWrapper().eq(User::getUserName, username) 890 | ); 891 | } 892 | 893 | @Override 894 | public CacheUser login(String userName, String password) { 895 | 896 | // 获取Subject实例对象,用户实例 897 | Subject currentUser = SecurityUtils.getSubject(); 898 | 899 | // 将用户名和密码封装到UsernamePasswordToken 900 | UsernamePasswordToken token = new UsernamePasswordToken(userName, password); 901 | 902 | CacheUser cacheUser; 903 | 904 | // 4、认证 905 | try { 906 | // 传到 MyShiroRealm 类中的方法进行认证 907 | currentUser.login(token); 908 | // 构建缓存用户信息返回给前端 909 | User user = (User) currentUser.getPrincipals().getPrimaryPrincipal(); 910 | cacheUser = CacheUser.builder() 911 | .token(currentUser.getSession().getId().toString()) 912 | .build(); 913 | BeanUtils.copyProperties(user, cacheUser); 914 | log.warn("CacheUser is {}", cacheUser.toString()); 915 | } catch (UnknownAccountException e) { 916 | log.error("账户不存在异常:", e); 917 | throw new LoginException("账号不存在!", e); 918 | } catch (IncorrectCredentialsException e) { 919 | log.error("凭据错误(密码错误)异常:", e); 920 | throw new LoginException("密码不正确!", e); 921 | } catch (AuthenticationException e) { 922 | log.error("身份验证异常:", e); 923 | throw new LoginException("用户验证失败!", e); 924 | } 925 | return cacheUser; 926 | } 927 | 928 | @Override 929 | public void logout() { 930 | Subject subject = SecurityUtils.getSubject(); 931 | subject.logout(); 932 | } 933 | 934 | @Override 935 | public List listUsers() { 936 | return baseMapper.selectList(new LambdaQueryWrapper<>()); 937 | } 938 | } 939 | ``` 940 | 上面我列出了项目中主要的几个类,大家可以对照着项目看,每个类中的注释已经写的很详细了。 941 | 942 | ## 使用及测试 943 | 944 | 我们配置每个接口的权限使用`@RequiresPermissions("user:view")`注解即可,其中`user:view`对应权限表中的权限。 945 | 946 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190706160507657.png) 947 | 948 | 1、登录测试 949 | 950 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190706160656648.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM0ODQ1Mzk0,size_16,color_FFFFFF,t_70) 951 | 952 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190706160825460.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM0ODQ1Mzk0,size_16,color_FFFFFF,t_70) 953 | 954 | 登录成功会将用户信息存入缓存。 955 | 956 | 2、请求查询用户接口 957 | 958 | 我们先输入错误的token试试 959 | 960 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190706160924408.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM0ODQ1Mzk0,size_16,color_FFFFFF,t_70) 961 | 962 | 我们再输入正确的token试试 963 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190706160956360.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM0ODQ1Mzk0,size_16,color_FFFFFF,t_70) 964 | 965 | 3、请求用户删除接口 966 | 967 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190706161155901.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM0ODQ1Mzk0,size_16,color_FFFFFF,t_70) 968 | 969 | 因为我们没有给此用户配置此权限,所以返回无权限 970 | 971 | 4、退出登录 972 | 973 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190706161535906.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM0ODQ1Mzk0,size_16,color_FFFFFF,t_70) 974 | 975 | 我们再请求用户列表接口 976 | 977 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190706161618536.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM0ODQ1Mzk0,size_16,color_FFFFFF,t_70) 978 | 979 | ## 感谢 980 | 981 | - [springboot整合shiro应用:thumbsup:](https://www.cnblogs.com/ll409546297/p/7815409.html) 982 | 983 | - [Springboot2.0 集成shiro权限管理:gift:](https://www.cnblogs.com/asker009/p/9471712.html) 984 | 985 | - [SpringBoot+Shiro+Redis共享Session入门小栗子:heart:](https://www.cnblogs.com/LUA123/p/9337963.html) 986 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/SpringBootShiroApplication.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro; 2 | 3 | import org.mybatis.spring.annotation.MapperScan; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.transaction.annotation.EnableTransactionManagement; 7 | 8 | @SpringBootApplication 9 | @EnableTransactionManagement 10 | @MapperScan("com.leigq.www.shiro.domain.mapper") 11 | public class SpringBootShiroApplication { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(SpringBootShiroApplication.class, args); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/bean/CacheUser.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.bean; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | import org.crazycake.shiro.AuthCachePrincipal; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | * 缓存用户信息 11 | *
12 | * @author :leigq 13 | * @date :2019/7/3 14:40 14 | */ 15 | @Data 16 | @Builder 17 | public class CacheUser implements Serializable { 18 | 19 | private static final long serialVersionUID = 1L; 20 | 21 | private String email; 22 | 23 | private String name; 24 | 25 | private Integer state; 26 | 27 | private String userName; 28 | 29 | private String token; 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/bean/Response.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.bean; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.Getter; 6 | import org.springframework.context.annotation.Scope; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.stereotype.Component; 9 | 10 | /** 11 | * 响应对象。包含处理结果(Meta)和返回数据(Data)两部分,在 Controller 处理完请求后将此对象转换成 json 返回给前台。注意: 12 | *
    13 | *
  • 处理成功一般返回处理结果和返回数据,失败只返回处理结果。具体返回什么需看接口文档。
  • 14 | *
  • 处理成功结果码一般是200,失败码具体看出了什么错,对照 HTTP 响应码填。
  • 15 | *
  • 默认处理方法慎用,前台最想要拿到的还是具体的结果码和信息。
  • 16 | *
17 | *

18 | * @author :leigq
19 | * 创建时间:2017年10月9日 下午3:26:17
20 | *

21 | * 修改人:
22 | * 修改时间:
23 | * 修改备注:
24 | *

25 | */ 26 | @Component 27 | @Scope("prototype") 28 | @SuppressWarnings(value = "all") 29 | public class Response { 30 | 31 | /** 32 | * 默认成功响应码 33 | */ 34 | private static final Integer DEAFAULT_SUCCESS_CODE = HttpStatus.OK.value(); 35 | /** 36 | * 默认成功响应信息 37 | */ 38 | private static final String DEAFAULT_SUCCESS_MSG = "请求/处理成功!"; 39 | /** 40 | * 默认失败响应码 41 | */ 42 | private static final Integer DEAFAULT_FAILURE_CODE = HttpStatus.INTERNAL_SERVER_ERROR.value(); 43 | /** 44 | * 默认失败响应信息 45 | */ 46 | private static final String DEAFAULT_FAILURE_MSG = "请求/处理失败!"; 47 | 48 | @Getter 49 | private Meta meta; 50 | 51 | @Getter 52 | private Object data; 53 | 54 | 55 | /*↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓成功↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/ 56 | 57 | /** 58 | * 处理成功响应,返回自定义响应码、信息和数据。 59 | * 60 | * @param msg 处理结果信息 61 | * @return 响应对象 62 | *

63 | * @author :LeiGQ
64 | * @date :2019-05-20 15:25
65 | */ 66 | public Response success(String msg) { 67 | this.meta = new Meta(DEAFAULT_SUCCESS_CODE, msg); 68 | this.data = null; 69 | return this; 70 | } 71 | 72 | /** 73 | * 处理成功响应,返回自定义响应码、信息和数据。 74 | * 75 | * @param data 返回数据 76 | * @return 响应对象 77 | *

78 | * @author :LeiGQ
79 | * @date :2019-05-20 15:25
80 | */ 81 | public Response success(Object data) { 82 | this.meta = new Meta(DEAFAULT_SUCCESS_CODE, DEAFAULT_SUCCESS_MSG); 83 | this.data = data; 84 | return this; 85 | } 86 | 87 | /** 88 | * 处理成功响应,返回自定义响应码、信息和数据。 89 | * 90 | * @param msg 处理结果信息 91 | * @param data 返回数据 92 | * @return 响应对象 93 | *

94 | * @author :LeiGQ
95 | * @date :2019-05-20 15:25
96 | */ 97 | public Response success(String msg, Object data) { 98 | this.meta = new Meta(DEAFAULT_SUCCESS_CODE, msg); 99 | this.data = data; 100 | return this; 101 | } 102 | 103 | /** 104 | * 处理成功响应,返回自定义响应码、信息和数据。 105 | * 106 | * @param httpStatus HTTP 响应码 107 | * @param msg 处理结果信息 108 | * @param data 返回数据 109 | * @return 响应对象 110 | *

111 | * @author :LeiGQ
112 | * @date :2019-05-20 15:25
113 | */ 114 | public Response success(HttpStatus httpStatus, String msg, Object data) { 115 | this.meta = new Meta(httpStatus.value(), msg); 116 | this.data = data; 117 | return this; 118 | } 119 | 120 | 121 | /*↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓失败↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/ 122 | 123 | /** 124 | * 处理失败响应,返回自定义响应码、信息和数据。 125 | * 126 | * @param msg 处理结果信息 127 | * @return 响应对象 128 | *

129 | * @author :LeiGQ
130 | * @date :2019-05-20 15:22
131 | */ 132 | public Response failure(String msg) { 133 | this.meta = new Meta(DEAFAULT_FAILURE_CODE, msg); 134 | this.data = null; 135 | return this; 136 | } 137 | 138 | /** 139 | * 处理失败响应,返回自定义响应码、信息和数据。 140 | * 141 | * @param data 返回数据 142 | * @return 响应对象 143 | *

144 | * @author :LeiGQ
145 | * @date :2019-05-20 15:22
146 | */ 147 | public Response failure(Object data) { 148 | this.meta = new Meta(DEAFAULT_FAILURE_CODE, DEAFAULT_FAILURE_MSG); 149 | this.data = data; 150 | return this; 151 | } 152 | 153 | /** 154 | * 处理失败响应,返回自定义响应码、信息和数据。 155 | * 156 | * @param msg 处理结果信息 157 | * @param data 返回数据 158 | * @return 响应对象 159 | *

160 | * @author :LeiGQ
161 | * @date :2019-05-20 15:22
162 | */ 163 | public Response failure(String msg, Object data) { 164 | this.meta = new Meta(DEAFAULT_FAILURE_CODE, msg); 165 | this.data = data; 166 | return this; 167 | } 168 | 169 | /** 170 | * 处理失败响应,返回自定义响应码、信息和数据。 171 | * 172 | * @param httpStatus HTTP 响应码 173 | * @param msg 处理结果信息 174 | * @param data 返回数据 175 | * @return 响应对象 176 | *

177 | * @author :LeiGQ
178 | * @date :2019-05-20 15:22
179 | */ 180 | public Response failure(HttpStatus httpStatus, String msg, Object data) { 181 | this.meta = new Meta(httpStatus.value(), msg); 182 | this.data = data; 183 | return this; 184 | } 185 | 186 | /** 187 | * 元数据,包含响应码和信息。 188 | *

189 | * 创建人:袁炜林
190 | * 创建时间:2017年10月9日 下午3:31:17
191 | *

192 | * 修改人:
193 | * 修改时间:
194 | * 修改备注:
195 | *

196 | */ 197 | @Data 198 | @AllArgsConstructor 199 | private class Meta { 200 | 201 | /** 202 | * 处理结果代码,与 HTTP 状态响应码对应 203 | */ 204 | private Integer code; 205 | 206 | /** 207 | * 处理结果信息 208 | */ 209 | private String msg; 210 | } 211 | 212 | } 213 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/config/DruidDataSourceConfig.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.config; 2 | 3 | import com.alibaba.druid.pool.DruidDataSource; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.annotation.Primary; 9 | 10 | import javax.sql.DataSource; 11 | import java.sql.SQLException; 12 | 13 | /** 14 | * Druid数据源配置 15 | *
16 | * 简介: 17 | *

Druid是一个非常优秀的数据库连接池。在功能、性能、扩展性方面,都超过其他数据库连接池,包括DBCP、C3P0、BoneCP、Proxool、JBoss DataSource。

18 | *

Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。

19 | *

Druid是一个JDBC组件,它包括三个部分:

20 | *
    21 | *
  • 基于Filter-Chain模式的插件体系。
  • 22 | *
  • DruidDataSource 高效可管理的数据库连接池。
  • 23 | *
  • SQLParser
  • 24 | *
25 | *

26 | * 更多详细说明点这个 27 | *
28 | * 具体实现参考 29 | *

30 | *

31 | * 创建人:leigq
32 | * 创建时间:2018-11-10 13:39
33 | *

34 | * 修改人:
35 | * 修改时间:
36 | * 修改备注:
37 | *

38 | */ 39 | @Configuration 40 | @Slf4j 41 | public class DruidDataSourceConfig { 42 | 43 | @Value("${spring.datasource.url}") 44 | private String dbUrl; 45 | 46 | @Value("${spring.datasource.username}") 47 | private String username; 48 | 49 | @Value("${spring.datasource.password}") 50 | private String password; 51 | 52 | @Value("${spring.datasource.driver-class-name}") 53 | private String driverClassName; 54 | 55 | @Value("${spring.datasource.initialSize}") 56 | private int initialSize; 57 | 58 | @Value("${spring.datasource.minIdle}") 59 | private int minIdle; 60 | 61 | @Value("${spring.datasource.maxActive}") 62 | private int maxActive; 63 | 64 | @Value("${spring.datasource.maxWait}") 65 | private int maxWait; 66 | 67 | @Value("${spring.datasource.timeBetweenEvictionRunsMillis}") 68 | private int timeBetweenEvictionRunsMillis; 69 | 70 | @Value("${spring.datasource.minEvictableIdleTimeMillis}") 71 | private int minEvictableIdleTimeMillis; 72 | 73 | @Value("${spring.datasource.validationQuery}") 74 | private String validationQuery; 75 | 76 | @Value("${spring.datasource.testWhileIdle}") 77 | private boolean testWhileIdle; 78 | 79 | @Value("${spring.datasource.testOnBorrow}") 80 | private boolean testOnBorrow; 81 | 82 | @Value("${spring.datasource.testOnReturn}") 83 | private boolean testOnReturn; 84 | 85 | @Value("${spring.datasource.poolPreparedStatements}") 86 | private boolean poolPreparedStatements; 87 | 88 | @Value("${spring.datasource.maxPoolPreparedStatementPerConnectionSize}") 89 | private int maxPoolPreparedStatementPerConnectionSize; 90 | 91 | @Value("${spring.datasource.filters}") 92 | private String filters; 93 | 94 | @Value("${spring.datasource.connectionProperties}") 95 | private String connectionProperties; 96 | 97 | @Bean 98 | @Primary //在同样的DataSource中,首先使用被标注的DataSource 99 | public DataSource getDataSource() { 100 | DruidDataSource datasource = new DruidDataSource(); 101 | datasource.setUrl(this.dbUrl); 102 | datasource.setUsername(username); 103 | datasource.setPassword(password); 104 | datasource.setDriverClassName(driverClassName); 105 | 106 | //configuration 107 | datasource.setInitialSize(initialSize); 108 | datasource.setMinIdle(minIdle); 109 | datasource.setMaxActive(maxActive); 110 | datasource.setMaxWait(maxWait); 111 | datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); 112 | datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); 113 | datasource.setValidationQuery(validationQuery); 114 | datasource.setTestWhileIdle(testWhileIdle); 115 | datasource.setTestOnBorrow(testOnBorrow); 116 | datasource.setTestOnReturn(testOnReturn); 117 | datasource.setPoolPreparedStatements(poolPreparedStatements); 118 | datasource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize); 119 | try { 120 | datasource.setFilters(filters); 121 | } catch (SQLException e) { 122 | log.error("druid配置初始化失败", e); 123 | } 124 | datasource.setConnectionProperties(connectionProperties); 125 | 126 | return datasource; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/config/DruidMonitorConfig.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.config; 2 | 3 | import com.alibaba.druid.support.http.StatViewServlet; 4 | import com.alibaba.druid.support.http.WebStatFilter; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.boot.web.servlet.FilterRegistrationBean; 7 | import org.springframework.boot.web.servlet.ServletRegistrationBean; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | /** 12 | * Druid监控配置,使用基于Java代码实现的servlet和filter 13 | *

14 | * 具体实现参考 15 | *
16 | * 基于注解的配置StatView的Servlet 17 | *

18 | *

19 | * 创建人:leigq
20 | * 创建时间:2018-11-10 13:43
21 | *

22 | * 修改人:
23 | * 修改时间:
24 | * 修改备注:
25 | *

26 | */ 27 | @Configuration 28 | @Slf4j 29 | public class DruidMonitorConfig { 30 | 31 | /** 32 | * 注册ServletRegistrationBean 33 | * @return 34 | */ 35 | @Bean 36 | public ServletRegistrationBean registrationBean() { 37 | //org.springframework.boot.context.embedded.ServletRegistrationBean提供类的进行注册. 38 | ServletRegistrationBean bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*"); 39 | /** 初始化参数配置,initParams**/ 40 | //白名单 41 | bean.addInitParameter("allow", "127.0.0.1");//多个ip逗号隔开 42 | //IP黑名单 (存在共同时,deny优先于allow) : 如果满足deny的话提示:Sorry, you are not permitted to view this page. 43 | //bean.addInitParameter("deny", "192.168.1.110"); 44 | //登录查看信息的账号密码. 45 | bean.addInitParameter("loginUsername", "admin"); 46 | bean.addInitParameter("loginPassword", "123456"); 47 | //是否能够重置数据. 48 | bean.addInitParameter("resetEnable", "false"); 49 | return bean; 50 | } 51 | 52 | /** 53 | * 注册FilterRegistrationBean 54 | * @return 55 | */ 56 | @Bean 57 | public FilterRegistrationBean druidStatFilter() { 58 | FilterRegistrationBean bean = new FilterRegistrationBean<>(new WebStatFilter()); 59 | //添加过滤规则. 60 | bean.addUrlPatterns("/*"); 61 | //添加不需要忽略的格式信息. 62 | bean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"); 63 | return bean; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/config/MyBatisPlusConfig.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.config; 2 | 3 | import com.baomidou.mybatisplus.core.parser.ISqlParser; 4 | import com.baomidou.mybatisplus.extension.parsers.BlockAttackSqlParser; 5 | import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor; 6 | import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor; 7 | import com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.context.annotation.Profile; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | /** 16 | * MyBatisPlus 配置 17 | *

18 | * 创建人:LeiGQ
19 | * 创建时间:2019-06-14 10:40
20 | *

21 | * 修改人:
22 | * 修改时间:
23 | * 修改备注:
24 | *

25 | */ 26 | @Configuration 27 | public class MyBatisPlusConfig { 28 | 29 | /** 30 | * 分页插件 31 | * @author :LeiGQ
32 | * @date :2019-06-14 10:43
33 | *

34 | */ 35 | @Bean 36 | public PaginationInterceptor paginationInterceptor() { 37 | PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); 38 | List sqlParserList = new ArrayList<>(); 39 | // 攻击 SQL 阻断解析器、加入解析链 作用!阻止恶意的全表更新删除 40 | sqlParserList.add(new BlockAttackSqlParser()); 41 | paginationInterceptor.setSqlParserList(sqlParserList); 42 | return paginationInterceptor; 43 | } 44 | 45 | /** 46 | * SQL执行效率插件 设置 dev test 环境开启 47 | * @author :LeiGQ
48 | * @date :2019-06-14 10:46
49 | *

50 | */ 51 | @Bean 52 | @Profile({"dev","test"}) 53 | public PerformanceInterceptor performanceInterceptor() { 54 | final PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor(); 55 | performanceInterceptor.setFormat(true); 56 | performanceInterceptor.setMaxTime(3000); 57 | return new PerformanceInterceptor(); 58 | } 59 | 60 | /** 61 | * 乐观锁 62 | * @author :LeiGQ
63 | * @date :2019-06-14 10:55
64 | *

65 | */ 66 | @Bean 67 | public OptimisticLockerInterceptor optimisticLockerInterceptor() { 68 | return new OptimisticLockerInterceptor(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/config/MySessionManager.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.config; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.apache.shiro.web.servlet.ShiroHttpServletRequest; 5 | import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; 6 | import org.apache.shiro.web.util.WebUtils; 7 | 8 | import javax.servlet.ServletRequest; 9 | import javax.servlet.ServletResponse; 10 | import java.io.Serializable; 11 | 12 | /** 13 | * 自定义session管理 14 | *
15 | * 传统结构项目中,shiro从cookie中读取sessionId以此来维持会话,在前后端分离的项目中(也可在移动APP项目使用), 16 | * 我们选择在ajax的请求头中传递sessionId,因此需要重写shiro获取sessionId的方式。 17 | * 自定义MySessionManager类继承DefaultWebSessionManager类,重写getSessionId方法 18 | * @author :leigq 19 | * @date :2019/7/1 10:52 20 | */ 21 | public class MySessionManager extends DefaultWebSessionManager { 22 | 23 | private static final String AUTHORIZATION = "Authorization"; 24 | 25 | private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request"; 26 | 27 | @Override 28 | protected Serializable getSessionId(ServletRequest request, ServletResponse response) { 29 | String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION); 30 | //如果请求头中有 Authorization 则其值为sessionId 31 | if (!StringUtils.isEmpty(id)) { 32 | request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE); 33 | request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id); 34 | request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE); 35 | return id; 36 | } else { 37 | //否则按默认规则从cookie取sessionId 38 | return super.getSessionId(request, response); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/config/MyShiroRealm.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.config; 2 | 3 | import com.leigq.www.shiro.domain.entity.Permission; 4 | import com.leigq.www.shiro.domain.entity.Role; 5 | import com.leigq.www.shiro.domain.entity.User; 6 | import com.leigq.www.shiro.service.IPermissionService; 7 | import com.leigq.www.shiro.service.IRoleService; 8 | import com.leigq.www.shiro.service.IUserService; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.apache.shiro.authc.AuthenticationException; 11 | import org.apache.shiro.authc.AuthenticationInfo; 12 | import org.apache.shiro.authc.AuthenticationToken; 13 | import org.apache.shiro.authc.SimpleAuthenticationInfo; 14 | import org.apache.shiro.authz.AuthorizationInfo; 15 | import org.apache.shiro.authz.SimpleAuthorizationInfo; 16 | import org.apache.shiro.realm.AuthorizingRealm; 17 | import org.apache.shiro.subject.PrincipalCollection; 18 | import org.apache.shiro.util.ByteSource; 19 | 20 | import javax.annotation.Resource; 21 | import java.util.List; 22 | import java.util.Objects; 23 | 24 | /** 25 | * @author :leigq 26 | * @date :2019/6/28 16:31 27 | * @description:自定义 shiroRealm, 主要是重写其认证、授权 28 | */ 29 | @Slf4j 30 | public class MyShiroRealm extends AuthorizingRealm { 31 | 32 | @Resource 33 | private IUserService iUserService; 34 | 35 | @Resource 36 | private IRoleService iRoleService; 37 | 38 | @Resource 39 | private IPermissionService iPermissionService; 40 | 41 | 42 | /** 43 | * create by: leigq 44 | * description: 授权 45 | * create time: 2019/7/1 10:32 46 | * 47 | * @return 权限信息,包括角色以及权限 48 | */ 49 | @Override 50 | protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { 51 | log.warn("开始执行授权操作......."); 52 | 53 | SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); 54 | //如果身份认证的时候没有传入User对象,这里只能取到userName 55 | //也就是SimpleAuthenticationInfo构造的时候第一个参数传递需要User对象 56 | User user = (User) principals.getPrimaryPrincipal(); 57 | 58 | // 查询用户角色,一个用户可能有多个角色 59 | List roles = iRoleService.getUserRoles(user.getUserId()); 60 | 61 | for (Role role : roles) { 62 | authorizationInfo.addRole(role.getRole()); 63 | // 根据角色查询权限 64 | List permissions = iPermissionService.getRolePermissions(role.getRoleId()); 65 | for (Permission p : permissions) { 66 | authorizationInfo.addStringPermission(p.getPermission()); 67 | } 68 | } 69 | return authorizationInfo; 70 | } 71 | 72 | /** 73 | * create by: leigq 74 | * description: 主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。 75 | * create time: 2019/7/1 09:04 76 | * 77 | * @return 身份验证信息 78 | */ 79 | @Override 80 | protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { 81 | log.warn("开始进行身份认证......"); 82 | 83 | //获取用户的输入的账号. 84 | String userName = (String) token.getPrincipal(); 85 | 86 | //通过username从数据库中查找 User对象. 87 | //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法 88 | User user = iUserService.findByUsername(userName); 89 | if (Objects.isNull(user)) { 90 | return null; 91 | } 92 | 93 | return new SimpleAuthenticationInfo( 94 | // 这里传入的是user对象,比对的是用户名,直接传入用户名也没错,但是在授权部分就需要自己重新从数据库里取权限 95 | user, 96 | // 密码 97 | user.getPassword(), 98 | // salt = username + salt 99 | ByteSource.Util.bytes(user.getCredentialsSalt()), 100 | // realm name 101 | getName() 102 | ); 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/config/ShiroConfig.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.config; 2 | 3 | import lombok.Data; 4 | import org.apache.shiro.authc.credential.HashedCredentialsMatcher; 5 | import org.apache.shiro.mgt.SecurityManager; 6 | import org.apache.shiro.session.mgt.SessionManager; 7 | import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator; 8 | import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; 9 | import org.apache.shiro.spring.web.ShiroFilterFactoryBean; 10 | import org.apache.shiro.web.mgt.DefaultWebSecurityManager; 11 | import org.apache.shiro.web.servlet.SimpleCookie; 12 | import org.crazycake.shiro.RedisCacheManager; 13 | import org.crazycake.shiro.RedisManager; 14 | import org.crazycake.shiro.RedisSessionDAO; 15 | import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; 16 | import org.springframework.boot.context.properties.ConfigurationProperties; 17 | import org.springframework.context.annotation.Bean; 18 | import org.springframework.context.annotation.Configuration; 19 | 20 | import java.time.Duration; 21 | import java.util.LinkedHashMap; 22 | import java.util.Map; 23 | 24 | /** 25 | * @author :leigq 26 | * @date :2019/6/28 16:53 27 | * @description:shiro配置 28 | */ 29 | @Configuration 30 | @ConfigurationProperties( 31 | prefix = "spring.redis" 32 | ) 33 | @Data 34 | public class ShiroConfig { 35 | 36 | private String host = "localhost"; 37 | private int port = 6379; 38 | private String password; 39 | private Duration timeout; 40 | 41 | /** 42 | * Filter工厂,设置对应的过滤条件和跳转条件 43 | * create by: leigq 44 | * create time: 2019/7/3 14:29 45 | * 46 | * @return ShiroFilterFactoryBean 47 | */ 48 | @Bean 49 | public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { 50 | 51 | ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); 52 | shiroFilterFactoryBean.setSecurityManager(securityManager); 53 | 54 | // 过滤器链定义映射 55 | Map filterChainDefinitionMap = new LinkedHashMap<>(); 56 | 57 | /* 58 | * anon:所有url都都可以匿名访问,authc:所有url都必须认证通过才可以访问; 59 | * 过滤链定义,从上向下顺序执行,authc 应放在 anon 下面 60 | * */ 61 | filterChainDefinitionMap.put("/login", "anon"); 62 | // 配置不会被拦截的链接 顺序判断,因为前端模板采用了thymeleaf,这里不能直接使用 ("/static/**", "anon")来配置匿名访问,必须配置到每个静态目录 63 | filterChainDefinitionMap.put("/css/**", "anon"); 64 | filterChainDefinitionMap.put("/fonts/**", "anon"); 65 | filterChainDefinitionMap.put("/img/**", "anon"); 66 | filterChainDefinitionMap.put("/js/**", "anon"); 67 | filterChainDefinitionMap.put("/html/**", "anon"); 68 | // 所有url都必须认证通过才可以访问 69 | filterChainDefinitionMap.put("/**", "authc"); 70 | 71 | // 配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了, 位置放在 anon、authc下面 72 | filterChainDefinitionMap.put("/logout", "logout"); 73 | 74 | // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面 75 | // 配器shirot认登录累面地址,前后端分离中登录累面跳转应由前端路由控制,后台仅返回json数据, 对应LoginController中unauth请求 76 | shiroFilterFactoryBean.setLoginUrl("/un_auth"); 77 | 78 | // 登录成功后要跳转的链接, 此项目是前后端分离,故此行注释掉,登录成功之后返回用户基本信息及token给前端 79 | // shiroFilterFactoryBean.setSuccessUrl("/index"); 80 | 81 | // 未授权界面, 对应LoginController中 unauthorized 请求 82 | shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized"); 83 | shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); 84 | return shiroFilterFactoryBean; 85 | } 86 | 87 | 88 | /** 89 | * 凭证匹配器(由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了) 90 | * create by: leigq 91 | * create time: 2019/7/3 14:30 92 | * 93 | * @return HashedCredentialsMatcher 94 | */ 95 | @Bean 96 | public HashedCredentialsMatcher hashedCredentialsMatcher() { 97 | HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); 98 | // 散列算法:这里使用MD5算法; 99 | hashedCredentialsMatcher.setHashAlgorithmName("md5"); 100 | // 散列的次数,比如散列两次,相当于 md5(md5("")); 101 | hashedCredentialsMatcher.setHashIterations(2); 102 | return hashedCredentialsMatcher; 103 | } 104 | 105 | /** 106 | * 将自己的验证方式加入容器 107 | * create by: leigq 108 | * create time: 2019/7/3 14:30 109 | * 110 | * @return MyShiroRealm 111 | */ 112 | @Bean 113 | public MyShiroRealm myShiroRealm() { 114 | MyShiroRealm myShiroRealm = new MyShiroRealm(); 115 | myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); 116 | return myShiroRealm; 117 | } 118 | 119 | /** 120 | * RedisSessionDAO shiro sessionDao层的实现 通过redis, 使用的是shiro-redis开源插件 121 | * create by: leigq 122 | * create time: 2019/7/3 14:30 123 | * 124 | * @return RedisSessionDAO 125 | */ 126 | @Bean 127 | public RedisSessionDAO redisSessionDAO() { 128 | RedisSessionDAO redisSessionDAO = new RedisSessionDAO(); 129 | redisSessionDAO.setRedisManager(redisManager()); 130 | redisSessionDAO.setSessionIdGenerator(sessionIdGenerator()); 131 | redisSessionDAO.setExpire(1800); 132 | return redisSessionDAO; 133 | } 134 | 135 | /** 136 | * Session ID 生成器 137 | *
138 | * create by: leigq 139 | *
140 | * create time: 2019/7/3 16:08 141 | * 142 | * @return JavaUuidSessionIdGenerator 143 | */ 144 | @Bean 145 | public JavaUuidSessionIdGenerator sessionIdGenerator() { 146 | return new JavaUuidSessionIdGenerator(); 147 | } 148 | 149 | /** 150 | * 自定义sessionManager 151 | * create by: leigq 152 | * create time: 2019/7/3 14:31 153 | * 154 | * @return SessionManager 155 | */ 156 | @Bean 157 | public SessionManager sessionManager() { 158 | MySessionManager mySessionManager = new MySessionManager(); 159 | mySessionManager.setSessionDAO(redisSessionDAO()); 160 | return mySessionManager; 161 | } 162 | 163 | /** 164 | * 配置shiro redisManager, 使用的是shiro-redis开源插件 165 | *
166 | * create by: leigq 167 | *
168 | * create time: 2019/7/3 14:33 169 | * 170 | * @return RedisManager 171 | */ 172 | private RedisManager redisManager() { 173 | RedisManager redisManager = new RedisManager(); 174 | redisManager.setHost(host); 175 | redisManager.setPort(port); 176 | redisManager.setTimeout((int) timeout.toMillis()); 177 | redisManager.setPassword(password); 178 | return redisManager; 179 | } 180 | 181 | /** 182 | * cacheManager 缓存 redis实现, 使用的是shiro-redis开源插件 183 | *
184 | * create by: leigq 185 | *
186 | * create time: 2019/7/3 14:33 187 | * 188 | * @return RedisCacheManager 189 | */ 190 | @Bean 191 | public RedisCacheManager cacheManager() { 192 | RedisCacheManager redisCacheManager = new RedisCacheManager(); 193 | redisCacheManager.setRedisManager(redisManager()); 194 | // 必须要设置主键名称,shiro-redis 插件用过这个缓存用户信息 195 | redisCacheManager.setPrincipalIdFieldName("userId"); 196 | return redisCacheManager; 197 | } 198 | 199 | /** 200 | * create by: leigq 201 | * description: 权限管理,配置主要是Realm的管理认证 202 | * create time: 2019/7/1 10:09 203 | * 204 | * @return SecurityManager 205 | */ 206 | @Bean 207 | public SecurityManager securityManager() { 208 | DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); 209 | securityManager.setRealm(myShiroRealm()); 210 | // 自定义session管理 使用redis 211 | securityManager.setSessionManager(sessionManager()); 212 | // 自定义缓存实现 使用redis 213 | securityManager.setCacheManager(cacheManager()); 214 | return securityManager; 215 | } 216 | 217 | /* 218 | * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证 219 | * 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能 220 | */ 221 | @Bean 222 | public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() { 223 | DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); 224 | advisorAutoProxyCreator.setProxyTargetClass(true); 225 | return advisorAutoProxyCreator; 226 | } 227 | 228 | @Bean 229 | public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { 230 | AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); 231 | authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); 232 | return authorizationAttributeSourceAdvisor; 233 | } 234 | 235 | @Bean 236 | public SimpleCookie cookie() { 237 | // cookie的name,对应的默认是 JSESSIONID 238 | SimpleCookie cookie = new SimpleCookie("SHARE_JSESSIONID"); 239 | cookie.setHttpOnly(true); 240 | // path为 / 用于多个系统共享 JSESSIONID 241 | cookie.setPath("/"); 242 | return cookie; 243 | } 244 | 245 | /* 此项目使用 shiro 场景为前后端分离项目,这里先注释掉,统一异常处理已在 GlobalExceptionHand.java 中实现 */ 246 | /** 247 | * create by: leigq 248 | * description: 异常处理, 详见:https://www.cnblogs.com/libra0920/p/6289848.html 249 | * create time: 2019/7/1 10:28 250 | * @return SimpleMappingExceptionResolver 251 | */ 252 | // @Bean(name = "simpleMappingExceptionResolver") 253 | // public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() { 254 | // SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver(); 255 | // Properties mappings = new Properties(); 256 | // mappings.setProperty("DatabaseException", "databaseError");//数据库异常处理 257 | // mappings.setProperty("UnauthorizedException", "/user/403"); 258 | // r.setExceptionMappings(mappings); // None by default 259 | // r.setDefaultErrorView("error"); // No default 260 | // r.setExceptionAttribute("exception"); // Default is "exception" 261 | // //r.setWarnLogCategory("example.MvcLogger"); // No default 262 | // return r; 263 | // } 264 | } -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/controller/LoginController.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.controller; 2 | 3 | import com.leigq.www.shiro.bean.CacheUser; 4 | import com.leigq.www.shiro.bean.Response; 5 | import com.leigq.www.shiro.domain.entity.User; 6 | import com.leigq.www.shiro.service.IUserService; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.apache.commons.lang3.StringUtils; 9 | import org.springframework.http.HttpStatus; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | import javax.annotation.Resource; 15 | 16 | /** 17 | * @author :leigq 18 | * @date :2019/6/28 16:55 19 | * @description:登录Controller 20 | */ 21 | @Slf4j 22 | @RestController 23 | public class LoginController { 24 | 25 | @Resource 26 | private IUserService iUserService; 27 | 28 | @Resource 29 | private Response response; 30 | 31 | /** 32 | * create by: leigq 33 | * description: 登录 34 | * create time: 2019/6/28 17:11 35 | * 36 | * @return 登录结果 37 | */ 38 | @PostMapping("/login") 39 | public Response login(User user) { 40 | log.warn("进入登录....."); 41 | 42 | String userName = user.getUserName(); 43 | String password = user.getPassword(); 44 | 45 | if (StringUtils.isBlank(userName)) { 46 | return response.failure("用户名为空!"); 47 | } 48 | 49 | if (StringUtils.isBlank(password)) { 50 | return response.failure("密码为空!"); 51 | } 52 | 53 | CacheUser loginUser = iUserService.login(userName, password); 54 | // 登录成功返回用户信息 55 | return response.success("登录成功!", loginUser); 56 | } 57 | 58 | /** 59 | * create by: leigq 60 | * description: 登出 61 | * create time: 2019/6/28 17:37 62 | */ 63 | @RequestMapping("/logout") 64 | public Response logOut() { 65 | iUserService.logout(); 66 | return response.success("登出成功!"); 67 | } 68 | 69 | /** 70 | * 未登录,shiro应重定向到登录界面,此处返回未登录状态信息由前端控制跳转页面 71 | *
72 | * create by: leigq 73 | *
74 | * create time: 2019/7/3 14:53 75 | * @return 76 | */ 77 | @RequestMapping("/un_auth") 78 | public Response unAuth() { 79 | return response.failure(HttpStatus.UNAUTHORIZED, "用户未登录!", null); 80 | } 81 | 82 | /** 83 | * 未授权,无权限,此处返回未授权状态信息由前端控制跳转页面 84 | *
85 | * create by: leigq 86 | *
87 | * create time: 2019/7/3 14:53 88 | * @return 89 | */ 90 | @RequestMapping("/unauthorized") 91 | public Response unauthorized() { 92 | return response.failure(HttpStatus.FORBIDDEN, "用户无权限!", null); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/controller/PermissionController.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.controller; 2 | 3 | 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | /** 8 | *

9 | * 前端控制器 10 | *

11 | * 12 | * @author leigq 13 | * @since 2019-06-28 14 | */ 15 | @RestController 16 | @RequestMapping("/sys/permission") 17 | public class PermissionController { 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/controller/RoleController.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.controller; 2 | 3 | 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | /** 8 | *

9 | * 前端控制器 10 | *

11 | * 12 | * @author leigq 13 | * @since 2019-06-28 14 | */ 15 | @RestController 16 | @RequestMapping("/sys/role") 17 | public class RoleController { 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/controller/RolePermissionController.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.controller; 2 | 3 | 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | /** 8 | *

9 | * 前端控制器 10 | *

11 | * 12 | * @author leigq 13 | * @since 2019-06-28 14 | */ 15 | @RestController 16 | @RequestMapping("/sys/role-permission") 17 | public class RolePermissionController { 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.controller; 2 | 3 | 4 | import com.leigq.www.shiro.bean.Response; 5 | import com.leigq.www.shiro.domain.entity.User; 6 | import com.leigq.www.shiro.service.IUserService; 7 | import org.apache.shiro.authz.annotation.RequiresPermissions; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | *

15 | * 前端控制器 16 | *

17 | * 18 | * @author leigq 19 | * @since 2019-06-28 20 | */ 21 | @RestController 22 | @RequestMapping("/user") 23 | public class UserController { 24 | 25 | @Autowired 26 | private IUserService iUserService; 27 | 28 | @Autowired 29 | private Response response; 30 | 31 | /** 32 | * 用户查询. 33 | * @return 34 | */ 35 | @GetMapping("/userList") 36 | @RequiresPermissions("user:view")//权限管理; 37 | public Response listUsers(){ 38 | List users = iUserService.listUsers(); 39 | return response.success("查询成功!", users); 40 | } 41 | 42 | /** 43 | * 用户添加; 44 | * @return 45 | */ 46 | @PostMapping("/userAdd") 47 | @RequiresPermissions("user:add")//权限管理; 48 | public String userInfoAdd(){ 49 | return "userAdd"; 50 | } 51 | 52 | /** 53 | * 用户删除; 54 | * @return 55 | */ 56 | @DeleteMapping("/userDel") 57 | @RequiresPermissions("user:del")//权限管理; 58 | public String userDel(){ 59 | return "userDel"; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/controller/UserRoleController.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.controller; 2 | 3 | 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | /** 8 | *

9 | * 前端控制器 10 | *

11 | * 12 | * @author leigq 13 | * @since 2019-06-28 14 | */ 15 | @RestController 16 | @RequestMapping("/sys/user-role") 17 | public class UserRoleController { 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/domain/entity/Permission.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.domain.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import lombok.Data; 8 | import lombok.EqualsAndHashCode; 9 | import lombok.experimental.Accessors; 10 | 11 | import java.io.Serializable; 12 | 13 | /** 14 | *

15 | * 16 | *

17 | * 18 | * @author leigq 19 | * @since 2019-06-28 20 | */ 21 | @Data 22 | @EqualsAndHashCode(callSuper = false) 23 | @Accessors(chain = true) 24 | @TableName("sys_permission") 25 | public class Permission implements Serializable { 26 | 27 | private static final long serialVersionUID = 1L; 28 | 29 | @TableId(value = "permissionId", type = IdType.AUTO) 30 | private Integer permissionId; 31 | 32 | @TableField("available") 33 | private Boolean available; 34 | 35 | @TableField("parentId") 36 | private Long parentId; 37 | 38 | @TableField("parentIds") 39 | private String parentIds; 40 | 41 | @TableField("permission") 42 | private String permission; 43 | 44 | @TableField("permissionName") 45 | private String permissionName; 46 | 47 | @TableField("resourceType") 48 | private String resourceType; 49 | 50 | @TableField("url") 51 | private String url; 52 | 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/domain/entity/Role.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.domain.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import lombok.Data; 7 | import lombok.EqualsAndHashCode; 8 | import lombok.experimental.Accessors; 9 | 10 | import java.io.Serializable; 11 | 12 | /** 13 | *

14 | * 15 | *

16 | * 17 | * @author leigq 18 | * @since 2019-06-28 19 | */ 20 | @Data 21 | @EqualsAndHashCode(callSuper = false) 22 | @Accessors(chain = true) 23 | @TableName("sys_role") 24 | public class Role implements Serializable { 25 | 26 | private static final long serialVersionUID = 1L; 27 | 28 | @TableId(value = "roleId", type = IdType.AUTO) 29 | private Integer roleId; 30 | 31 | private Boolean available; 32 | 33 | private String description; 34 | 35 | private String role; 36 | 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/domain/entity/RolePermission.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.domain.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.TableField; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import lombok.Data; 7 | import lombok.EqualsAndHashCode; 8 | import lombok.experimental.Accessors; 9 | 10 | import java.io.Serializable; 11 | 12 | /** 13 | *

14 | * 15 | *

16 | * 17 | * @author leigq 18 | * @since 2019-06-28 19 | */ 20 | @Data 21 | @EqualsAndHashCode(callSuper = false) 22 | @Accessors(chain = true) 23 | @TableName("sys_role_permission") 24 | public class RolePermission implements Serializable { 25 | 26 | private static final long serialVersionUID = 1L; 27 | 28 | @TableId 29 | private Long id; 30 | 31 | @TableField("permissionId") 32 | private Integer permissionId; 33 | 34 | @TableField("roleId") 35 | private Integer roleId; 36 | 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/domain/entity/User.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.domain.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import lombok.Data; 8 | import lombok.EqualsAndHashCode; 9 | import lombok.experimental.Accessors; 10 | import org.crazycake.shiro.AuthCachePrincipal; 11 | 12 | import java.io.Serializable; 13 | import java.time.LocalDate; 14 | import java.time.LocalDateTime; 15 | import java.util.Date; 16 | 17 | /** 18 | *

19 | * 20 | *

21 | * 22 | * @author leigq 23 | * @since 2019-06-28 24 | */ 25 | @Data 26 | @EqualsAndHashCode(callSuper = false) 27 | @Accessors(chain = true) 28 | @TableName("sys_user") 29 | public class User implements Serializable { 30 | 31 | private static final long serialVersionUID = 1L; 32 | 33 | @TableId(value = "userId", type = IdType.AUTO) 34 | private Integer userId; 35 | 36 | @TableField("createTime") 37 | private Date createTime; 38 | 39 | private String email; 40 | 41 | @TableField("expiredDate") 42 | private Date expiredDate; 43 | 44 | private String name; 45 | 46 | private String password; 47 | 48 | private String salt; 49 | 50 | private Integer state; 51 | 52 | @TableField("userName") 53 | private String userName; 54 | 55 | /** 56 | * 密码盐. 重新对盐重新进行了定义,用户名+salt,这样就不容易被破解,可以采用多种方式定义加盐 57 | * 58 | * @return 59 | */ 60 | public String getCredentialsSalt() { 61 | return this.userName + this.salt; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/domain/entity/UserRole.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.domain.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.TableField; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import lombok.Data; 7 | import lombok.EqualsAndHashCode; 8 | import lombok.experimental.Accessors; 9 | 10 | import java.io.Serializable; 11 | 12 | /** 13 | *

14 | * 15 | *

16 | * 17 | * @author leigq 18 | * @since 2019-06-28 19 | */ 20 | @Data 21 | @EqualsAndHashCode(callSuper = false) 22 | @Accessors(chain = true) 23 | @TableName("sys_user_role") 24 | public class UserRole implements Serializable { 25 | 26 | private static final long serialVersionUID = 1L; 27 | 28 | @TableId 29 | private Long id; 30 | 31 | @TableField("userId") 32 | private Integer userId; 33 | 34 | @TableField("roleId") 35 | private Integer roleId; 36 | 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/domain/mapper/PermissionMapper.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.domain.mapper; 2 | 3 | 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import com.leigq.www.shiro.domain.entity.Permission; 6 | import org.apache.ibatis.annotations.Mapper; 7 | import org.apache.ibatis.annotations.Param; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | *

13 | * Mapper 接口 14 | *

15 | * 16 | * @author leigq 17 | * @since 2019-06-28 18 | */ 19 | public interface PermissionMapper extends BaseMapper { 20 | 21 | List getRolePermissions(@Param("roleId") Integer roleId); 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/domain/mapper/RoleMapper.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.domain.mapper; 2 | 3 | 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import com.leigq.www.shiro.domain.entity.Role; 6 | import org.apache.ibatis.annotations.Mapper; 7 | import org.apache.ibatis.annotations.Param; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | *

13 | * Mapper 接口 14 | *

15 | * 16 | * @author leigq 17 | * @since 2019-06-28 18 | */ 19 | public interface RoleMapper extends BaseMapper { 20 | 21 | List getUserRoles(@Param("userId") Integer userId); 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/domain/mapper/RolePermissionMapper.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.domain.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.leigq.www.shiro.domain.entity.RolePermission; 5 | 6 | /** 7 | *

8 | * Mapper 接口 9 | *

10 | * 11 | * @author leigq 12 | * @since 2019-06-28 13 | */ 14 | public interface RolePermissionMapper extends BaseMapper { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/domain/mapper/UserMapper.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.domain.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.leigq.www.shiro.domain.entity.User; 5 | 6 | /** 7 | *

8 | * Mapper 接口 9 | *

10 | * 11 | * @author leigq 12 | * @since 2019-06-28 13 | */ 14 | public interface UserMapper extends BaseMapper { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/domain/mapper/UserRoleMapper.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.domain.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.leigq.www.shiro.domain.entity.UserRole; 5 | 6 | /** 7 | *

8 | * Mapper 接口 9 | *

10 | * 11 | * @author leigq 12 | * @since 2019-06-28 13 | */ 14 | public interface UserRoleMapper extends BaseMapper { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/service/IPermissionService.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.leigq.www.shiro.domain.entity.Permission; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | *

10 | * 服务类 11 | *

12 | * 13 | * @author leigq 14 | * @since 2019-06-28 15 | */ 16 | public interface IPermissionService extends IService { 17 | 18 | List getRolePermissions(Integer roleId); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/service/IRolePermissionService.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.leigq.www.shiro.domain.entity.RolePermission; 5 | 6 | /** 7 | *

8 | * 服务类 9 | *

10 | * 11 | * @author leigq 12 | * @since 2019-06-28 13 | */ 14 | public interface IRolePermissionService extends IService { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/service/IRoleService.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.leigq.www.shiro.domain.entity.Role; 5 | import org.apache.ibatis.annotations.Mapper; 6 | import org.mybatis.spring.annotation.MapperScan; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | *

12 | * 服务类 13 | *

14 | * 15 | * @author leigq 16 | * @since 2019-06-28 17 | */ 18 | public interface IRoleService extends IService { 19 | List getUserRoles(Integer userId); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/service/IUserRoleService.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.leigq.www.shiro.domain.entity.UserRole; 5 | 6 | /** 7 | *

8 | * 服务类 9 | *

10 | * 11 | * @author leigq 12 | * @since 2019-06-28 13 | */ 14 | public interface IUserRoleService extends IService { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/service/IUserService.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.leigq.www.shiro.bean.CacheUser; 5 | import com.leigq.www.shiro.domain.entity.User; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | *

11 | * 服务类 12 | *

13 | * 14 | * @author leigq 15 | * @since 2019-06-28 16 | */ 17 | public interface IUserService extends IService { 18 | 19 | /** 20 | * create by: leigq 21 | * description: 根据用户名获取用户 22 | * create time: 2019/6/28 16:19 23 | * @param userName 用户名 24 | * @return 用户 25 | */ 26 | User findByUsername(String userName); 27 | 28 | /** 29 | * create by: leigq 30 | * description: 登录 31 | * create time: 2019/6/28 16:26 32 | * @param userName 用户名 33 | * @param password 密码 34 | * @return 用户信息 35 | */ 36 | CacheUser login(String userName, String password); 37 | 38 | 39 | /** 40 | * create by: leigq 41 | * description: 登出 42 | * create time: 2019/6/28 16:30 43 | */ 44 | void logout(); 45 | 46 | List listUsers(); 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/service/impl/PermissionServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.service.impl; 2 | 3 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 4 | import com.leigq.www.shiro.domain.entity.Permission; 5 | import com.leigq.www.shiro.domain.mapper.PermissionMapper; 6 | import com.leigq.www.shiro.service.IPermissionService; 7 | import org.springframework.stereotype.Service; 8 | 9 | import javax.annotation.Resource; 10 | import java.util.List; 11 | 12 | /** 13 | *

14 | * 服务实现类 15 | *

16 | * 17 | * @author leigq 18 | * @since 2019-06-28 19 | */ 20 | @Service 21 | public class PermissionServiceImpl extends ServiceImpl implements IPermissionService { 22 | 23 | @Resource 24 | private PermissionMapper permissionMapper; 25 | 26 | @Override 27 | public List getRolePermissions(Integer roleId) { 28 | return permissionMapper.getRolePermissions(roleId); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/service/impl/RolePermissionServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.service.impl; 2 | 3 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 4 | import com.leigq.www.shiro.domain.entity.RolePermission; 5 | import com.leigq.www.shiro.domain.mapper.RolePermissionMapper; 6 | import com.leigq.www.shiro.service.IRolePermissionService; 7 | import org.springframework.stereotype.Service; 8 | 9 | /** 10 | *

11 | * 服务实现类 12 | *

13 | * 14 | * @author leigq 15 | * @since 2019-06-28 16 | */ 17 | @Service 18 | public class RolePermissionServiceImpl extends ServiceImpl implements IRolePermissionService { 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/service/impl/RoleServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.service.impl; 2 | 3 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 4 | import com.leigq.www.shiro.domain.entity.Role; 5 | import com.leigq.www.shiro.domain.mapper.RoleMapper; 6 | import com.leigq.www.shiro.service.IRoleService; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Service; 9 | 10 | import javax.annotation.Resource; 11 | import java.util.List; 12 | 13 | /** 14 | *

15 | * 服务实现类 16 | *

17 | * 18 | * @author leigq 19 | * @since 2019-06-28 20 | */ 21 | @Service 22 | public class RoleServiceImpl extends ServiceImpl implements IRoleService { 23 | 24 | @Resource 25 | private RoleMapper roleMapper; 26 | 27 | @Override 28 | public List getUserRoles(Integer userId) { 29 | return roleMapper.getUserRoles(userId); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/service/impl/UserRoleServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.service.impl; 2 | 3 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 4 | import com.leigq.www.shiro.domain.entity.UserRole; 5 | import com.leigq.www.shiro.domain.mapper.UserRoleMapper; 6 | import com.leigq.www.shiro.service.IUserRoleService; 7 | import org.springframework.stereotype.Service; 8 | 9 | /** 10 | *

11 | * 服务实现类 12 | *

13 | * 14 | * @author leigq 15 | * @since 2019-06-28 16 | */ 17 | @Service 18 | public class UserRoleServiceImpl extends ServiceImpl implements IUserRoleService { 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/service/impl/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.service.impl; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 4 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 5 | import com.leigq.www.shiro.bean.CacheUser; 6 | import com.leigq.www.shiro.domain.entity.User; 7 | import com.leigq.www.shiro.domain.mapper.UserMapper; 8 | import com.leigq.www.shiro.service.IUserService; 9 | import com.leigq.www.shiro.web.exception.LoginException; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.apache.shiro.SecurityUtils; 12 | import org.apache.shiro.authc.AuthenticationException; 13 | import org.apache.shiro.authc.IncorrectCredentialsException; 14 | import org.apache.shiro.authc.UnknownAccountException; 15 | import org.apache.shiro.authc.UsernamePasswordToken; 16 | import org.apache.shiro.subject.Subject; 17 | import org.springframework.beans.BeanUtils; 18 | import org.springframework.stereotype.Service; 19 | 20 | import java.util.List; 21 | 22 | /** 23 | *

24 | * 服务实现类 25 | *

26 | * 27 | * @author leigq 28 | * @since 2019-06-28 29 | */ 30 | @Service 31 | @Slf4j 32 | public class UserServiceImpl extends ServiceImpl implements IUserService { 33 | 34 | @Override 35 | public User findByUsername(String username) { 36 | return baseMapper.selectOne( 37 | new LambdaQueryWrapper().eq(User::getUserName, username) 38 | ); 39 | } 40 | 41 | @Override 42 | public CacheUser login(String userName, String password) { 43 | 44 | // 获取Subject实例对象,用户实例 45 | Subject currentUser = SecurityUtils.getSubject(); 46 | 47 | // 将用户名和密码封装到UsernamePasswordToken 48 | UsernamePasswordToken token = new UsernamePasswordToken(userName, password); 49 | 50 | CacheUser cacheUser; 51 | 52 | // 4、认证 53 | try { 54 | // 传到 MyShiroRealm 类中的方法进行认证 55 | currentUser.login(token); 56 | // 构建缓存用户信息返回给前端 57 | User user = (User) currentUser.getPrincipals().getPrimaryPrincipal(); 58 | cacheUser = CacheUser.builder() 59 | .token(currentUser.getSession().getId().toString()) 60 | .build(); 61 | BeanUtils.copyProperties(user, cacheUser); 62 | log.warn("CacheUser is {}", cacheUser.toString()); 63 | } catch (UnknownAccountException e) { 64 | log.error("账户不存在异常:", e); 65 | throw new LoginException("账号不存在!", e); 66 | } catch (IncorrectCredentialsException e) { 67 | log.error("凭据错误(密码错误)异常:", e); 68 | throw new LoginException("密码不正确!", e); 69 | } catch (AuthenticationException e) { 70 | log.error("身份验证异常:", e); 71 | throw new LoginException("用户验证失败!", e); 72 | } 73 | return cacheUser; 74 | } 75 | 76 | @Override 77 | public void logout() { 78 | Subject subject = SecurityUtils.getSubject(); 79 | subject.logout(); 80 | } 81 | 82 | @Override 83 | public List listUsers() { 84 | return baseMapper.selectList(new LambdaQueryWrapper<>()); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/util/CodeGeneratorUtils.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.util; 2 | 3 | import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException; 4 | import com.baomidou.mybatisplus.core.toolkit.StringPool; 5 | import com.baomidou.mybatisplus.generator.AutoGenerator; 6 | import com.baomidou.mybatisplus.generator.InjectionConfig; 7 | import com.baomidou.mybatisplus.generator.config.*; 8 | import com.baomidou.mybatisplus.generator.config.po.TableInfo; 9 | import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; 10 | import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; 11 | import org.apache.commons.lang3.StringUtils; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; 14 | 15 | import java.sql.Driver; 16 | import java.util.*; 17 | 18 | /** 19 | * 代码生成器 20 | *
21 | * AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。 22 | *
23 | * 详见:https://mp.baomidou.com/guide/generator.html 24 | *

25 | * 创建人:LeiGQ
26 | * 创建时间:2019-06-14 11:03
27 | *

28 | * 修改人:
29 | * 修改时间:
30 | * 修改备注:
31 | *

32 | */ 33 | public class CodeGeneratorUtils { 34 | 35 | private static final String AUTHOR = "leigq"; 36 | private static final String HOST = "127.0.0.1"; 37 | private static final String DRIVER_CLASS_NAME = "com.mysql.cj.jdbc.Driver"; 38 | private static final String DATABASE_NAME = "shiro"; 39 | private static final String DATABASE_USERNAME = "root"; 40 | private static final String DATABASE_PASSWORD = "123456"; 41 | private static final String PARENT_PACKAGE = "com.leigq.www.shiro"; 42 | 43 | /** 44 | *

45 | * 读取控制台内容 46 | *

47 | */ 48 | private static String scanner(String tip) { 49 | Scanner scanner = new Scanner(System.in); 50 | System.out.println(("请输入" + tip + ":")); 51 | if (scanner.hasNext()) { 52 | String ipt = scanner.next(); 53 | if (StringUtils.isNotEmpty(ipt)) { 54 | return ipt; 55 | } 56 | } 57 | throw new MybatisPlusException("请输入正确的" + tip + "!"); 58 | } 59 | 60 | 61 | public static void main(String[] args) { 62 | // 代码生成器 63 | AutoGenerator mpg = new AutoGenerator(); 64 | 65 | // 全局配置 66 | GlobalConfig gc = new GlobalConfig(); 67 | String projectPath = System.getProperty("user.dir"); 68 | gc.setOutputDir(projectPath + "/src/main/java"); 69 | gc.setAuthor(AUTHOR); 70 | gc.setOpen(false); 71 | // gc.setSwagger2(true); 72 | mpg.setGlobalConfig(gc); 73 | 74 | // 数据源配置 75 | DataSourceConfig dsc = new DataSourceConfig(); 76 | dsc.setUrl(String.format("jdbc:mysql://%s:3306/%s?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=UTC", HOST, DATABASE_NAME)); 77 | // dsc.setSchemaName("public"); 78 | dsc.setDriverName(DRIVER_CLASS_NAME); 79 | dsc.setUsername(DATABASE_USERNAME); 80 | dsc.setPassword(DATABASE_PASSWORD); 81 | mpg.setDataSource(dsc); 82 | 83 | // 包配置 84 | PackageConfig pc = new PackageConfig(); 85 | pc.setModuleName(scanner("模块名")); 86 | pc.setParent(PARENT_PACKAGE); 87 | mpg.setPackageInfo(pc); 88 | 89 | // 自定义配置 90 | InjectionConfig cfg = new InjectionConfig() { 91 | @Override 92 | public void initMap() { 93 | // to do nothing 94 | } 95 | }; 96 | 97 | // 如果模板引擎是 freemarker 98 | String templatePath = "/templates/mapper.xml.ftl"; 99 | // 如果模板引擎是 velocity 100 | // String templatePath = "/templates/mapper.xml.vm"; 101 | 102 | // 自定义输出配置 103 | List focList = new ArrayList<>(); 104 | // 自定义配置会被优先输出 105 | focList.add(new FileOutConfig(templatePath) { 106 | @Override 107 | public String outputFile(TableInfo tableInfo) { 108 | // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!! 109 | return projectPath + "/src/main/resources/mapper/" + pc.getModuleName() 110 | + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML; 111 | } 112 | }); 113 | /* 114 | cfg.setFileCreate(new IFileCreate() { 115 | @Override 116 | public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) { 117 | // 判断自定义文件夹是否需要创建 118 | checkDir("调用默认方法创建的目录"); 119 | return false; 120 | } 121 | }); 122 | */ 123 | cfg.setFileOutConfigList(focList); 124 | mpg.setCfg(cfg); 125 | 126 | // 配置模板 127 | TemplateConfig templateConfig = new TemplateConfig(); 128 | 129 | // 配置自定义输出模板 130 | //指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别 131 | // templateConfig.setEntity("templates/entity2.java"); 132 | // templateConfig.setService(); 133 | // templateConfig.setController(); 134 | 135 | templateConfig.setXml(null); 136 | mpg.setTemplate(templateConfig); 137 | 138 | // 策略配置 139 | StrategyConfig strategy = new StrategyConfig(); 140 | strategy.setNaming(NamingStrategy.underline_to_camel); 141 | strategy.setColumnNaming(NamingStrategy.underline_to_camel); 142 | // strategy.setSuperEntityClass("com.baomidou.ant.common.BaseEntity"); 143 | strategy.setEntityLombokModel(true); 144 | strategy.setRestControllerStyle(true); 145 | // strategy.setSuperControllerClass("com.baomidou.ant.common.BaseController"); 146 | strategy.setInclude(scanner("表名,多个英文逗号分割").split(",")); 147 | strategy.setSuperEntityColumns("id"); 148 | strategy.setControllerMappingHyphenStyle(true); 149 | strategy.setTablePrefix(pc.getModuleName() + "_"); 150 | mpg.setStrategy(strategy); 151 | mpg.setTemplateEngine(new FreemarkerTemplateEngine()); 152 | mpg.execute(); 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/web/GlobalExceptionHand.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.web; 2 | 3 | 4 | import com.leigq.www.shiro.bean.Response; 5 | import com.leigq.www.shiro.web.exception.LoginException; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.apache.shiro.authz.UnauthorizedException; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.http.converter.HttpMessageNotReadableException; 10 | import org.springframework.validation.BindException; 11 | import org.springframework.validation.BindingResult; 12 | import org.springframework.validation.FieldError; 13 | import org.springframework.web.HttpMediaTypeNotSupportedException; 14 | import org.springframework.web.HttpRequestMethodNotSupportedException; 15 | import org.springframework.web.bind.MethodArgumentNotValidException; 16 | import org.springframework.web.bind.MissingServletRequestParameterException; 17 | import org.springframework.web.bind.annotation.ExceptionHandler; 18 | import org.springframework.web.bind.annotation.ResponseStatus; 19 | import org.springframework.web.bind.annotation.RestControllerAdvice; 20 | import org.springframework.web.multipart.MaxUploadSizeExceededException; 21 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 22 | 23 | import javax.management.ServiceNotFoundException; 24 | import javax.validation.ConstraintViolation; 25 | import javax.validation.ConstraintViolationException; 26 | import javax.validation.ValidationException; 27 | import java.util.List; 28 | import java.util.Set; 29 | 30 | /** 31 | * 全局异常处理 32 | *

33 | * 创建人:LeiGQ
34 | * 创建时间:2019-05-14 17:09
35 | *

36 | * 修改人:
37 | * 修改时间:
38 | * 修改备注:
39 | *

40 | */ 41 | @EnableWebMvc 42 | @RestControllerAdvice 43 | @Slf4j 44 | public class GlobalExceptionHand { 45 | 46 | /** 47 | * 400 - Bad Request 48 | */ 49 | @ResponseStatus(HttpStatus.BAD_REQUEST) 50 | @ExceptionHandler(MissingServletRequestParameterException.class) 51 | public Response handleMissingServletRequestParameterException(MissingServletRequestParameterException e) { 52 | String msg = "缺少请求参数!"; 53 | log.error(msg, e); 54 | return new Response().failure(msg); 55 | } 56 | 57 | /** 58 | * 400 - Bad Request 59 | */ 60 | @ResponseStatus(HttpStatus.BAD_REQUEST) 61 | @ExceptionHandler(HttpMessageNotReadableException.class) 62 | public Response handleHttpMessageNotReadableException(HttpMessageNotReadableException e) { 63 | String msg = e.getMessage(); 64 | log.error("参数解析失败:", e); 65 | return new Response().failure(msg); 66 | } 67 | 68 | /** 69 | * 400 - Bad Request 70 | */ 71 | @ResponseStatus(HttpStatus.BAD_REQUEST) 72 | @ExceptionHandler(MethodArgumentNotValidException.class) 73 | public Response handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { 74 | String msg = handleBindingResult(e.getBindingResult()); 75 | log.error("方法参数无效: ", e); 76 | return new Response().failure(msg); 77 | } 78 | 79 | /** 80 | * 400 - Bad Request 81 | */ 82 | @ResponseStatus(HttpStatus.BAD_REQUEST) 83 | @ExceptionHandler(BindException.class) 84 | public Response handleBindException(BindException e) { 85 | String msg = handleBindingResult(e.getBindingResult()); 86 | log.error("参数绑定失败:", e); 87 | return new Response().failure(msg); 88 | } 89 | 90 | /** 91 | * 400 - Bad Request 92 | */ 93 | @ResponseStatus(HttpStatus.BAD_REQUEST) 94 | @ExceptionHandler(ConstraintViolationException.class) 95 | public Response handleServiceException(ConstraintViolationException e) { 96 | Set> violations = e.getConstraintViolations(); 97 | String msg = violations.iterator().next().getMessage(); 98 | log.error("参数验证失败:", e); 99 | return new Response().failure(msg); 100 | } 101 | 102 | /** 103 | * 400 - Bad Request 104 | */ 105 | @ResponseStatus(HttpStatus.BAD_REQUEST) 106 | @ExceptionHandler(ValidationException.class) 107 | public Response handleValidationException(ValidationException e) { 108 | String msg = e.getMessage(); 109 | log.error("参数验证失败:", e); 110 | return new Response().failure(msg); 111 | } 112 | 113 | /** 114 | * 401 - Unauthorized 115 | */ 116 | @ResponseStatus(HttpStatus.UNAUTHORIZED) 117 | @ExceptionHandler(LoginException.class) 118 | public Response handleLoginException(LoginException e) { 119 | String msg = e.getMessage(); 120 | log.error("登录异常:", e); 121 | return new Response().failure(msg); 122 | } 123 | 124 | /** 125 | * 403 - Unauthorized 126 | */ 127 | @ResponseStatus(HttpStatus.FORBIDDEN) 128 | @ExceptionHandler(UnauthorizedException.class) 129 | public Response handleLoginException(UnauthorizedException e) { 130 | String msg = e.getMessage(); 131 | log.error("用户无权限:", e); 132 | return new Response().failure(HttpStatus.FORBIDDEN, "用户无权限!", null); 133 | } 134 | 135 | /** 136 | * 405 - Method Not Allowed 137 | */ 138 | @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED) 139 | @ExceptionHandler(HttpRequestMethodNotSupportedException.class) 140 | public Response handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) { 141 | String msg = "不支持当前请求方法!"; 142 | log.error(msg, e); 143 | return new Response().failure(msg); 144 | } 145 | 146 | /** 147 | * 415 - Unsupported Media Type 148 | */ 149 | @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE) 150 | @ExceptionHandler(HttpMediaTypeNotSupportedException.class) 151 | public Response handleHttpMediaTypeNotSupportedException(Exception e) { 152 | String msg = "不支持当前媒体类型!"; 153 | log.error(msg, e); 154 | return new Response().failure(msg); 155 | } 156 | 157 | /** 158 | * 422 - UNPROCESSABLE_ENTITY 159 | */ 160 | @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY) 161 | @ExceptionHandler(MaxUploadSizeExceededException.class) 162 | public Response handleMaxUploadSizeExceededException(Exception e) { 163 | String msg = "所上传文件大小超过最大限制,上传失败!"; 164 | log.error(msg, e); 165 | return new Response().failure(msg); 166 | } 167 | 168 | /** 169 | * 500 - Internal Server Error 170 | */ 171 | @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) 172 | @ExceptionHandler(ServiceNotFoundException.class) 173 | public Response handleServiceException(ServiceNotFoundException e) { 174 | String msg = "服务内部异常:" + e.getMessage(); 175 | log.error(msg, e); 176 | return new Response().failure(msg); 177 | } 178 | 179 | /** 180 | * 500 - Internal Server Error 181 | */ 182 | @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) 183 | @ExceptionHandler(Exception.class) 184 | public Response handleException(Exception e) { 185 | String msg = "服务内部异常!" + e.getMessage(); 186 | log.error(msg, e); 187 | return new Response().failure(msg); 188 | } 189 | 190 | /** 191 | * 处理参数绑定异常,并拼接出错的参数异常信息。 192 | *

193 | * 创建人:leigq
194 | * 创建时间:2017年10月16日 下午9:09:22
195 | *

196 | * 修改人:
197 | * 修改时间:
198 | * 修改备注:
199 | *

200 | * 201 | * @param result 202 | */ 203 | private String handleBindingResult(BindingResult result) { 204 | if (result.hasErrors()) { 205 | final List fieldErrors = result.getFieldErrors(); 206 | return fieldErrors.iterator().next().getDefaultMessage(); 207 | } 208 | return null; 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/main/java/com/leigq/www/shiro/web/exception/LoginException.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.web.exception; 2 | 3 | /** 4 | * @author :leigq 5 | * @date :2019/7/1 09:26 6 | * @description:登录异常 7 | */ 8 | public class LoginException extends RuntimeException { 9 | public LoginException() { 10 | super(); 11 | } 12 | 13 | public LoginException(String message) { 14 | super(message); 15 | } 16 | 17 | public LoginException(String message, Throwable cause) { 18 | super(message, cause); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | active: dev -------------------------------------------------------------------------------- /src/main/resources/config/application-dev.yml: -------------------------------------------------------------------------------- 1 | mybatis-plus: 2 | configuration: 3 | map-underscore-to-camel-case: true 4 | use-generated-keys: true 5 | mapper-locations: classpath*:/mappers/**/*.xml 6 | type-aliases-package: com.leigq.www.shiro.domain.entity 7 | server: 8 | tomcat: 9 | uri-encoding: UTF-8 10 | spring: 11 | datasource: 12 | connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000;config.decrypt=true;config.decrypt.key=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKPYsCl3alwZlRb1vKoFdVu0LP3Nm/+vH5iOWxI83pkUbrQc13Lxz/VT3D+H+ziaUpUsA+ZjG4iZGTDJWZnP8kcCAwEAAQ== 13 | driver-class-name: com.mysql.cj.jdbc.Driver 14 | filters: config,stat,wall,slf4j 15 | initialSize: 5 16 | maxActive: 20 17 | maxPoolPreparedStatementPerConnectionSize: 20 18 | maxWait: 60000 19 | minEvictableIdleTimeMillis: 300000 20 | minIdle: 5 21 | password: kGJF6c+pzVsf49LGs01ss0yijBGXIpNEp20cMkNCQo3ONaeMNPeoW9M89v+nGeiWs95/D2Ms59uGyydDGUWpmg== 22 | poolPreparedStatements: true 23 | testOnBorrow: false 24 | testOnReturn: false 25 | testWhileIdle: true 26 | timeBetweenEvictionRunsMillis: 60000 27 | type: com.alibaba.druid.pool.DruidDataSource 28 | url: jdbc:mysql://localhost:3306/shiro?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=true&serverTimezone=UTC 29 | username: root 30 | validationQuery: SELECT 1 FROM DUAL 31 | thymeleaf: 32 | cache: false 33 | redis: 34 | host: localhost 35 | port: 6379 36 | timeout: 2000s 37 | password: 111111 -------------------------------------------------------------------------------- /src/main/resources/config/application-prod.yml: -------------------------------------------------------------------------------- 1 | mybatis-plus: 2 | configuration: 3 | map-underscore-to-camel-case: true 4 | use-generated-keys: true 5 | mapper-locations: classpath*:/mappers/**/*.xml 6 | type-aliases-package: com.leigq.www.shiro.domain.entity 7 | server: 8 | tomcat: 9 | uri-encoding: UTF-8 10 | spring: 11 | datasource: 12 | connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000;config.decrypt=true;config.decrypt.key=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKPYsCl3alwZlRb1vKoFdVu0LP3Nm/+vH5iOWxI83pkUbrQc13Lxz/VT3D+H+ziaUpUsA+ZjG4iZGTDJWZnP8kcCAwEAAQ== 13 | driver-class-name: com.mysql.cj.jdbc.Driver 14 | filters: config,stat,wall,slf4j 15 | initialSize: 5 16 | maxActive: 20 17 | maxPoolPreparedStatementPerConnectionSize: 20 18 | maxWait: 60000 19 | minEvictableIdleTimeMillis: 300000 20 | minIdle: 5 21 | password: kGJF6c+pzVsf49LGs01ss0yijBGXIpNEp20cMkNCQo3ONaeMNPeoW9M89v+nGeiWs95/D2Ms59uGyydDGUWpmg== 22 | poolPreparedStatements: true 23 | testOnBorrow: false 24 | testOnReturn: false 25 | testWhileIdle: true 26 | timeBetweenEvictionRunsMillis: 60000 27 | type: com.alibaba.druid.pool.DruidDataSource 28 | url: jdbc:mysql://localhost:3306/shiro?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=true&serverTimezone=UTC 29 | username: root 30 | validationQuery: SELECT 1 FROM DUAL 31 | thymeleaf: 32 | cache: false 33 | redis: 34 | host: localhost 35 | port: 6379 36 | timeout: 2000s 37 | password: 111111 -------------------------------------------------------------------------------- /src/main/resources/config/application-test.yml: -------------------------------------------------------------------------------- 1 | mybatis-plus: 2 | configuration: 3 | map-underscore-to-camel-case: true 4 | use-generated-keys: true 5 | mapper-locations: classpath*:/mappers/**/*.xml 6 | type-aliases-package: com.leigq.www.shiro.domain.entity 7 | server: 8 | tomcat: 9 | uri-encoding: UTF-8 10 | spring: 11 | datasource: 12 | connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000;config.decrypt=true;config.decrypt.key=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKPYsCl3alwZlRb1vKoFdVu0LP3Nm/+vH5iOWxI83pkUbrQc13Lxz/VT3D+H+ziaUpUsA+ZjG4iZGTDJWZnP8kcCAwEAAQ== 13 | driver-class-name: com.mysql.cj.jdbc.Driver 14 | filters: config,stat,wall,slf4j 15 | initialSize: 5 16 | maxActive: 20 17 | maxPoolPreparedStatementPerConnectionSize: 20 18 | maxWait: 60000 19 | minEvictableIdleTimeMillis: 300000 20 | minIdle: 5 21 | password: kGJF6c+pzVsf49LGs01ss0yijBGXIpNEp20cMkNCQo3ONaeMNPeoW9M89v+nGeiWs95/D2Ms59uGyydDGUWpmg== 22 | poolPreparedStatements: true 23 | testOnBorrow: false 24 | testOnReturn: false 25 | testWhileIdle: true 26 | timeBetweenEvictionRunsMillis: 60000 27 | type: com.alibaba.druid.pool.DruidDataSource 28 | url: jdbc:mysql://localhost:3306/shiro?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=true&serverTimezone=UTC 29 | username: root 30 | validationQuery: SELECT 1 FROM DUAL 31 | thymeleaf: 32 | cache: false 33 | redis: 34 | host: localhost 35 | port: 6379 36 | timeout: 2000s 37 | password: 111111 -------------------------------------------------------------------------------- /src/main/resources/mappers/PermissionMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/resources/mappers/RoleMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/resources/mappers/RolePermissionMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/resources/mappers/UserMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/resources/mappers/UserRoleMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/resources/sql/shiro-V1.0.0.sql: -------------------------------------------------------------------------------- 1 | /* 2 | SQLyog Ultimate v12.5.0 (64 bit) 3 | MySQL - 5.7.21-log : Database - shiro 4 | ********************************************************************* 5 | */ 6 | 7 | /*!40101 SET NAMES utf8 */; 8 | 9 | /*!40101 SET SQL_MODE=''*/; 10 | 11 | /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; 12 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; 13 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; 14 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; 15 | CREATE DATABASE /*!32312 IF NOT EXISTS*/`shiro` /*!40100 DEFAULT CHARACTER SET utf8 */; 16 | 17 | USE `shiro`; 18 | 19 | /*Table structure for table `sys_permission` */ 20 | 21 | DROP TABLE IF EXISTS `sys_permission`; 22 | 23 | CREATE TABLE `sys_permission` ( 24 | `permissionId` int(11) NOT NULL AUTO_INCREMENT, 25 | `available` bit(1) DEFAULT NULL, 26 | `parentId` bigint(20) DEFAULT NULL, 27 | `parentIds` varchar(255) DEFAULT NULL, 28 | `permission` varchar(255) DEFAULT NULL, 29 | `permissionName` varchar(255) NOT NULL, 30 | `resourceType` enum('menu','button') DEFAULT NULL, 31 | `url` varchar(255) DEFAULT NULL, 32 | PRIMARY KEY (`permissionId`) 33 | ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; 34 | 35 | /*Data for the table `sys_permission` */ 36 | 37 | insert into `sys_permission`(`permissionId`,`available`,`parentId`,`parentIds`,`permission`,`permissionName`,`resourceType`,`url`) values 38 | (1,'\0',0,'0/','user:view','用户管理','menu','user/userList'), 39 | (2,'\0',1,'0/1','user:add','用户添加','button','user/userAdd'), 40 | (3,'\0',1,'0/1','user:del','用户删除','button','user/userDel'); 41 | 42 | /*Table structure for table `sys_role` */ 43 | 44 | DROP TABLE IF EXISTS `sys_role`; 45 | 46 | CREATE TABLE `sys_role` ( 47 | `roleId` int(11) NOT NULL AUTO_INCREMENT, 48 | `available` bit(1) DEFAULT NULL, 49 | `description` varchar(255) DEFAULT NULL, 50 | `role` varchar(255) NOT NULL, 51 | PRIMARY KEY (`roleId`), 52 | UNIQUE KEY `UK_8sggqkp1sv8guimk979mf6anf` (`role`) 53 | ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; 54 | 55 | /*Data for the table `sys_role` */ 56 | 57 | insert into `sys_role`(`roleId`,`available`,`description`,`role`) values 58 | (1,'\0','管理员','admin'), 59 | (2,'\0','VIP会员','vip'), 60 | (3,'','test','test'); 61 | 62 | /*Table structure for table `sys_role_permission` */ 63 | 64 | DROP TABLE IF EXISTS `sys_role_permission`; 65 | 66 | CREATE TABLE `sys_role_permission` ( 67 | `permissionId` int(11) NOT NULL, 68 | `roleId` int(11) NOT NULL, 69 | KEY `FKpuhqkr403td1v28c3e71cgm4b` (`roleId`), 70 | KEY `FKjwye79px7p33gsqu4kftj0ua1` (`permissionId`), 71 | CONSTRAINT `FKjwye79px7p33gsqu4kftj0ua1` FOREIGN KEY (`permissionId`) REFERENCES `sys_permission` (`permissionId`), 72 | CONSTRAINT `FKpuhqkr403td1v28c3e71cgm4b` FOREIGN KEY (`roleId`) REFERENCES `sys_role` (`roleId`) 73 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 74 | 75 | /*Data for the table `sys_role_permission` */ 76 | 77 | insert into `sys_role_permission`(`permissionId`,`roleId`) values 78 | (1,1), 79 | (2,1), 80 | (3,2); 81 | 82 | /*Table structure for table `sys_user` */ 83 | 84 | DROP TABLE IF EXISTS `sys_user`; 85 | 86 | CREATE TABLE `sys_user` ( 87 | `userId` int(11) NOT NULL AUTO_INCREMENT, 88 | `createTime` datetime DEFAULT NULL, 89 | `email` varchar(255) DEFAULT NULL, 90 | `expiredDate` date DEFAULT NULL, 91 | `name` varchar(255) NOT NULL, 92 | `password` varchar(255) NOT NULL, 93 | `salt` varchar(255) DEFAULT NULL, 94 | `state` tinyint(4) NOT NULL, 95 | `userName` varchar(255) NOT NULL, 96 | PRIMARY KEY (`userId`), 97 | UNIQUE KEY `UK_hl8fftx66p59oqgkkcfit3eay` (`userName`) 98 | ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; 99 | 100 | /*Data for the table `sys_user` */ 101 | 102 | insert into `sys_user`(`userId`,`createTime`,`email`,`expiredDate`,`name`,`password`,`salt`,`state`,`userName`) values 103 | (1,'2019-06-28 18:00:31',NULL,NULL,'管理员','d3c59d25033dbf980d29554025c23a75','8d78869f470951332959580424d4bf4f',0,'admin'); 104 | 105 | /*Table structure for table `sys_user_role` */ 106 | 107 | DROP TABLE IF EXISTS `sys_user_role`; 108 | 109 | CREATE TABLE `sys_user_role` ( 110 | `userId` int(11) NOT NULL, 111 | `roleId` int(11) NOT NULL, 112 | KEY `FKgnn5rpnbwhx9fu93b19daiwbt` (`roleId`), 113 | KEY `FKsaymuhj4r4qr22w2q1e2oewx` (`userId`), 114 | CONSTRAINT `FKgnn5rpnbwhx9fu93b19daiwbt` FOREIGN KEY (`roleId`) REFERENCES `sys_role` (`roleId`), 115 | CONSTRAINT `FKsaymuhj4r4qr22w2q1e2oewx` FOREIGN KEY (`userId`) REFERENCES `sys_user` (`userId`) 116 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 117 | 118 | /*Data for the table `sys_user_role` */ 119 | 120 | insert into `sys_user_role`(`userId`,`roleId`) values 121 | (1,1); 122 | 123 | /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; 124 | /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; 125 | /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; 126 | /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; 127 | -------------------------------------------------------------------------------- /src/main/resources/sql/shiro-V1.0.1.sql: -------------------------------------------------------------------------------- 1 | /* 2 | SQLyog Ultimate v12.5.0 (64 bit) 3 | MySQL - 5.7.21-log : Database - shiro 4 | ********************************************************************* 5 | */ 6 | 7 | /*!40101 SET NAMES utf8 */; 8 | 9 | /*!40101 SET SQL_MODE=''*/; 10 | 11 | /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; 12 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; 13 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; 14 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; 15 | CREATE DATABASE /*!32312 IF NOT EXISTS*/`shiro` /*!40100 DEFAULT CHARACTER SET utf8 */; 16 | 17 | USE `shiro`; 18 | 19 | /*Table structure for table `sys_permission` */ 20 | 21 | DROP TABLE IF EXISTS `sys_permission`; 22 | 23 | CREATE TABLE `sys_permission` ( 24 | `permissionId` int(11) NOT NULL AUTO_INCREMENT, 25 | `available` bit(1) DEFAULT NULL, 26 | `parentId` bigint(20) DEFAULT NULL, 27 | `parentIds` varchar(255) DEFAULT NULL, 28 | `permission` varchar(255) DEFAULT NULL, 29 | `permissionName` varchar(255) NOT NULL, 30 | `resourceType` enum('menu','button') DEFAULT NULL, 31 | `url` varchar(255) DEFAULT NULL, 32 | PRIMARY KEY (`permissionId`) 33 | ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; 34 | 35 | /*Data for the table `sys_permission` */ 36 | 37 | insert into `sys_permission`(`permissionId`,`available`,`parentId`,`parentIds`,`permission`,`permissionName`,`resourceType`,`url`) values 38 | (1,'\0',0,'0/','user:view','用户管理','menu','user/userList'), 39 | (2,'\0',1,'0/1','user:add','用户添加','button','user/userAdd'), 40 | (3,'\0',1,'0/1','user:del','用户删除','button','user/userDel'); 41 | 42 | /*Table structure for table `sys_role` */ 43 | 44 | DROP TABLE IF EXISTS `sys_role`; 45 | 46 | CREATE TABLE `sys_role` ( 47 | `roleId` int(11) NOT NULL AUTO_INCREMENT, 48 | `available` bit(1) DEFAULT NULL, 49 | `description` varchar(255) DEFAULT NULL, 50 | `role` varchar(255) NOT NULL, 51 | PRIMARY KEY (`roleId`), 52 | UNIQUE KEY `UK_8sggqkp1sv8guimk979mf6anf` (`role`) 53 | ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; 54 | 55 | /*Data for the table `sys_role` */ 56 | 57 | insert into `sys_role`(`roleId`,`available`,`description`,`role`) values 58 | (1,'\0','管理员','admin'), 59 | (2,'\0','VIP会员','vip'), 60 | (3,'','test','test'); 61 | 62 | /*Table structure for table `sys_role_permission` */ 63 | 64 | DROP TABLE IF EXISTS `sys_role_permission`; 65 | 66 | CREATE TABLE `sys_role_permission` ( 67 | `id` bigint(20) NOT NULL AUTO_INCREMENT, 68 | `permissionId` int(11) NOT NULL, 69 | `roleId` int(11) NOT NULL, 70 | PRIMARY KEY (`id`), 71 | KEY `FKpuhqkr403td1v28c3e71cgm4b` (`roleId`), 72 | KEY `FKjwye79px7p33gsqu4kftj0ua1` (`permissionId`), 73 | CONSTRAINT `FKjwye79px7p33gsqu4kftj0ua1` FOREIGN KEY (`permissionId`) REFERENCES `sys_permission` (`permissionId`), 74 | CONSTRAINT `FKpuhqkr403td1v28c3e71cgm4b` FOREIGN KEY (`roleId`) REFERENCES `sys_role` (`roleId`) 75 | ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; 76 | 77 | /*Data for the table `sys_role_permission` */ 78 | 79 | insert into `sys_role_permission`(`id`,`permissionId`,`roleId`) values 80 | (1,1,1), 81 | (2,2,1); 82 | 83 | /*Table structure for table `sys_user` */ 84 | 85 | DROP TABLE IF EXISTS `sys_user`; 86 | 87 | CREATE TABLE `sys_user` ( 88 | `userId` int(11) NOT NULL AUTO_INCREMENT, 89 | `createTime` datetime DEFAULT NULL, 90 | `email` varchar(255) DEFAULT NULL, 91 | `expiredDate` date DEFAULT NULL, 92 | `name` varchar(255) NOT NULL, 93 | `password` varchar(255) NOT NULL, 94 | `salt` varchar(255) DEFAULT NULL, 95 | `state` tinyint(4) NOT NULL, 96 | `userName` varchar(255) NOT NULL, 97 | PRIMARY KEY (`userId`), 98 | UNIQUE KEY `UK_hl8fftx66p59oqgkkcfit3eay` (`userName`) 99 | ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; 100 | 101 | /*Data for the table `sys_user` */ 102 | 103 | insert into `sys_user`(`userId`,`createTime`,`email`,`expiredDate`,`name`,`password`,`salt`,`state`,`userName`) values 104 | (1,'2019-06-28 18:00:31',NULL,NULL,'管理员','d3c59d25033dbf980d29554025c23a75','8d78869f470951332959580424d4bf4f',0,'admin'); 105 | 106 | /*Table structure for table `sys_user_role` */ 107 | 108 | DROP TABLE IF EXISTS `sys_user_role`; 109 | 110 | CREATE TABLE `sys_user_role` ( 111 | `id` bigint(20) NOT NULL AUTO_INCREMENT, 112 | `userId` int(11) NOT NULL, 113 | `roleId` int(11) NOT NULL, 114 | PRIMARY KEY (`id`), 115 | KEY `FKgnn5rpnbwhx9fu93b19daiwbt` (`roleId`), 116 | KEY `FKsaymuhj4r4qr22w2q1e2oewx` (`userId`), 117 | CONSTRAINT `FKgnn5rpnbwhx9fu93b19daiwbt` FOREIGN KEY (`roleId`) REFERENCES `sys_role` (`roleId`), 118 | CONSTRAINT `FKsaymuhj4r4qr22w2q1e2oewx` FOREIGN KEY (`userId`) REFERENCES `sys_user` (`userId`) 119 | ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; 120 | 121 | /*Data for the table `sys_user_role` */ 122 | 123 | insert into `sys_user_role`(`id`,`userId`,`roleId`) values 124 | (1,1,1); 125 | 126 | /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; 127 | /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; 128 | /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; 129 | /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; 130 | -------------------------------------------------------------------------------- /src/test/java/com/leigq/www/shiro/base/BaseApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.base; 2 | 3 | import org.junit.After; 4 | import org.junit.Before; 5 | import org.junit.runner.RunWith; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.test.context.junit4.SpringRunner; 10 | 11 | @RunWith(SpringRunner.class) 12 | @SpringBootTest 13 | public abstract class BaseApplicationTests { 14 | 15 | protected Logger log = LoggerFactory.getLogger(this.getClass()); 16 | 17 | private Long time; 18 | 19 | @Before 20 | public void setUp() { 21 | this.time = System.currentTimeMillis(); 22 | log.info("==> 测试开始执行 <=="); 23 | } 24 | 25 | @After 26 | public void tearDown() { 27 | log.info("==> 测试执行完成,耗时:{} ms <==", System.currentTimeMillis() - this.time); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/com/leigq/www/shiro/test/ShiroApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.leigq.www.shiro.test; 2 | 3 | import com.leigq.www.shiro.base.BaseApplicationTests; 4 | import org.junit.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; 7 | 8 | public class ShiroApplicationTests extends BaseApplicationTests { 9 | 10 | @Autowired 11 | private DataSourceProperties dataSourceProperties; 12 | 13 | @Test 14 | public void contextLoads() { 15 | // 测试时候可以正确获取 DataSourceProperties bean 16 | log.warn("DriverClassName is {}", dataSourceProperties.getDriverClassName()); 17 | } 18 | 19 | } 20 | --------------------------------------------------------------------------------