├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src └── main ├── java └── com │ └── wang │ ├── Application.java │ ├── config │ ├── ExceptionAdvice.java │ ├── OriginFilter.java │ ├── redis │ │ └── JedisConfig.java │ └── shiro │ │ ├── ShiroConfig.java │ │ ├── UserRealm.java │ │ ├── cache │ │ ├── CustomCache.java │ │ └── CustomCacheManager.java │ │ └── jwt │ │ ├── JwtFilter.java │ │ └── JwtToken.java │ ├── controller │ └── UserController.java │ ├── exception │ ├── CustomException.java │ └── CustomUnauthorizedException.java │ ├── mapper │ ├── PermissionMapper.java │ ├── RoleMapper.java │ ├── RolePermissionMapper.java │ ├── UserMapper.java │ └── UserRoleMapper.java │ ├── model │ ├── PermissionDto.java │ ├── RoleDto.java │ ├── RolePermissionDto.java │ ├── UserDto.java │ ├── UserRoleDto.java │ ├── common │ │ ├── BaseDto.java │ │ ├── Constant.java │ │ └── ResponseBean.java │ ├── entity │ │ ├── Permission.java │ │ ├── Role.java │ │ ├── RolePermission.java │ │ ├── User.java │ │ └── UserRole.java │ └── valid │ │ └── group │ │ ├── UserEditValidGroup.java │ │ └── UserLoginValidGroup.java │ ├── service │ ├── IBaseService.java │ ├── IUserService.java │ └── impl │ │ ├── BaseServiceImpl.java │ │ └── UserServiceImpl.java │ └── util │ ├── AesCipherUtil.java │ ├── JedisUtil.java │ ├── JwtUtil.java │ ├── UserUtil.java │ └── common │ ├── Base64ConvertUtil.java │ ├── HexConvertUtil.java │ ├── JsonConvertUtil.java │ ├── PropertiesUtil.java │ ├── SerializableUtil.java │ └── StringUtil.java └── resources ├── application.yml ├── config.properties ├── generator └── generatorConfig.xml ├── mapper ├── PermissionMapper.xml ├── RoleMapper.xml ├── RolePermissionMapper.xml ├── UserMapper.xml └── UserRoleMapper.xml └── sql └── MySQL.sql /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Example user template template 3 | ### Example user template 4 | 5 | # IntelliJ project files 6 | .idea 7 | *.iml 8 | out 9 | gen 10 | target 11 | src/test 12 | *.iws 13 | *.ipr 14 | 15 | gen### Java template 16 | # Compiled class file 17 | *.class 18 | 19 | # Log file 20 | *.log 21 | 22 | # BlueJ files 23 | *.ctxt 24 | 25 | # Mobile Tools for Java (J2ME) 26 | .mtj.tmp/ 27 | 28 | # Package Files # 29 | *.jar 30 | *.war 31 | *.nar 32 | *.ear 33 | *.zip 34 | *.tar.gz 35 | *.rar 36 | 37 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 38 | hs_err_pid* 39 | 40 | ### STS ### 41 | .apt_generated 42 | .classpath 43 | .factorypath 44 | .project 45 | .settings 46 | .springBeans 47 | .sts4-cache 48 | 49 | ### NetBeans ### 50 | /nbproject/private/ 51 | /build/ 52 | /nbbuild/ 53 | /dist/ 54 | /nbdist/ 55 | /.nb-gradle/ 56 | 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 随心 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ShiroJwt 2 | 3 | [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) 4 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/wang926454/ShiroJwt/pulls) 5 | [![GitHub stars](https://img.shields.io/github/stars/wang926454/ShiroJwt.svg?style=social&label=Stars)](https://github.com/wang926454/ShiroJwt) 6 | [![GitHub forks](https://img.shields.io/github/forks/wang926454/ShiroJwt.svg?style=social&label=Fork)](https://github.com/wang926454/ShiroJwt) 7 | 8 | > 前端地址:[https://github.com/wang926454/VueStudy/tree/master/VueStudy08-JWT](https://github.com/wang926454/VueStudy/tree/master/VueStudy08-JWT) 9 | 10 | #### 疑问查看 11 | 12 | 1. [#14 重复请求会不会生成多个token](https://github.com/dolyw/ShiroJwt/issues/14) 13 | 2. [#19 跨域sso问题](https://github.com/dolyw/ShiroJwt/issues/19) 14 | 3. [#29 Token刷新并发处理](https://github.com/dolyw/ShiroJwt/issues/29) 15 | 16 | 17 | 18 | 有疑问请扫码加**QQ**群交流: **779168604** 19 | 20 | #### 项目相关 21 | 22 | * JavaDoc:[https://apidoc.gitee.com/dolyw/ShiroJwt](https://apidoc.gitee.com/dolyw/ShiroJwt) 23 | * 接口文档:[https://note.dolyw.com/shirojwt/ShiroJwt-Interface.html](https://note.dolyw.com/shirojwt/ShiroJwt-Interface.html) 24 | * 教程目录:[https://note.dolyw.com/shirojwt](https://note.dolyw.com/shirojwt) 25 | * 改为数据库形式(MySQL):[https://note.dolyw.com/shirojwt/ShiroJwt02-MySQL.html](https://note.dolyw.com/shirojwt/ShiroJwt02-MySQL.html) 26 | * 解决无法直接返回401错误:[https://note.dolyw.com/shirojwt/ShiroJwt03-401.html](https://note.dolyw.com/shirojwt/ShiroJwt03-401.html) 27 | * 实现Shiro的Cache(Redis)功能:[https://note.dolyw.com/shirojwt/ShiroJwt04-Redis.html](https://note.dolyw.com/shirojwt/ShiroJwt04-Redis.html) 28 | 29 | #### 项目介绍 30 | 31 | 1. RESTful API 32 | 2. Maven集成Mybatis Generator(逆向工程) 33 | 3. Shiro + Java-JWT实现无状态鉴权机制(Token) 34 | 4. 密码加密(采用AES-128 + Base64的方式) 35 | 5. 集成Redis(Jedis) 36 | 6. 重写Shiro缓存机制(Redis) 37 | 7. Redis中保存RefreshToken信息(做到JWT的可控性) 38 | 8. 根据RefreshToken自动刷新AccessToken 39 | 40 | ##### 关于Shiro + Java-JWT实现无状态鉴权机制(Token) 41 | 42 | > 1. 首先**Post**用户名与密码到**user/login**登入,成功返回加密的**AccessToken**,失败直接返回401错误(帐号或密码不正确) 43 | > 2. 以后访问都带上这个**AccessToken**即可 44 | > 3. 鉴权流程主要是重写了**Shiro**的入口过滤器**JWTFilter**(**BasicHttpAuthenticationFilter**),判断请求**Header**里面是否包含**Authorization**字段 45 | > 4. 有就进行**Shiro**的**Token**登录认证授权(用户访问每一个需要权限的请求必须在**Header**中添加**Authorization**字段存放**AccessToken**),没有就以游客直接访问(有权限管控的话,以游客访问就会被拦截) 46 | 47 | ##### 关于AES-128 + Base64当两个用户的明文密码相同时进行加密,会发现数据库中存在相同结构的暗文密码 48 | 49 | > 大部分是以**MD5 + 盐**的形式解决了这个问题(详细自己百度),我采用**AES-128 + Base64**是以帐号+密码的形式进行加密密码,因为帐号具有唯一性,所以也不会出现相同结构的暗文密码这个问题 50 | 51 | ##### 关于将Jedis工具类与SpringBoot整合 52 | 53 | > 本来是直接将**JedisUtil**注入为**Bean**,每次使用直接`@Autowired`注入使用即可,但是在重写**Shiro**的**CustomCache**无法注入**JedisUtil**,所以就改成静态注入**JedisPool连接池**,**JedisUtil工具类**还是直接调用静态方法,无需`@Autowired`注入 54 | 55 | ##### 关于Redis中保存RefreshToken信息(做到JWT的可控性) 56 | 57 | > 1. 登录认证通过后返回**AccessToken**信息(在**AccessToken**中**保存当前的时间戳和帐号**) 58 | > 2. 同时在**Redis**中设置一条以**帐号为Key,Value为当前时间戳(登录时间)**的**RefreshToken** 59 | > 3. 现在认证时必须**AccessToken**没失效以及**Redis**存在所对应的**RefreshToken**,且**RefreshToken时间戳**和**AccessToken信息中时间戳一致**才算认证通过,这样可以做到**JWT的可控性** 60 | > 4. 如果重新登录获取了新的**AccessToken**,旧的**AccessToken**就认证不了,因为**Redis**中所存放的的**RefreshToken时间戳信息**只会和最新生成的**AccessToken信息中携带的时间戳一致**,这样每个用户就只能使用最新的**AccessToken**认证 61 | > 5. **Redis**的**RefreshToken**也可以用来判断用户是否在线,如果删除**Redis**的某个**RefreshToken**,那这个**RefreshToken**所对应的**AccessToken**之后也无法通过认证了,就相当于控制了用户的登录,可以剔除用户 62 | 63 | ##### 关于根据RefreshToken自动刷新AccessToken 64 | 65 | > 1. 本身**AccessToken的过期时间为5分钟**(配置文件可配置),**RefreshToken过期时间为30分钟**(配置文件可配置) 66 | > 2. 当登录后时间过了5分钟之后,当前**AccessToken**便会过期失效,再次带上**AccessToken**访问**JWT**会抛出**TokenExpiredException**异常说明**Token**过期 67 | > 3. 开始判断是否要**进行AccessToken刷新**,**Redis查询当前用户的RefreshToken是否存在**,**以及这个RefreshToken所携带时间戳**和**过期AccessToken所携带的时间戳**是否**一致** 68 | > 4. **如果存在且一致就进行AccessToken刷新,设置过期时间为5分钟(配置文件可配置),时间戳为当前最新时间戳,同时也设置RefreshToken中的时间戳为当前最新时间戳,刷新过期时间重新为30分钟过期(配置文件可配置)** 69 | > 5. 最终将刷新的**AccessToken**存放在**Response的Header中的Authorization字段**返回(前端进行获取替换,下次用新的**AccessToken**进行访问) 70 | 71 | #### 软件架构 72 | 73 | 1. SpringBoot + Mybatis核心框架 74 | 2. PageHelper插件 + 通用Mapper插件 75 | 3. Shiro + Java-JWT无状态鉴权认证机制 76 | 4. Redis(Jedis)缓存框架 77 | 78 | #### 安装教程 79 | 80 | 1. 数据库帐号密码默认为root,如有修改,请自行修改配置文件application.yml 81 | 2. 解压后执行src\main\resources\sql\MySQL.sql脚本创建数据库和表 82 | 3. Redis需要自行安装Redis服务,端口密码默认 83 | 4. SpringBoot直接启动即可,测试工具PostMan 84 | 85 | #### 使用说明 86 | 87 | ##### Mybatis Generator使用(可视化自定义模板快速生成基础代码:[https://github.com/wang926454/ViewGenerator](https://github.com/wang926454/ViewGenerator)) 88 | 89 | 先配置src\main\resources\generator\generatorConfig.xml文件(默认配置都在原来包的下一级reverse包下),在pom.xml这一级目录(即项目根目录下)的命令行窗口执行(前提是配置了mvn)(IDEA可以直接在Maven窗口Plugins中双击执行) 90 | ```shell 91 | mvn mybatis-generator:generate 92 | ``` 93 | 94 | ##### PostMan使用(Token获取及使用) 95 | 96 | ```java 97 | 先设置Content-Type为application/json 98 | ``` 99 | ![image text](https://docs.dolyw.com/Project/ShiroJwt/image/20181006001.PNG) 100 | ```text 101 | 然后填写请求参数帐号密码信息 102 | ``` 103 | ![image text](https://docs.dolyw.com/Project/ShiroJwt/image/20181006002.PNG) 104 | ```text 105 | 进行请求访问,请求访问成功 106 | ``` 107 | ![image text](https://docs.dolyw.com/Project/ShiroJwt/image/20181006003.PNG) 108 | ```java 109 | 点击查看Header信息的Authorization属性即是Token字段 110 | ``` 111 | ![image text](https://docs.dolyw.com/Project/ShiroJwt/image/20181006004.PNG) 112 | ```java 113 | 访问需要权限的请求将Token字段放在Header信息的Authorization属性访问即可 114 | ``` 115 | ![image text](https://docs.dolyw.com/Project/ShiroJwt/image/20181006005.PNG) 116 | ```java 117 | Token的自动刷新也是在Token失效时返回新的Token在Header信息的Authorization属性 118 | ``` 119 | 120 | #### 搭建参考 121 | 122 | 1. 感谢SmithCruise的Shiro+JWT+Spring Boot Restful简易教程:[https://www.jianshu.com/p/f37f8c295057](https://www.jianshu.com/p/f37f8c295057) 123 | 2. 感谢王洪玉的[Shiro入门](一)使用Redis作为缓存管理器:[https://blog.csdn.net/why15732625998/article/details/78729254](https://blog.csdn.net/why15732625998/article/details/78729254) 124 | 3. 感谢袋🐴饲养员的springboot(七).springboot整合jedis实现redis缓存:[http://www.cnblogs.com/GodHeng/p/9301330.html](http://www.cnblogs.com/GodHeng/p/9301330.html) 125 | 4. 感谢W_Z_W_888的spring注入静态变量的三种方法及其注意事项:[https://blog.csdn.net/W_Z_W_888/article/details/79979103](https://blog.csdn.net/W_Z_W_888/article/details/79979103) 126 | 5. 感谢天降风云的Vue2.0+ElementUI+PageHelper实现的表格分页:[https://blog.csdn.net/u012907049/article/details/70237457](https://blog.csdn.net/u012907049/article/details/70237457) 127 | 6. 感谢yaxx的Vuejs之axios获取Http响应头:[https://segmentfault.com/a/1190000009125333](https://segmentfault.com/a/1190000009125333) 128 | 7. 感谢Twilight的解决使用jwt刷新token带来的问题:[https://segmentfault.com/a/1190000013151506](https://segmentfault.com/a/1190000013151506) 129 | 8. 感谢chuhx的shiro拦截器,返回json数据:[https://blog.csdn.net/chuhx/article/details/51148877](https://blog.csdn.net/chuhx/article/details/51148877) 130 | 9. 感谢yidao620c的Shiro自带拦截器配置规则:[https://github.com/yidao620c/SpringBootBucket/tree/master/springboot-jwt](https://github.com/yidao620c/SpringBootBucket/tree/master/springboot-jwt) 131 | 132 | #### 参与贡献 133 | 134 | 1. Fork 本项目 135 | 2. 新建 Feat_xxx 分支 136 | 3. 提交代码 137 | 4. 新建 Pull Request 138 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.wang 7 | ShiroJwt 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | ShiroJwt 12 | Demo project for Spring Boot 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 1.5.12.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | 1.3.1 26 | 1.1.9 27 | 1.2.3 28 | 1.2.3 29 | 1.2.47 30 | 1.3.2 31 | 3.3.0 32 | 2.9.0 33 | 34 | 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter 39 | 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-starter-test 44 | test 45 | 46 | 47 | 48 | org.springframework.boot 49 | spring-boot-devtools 50 | true 51 | 52 | 53 | 54 | 55 | org.springframework.boot 56 | spring-boot-starter-web 57 | 58 | 59 | 60 | 61 | mysql 62 | mysql-connector-java 63 | 64 | 65 | 66 | 67 | org.mybatis.spring.boot 68 | mybatis-spring-boot-starter 69 | ${mybatis.version} 70 | 71 | 72 | 73 | 74 | com.alibaba 75 | druid-spring-boot-starter 76 | ${druid.version} 77 | 78 | 79 | 80 | 81 | com.github.pagehelper 82 | pagehelper-spring-boot-starter 83 | ${pagehelper.version} 84 | 85 | 86 | 87 | 88 | tk.mybatis 89 | mapper-spring-boot-starter 90 | ${mapper.version} 91 | 92 | 93 | 94 | 95 | com.alibaba 96 | fastjson 97 | ${fastjson.version} 98 | 99 | 100 | 101 | 102 | org.apache.shiro 103 | shiro-spring 104 | ${shiro.version} 105 | 106 | 107 | 108 | 109 | com.auth0 110 | java-jwt 111 | ${jwt.version} 112 | 113 | 114 | 115 | 116 | redis.clients 117 | jedis 118 | ${jedis.version} 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | org.springframework.boot 128 | spring-boot-maven-plugin 129 | 130 | true 131 | 132 | 133 | 134 | 135 | org.apache.maven.plugins 136 | maven-compiler-plugin 137 | 138 | ${java.version} 139 | ${java.version} 140 | ${project.build.sourceEncoding} 141 | 142 | 143 | 144 | 145 | org.apache.maven.plugins 146 | maven-javadoc-plugin 147 | 3.0.0 148 | 149 | 150 | 151 | org.mybatis.generator 152 | mybatis-generator-maven-plugin 153 | 1.3.6 154 | 155 | 156 | src/main/resources/generator/generatorConfig.xml 157 | 158 | true 159 | true 160 | 161 | 162 | 163 | mysql 164 | mysql-connector-java 165 | 5.1.46 166 | 167 | 168 | tk.mybatis 169 | mapper-spring-boot-starter 170 | ${mapper.version} 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /src/main/java/com/wang/Application.java: -------------------------------------------------------------------------------- 1 | package com.wang; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | /** 7 | * 8 | * @author Wang926454 9 | * @date 2018/8/9 15:42 10 | */ 11 | @SpringBootApplication 12 | @tk.mybatis.spring.annotation.MapperScan("com.wang.mapper") 13 | public class Application { 14 | public static void main(String[] args) { 15 | SpringApplication.run(Application.class, args); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/wang/config/ExceptionAdvice.java: -------------------------------------------------------------------------------- 1 | package com.wang.config; 2 | 3 | import com.wang.exception.CustomException; 4 | import com.wang.exception.CustomUnauthorizedException; 5 | import com.wang.model.common.ResponseBean; 6 | import org.apache.shiro.ShiroException; 7 | import org.apache.shiro.authz.UnauthenticatedException; 8 | import org.apache.shiro.authz.UnauthorizedException; 9 | import org.springframework.http.HttpStatus; 10 | import org.springframework.validation.BindException; 11 | import org.springframework.validation.FieldError; 12 | import org.springframework.web.bind.MethodArgumentNotValidException; 13 | import org.springframework.web.bind.annotation.ExceptionHandler; 14 | import org.springframework.web.bind.annotation.ResponseStatus; 15 | import org.springframework.web.bind.annotation.RestControllerAdvice; 16 | import org.springframework.web.servlet.NoHandlerFoundException; 17 | 18 | import javax.servlet.http.HttpServletRequest; 19 | import java.util.ArrayList; 20 | import java.util.HashMap; 21 | import java.util.List; 22 | import java.util.Map; 23 | 24 | /** 25 | * 异常控制处理器 26 | * @author dolyw.com 27 | * @date 2018/8/30 14:02 28 | */ 29 | @RestControllerAdvice 30 | public class ExceptionAdvice { 31 | /** 32 | * 捕捉所有Shiro异常 33 | * @param e 34 | * @return 35 | */ 36 | @ResponseStatus(HttpStatus.UNAUTHORIZED) 37 | @ExceptionHandler(ShiroException.class) 38 | public ResponseBean handle401(ShiroException e) { 39 | return new ResponseBean(HttpStatus.UNAUTHORIZED.value(), "无权访问(Unauthorized):" + e.getMessage(), null); 40 | } 41 | 42 | /** 43 | * 单独捕捉Shiro(UnauthorizedException)异常 44 | * 该异常为访问有权限管控的请求而该用户没有所需权限所抛出的异常 45 | * @param e 46 | * @return 47 | */ 48 | @ResponseStatus(HttpStatus.UNAUTHORIZED) 49 | @ExceptionHandler(UnauthorizedException.class) 50 | public ResponseBean handle401(UnauthorizedException e) { 51 | return new ResponseBean(HttpStatus.UNAUTHORIZED.value(), "无权访问(Unauthorized):当前Subject没有此请求所需权限(" + e.getMessage() + ")", null); 52 | } 53 | 54 | /** 55 | * 单独捕捉Shiro(UnauthenticatedException)异常 56 | * 该异常为以游客身份访问有权限管控的请求无法对匿名主体进行授权,而授权失败所抛出的异常 57 | * @param e 58 | * @return 59 | */ 60 | @ResponseStatus(HttpStatus.UNAUTHORIZED) 61 | @ExceptionHandler(UnauthenticatedException.class) 62 | public ResponseBean handle401(UnauthenticatedException e) { 63 | return new ResponseBean(HttpStatus.UNAUTHORIZED.value(), "无权访问(Unauthorized):当前Subject是匿名Subject,请先登录(This subject is anonymous.)", null); 64 | } 65 | 66 | /** 67 | * 捕捉UnauthorizedException自定义异常 68 | * @return 69 | */ 70 | @ResponseStatus(HttpStatus.UNAUTHORIZED) 71 | @ExceptionHandler(CustomUnauthorizedException.class) 72 | public ResponseBean handle401(CustomUnauthorizedException e) { 73 | return new ResponseBean(HttpStatus.UNAUTHORIZED.value(), "无权访问(Unauthorized):" + e.getMessage(), null); 74 | } 75 | 76 | /** 77 | * 捕捉校验异常(BindException) 78 | * @return 79 | */ 80 | @ResponseStatus(HttpStatus.BAD_REQUEST) 81 | @ExceptionHandler(BindException.class) 82 | public ResponseBean validException(BindException e) { 83 | List fieldErrors = e.getBindingResult().getFieldErrors(); 84 | Map result = this.getValidError(fieldErrors); 85 | return new ResponseBean(HttpStatus.BAD_REQUEST.value(), result.get("errorMsg").toString(), result.get("errorList")); 86 | } 87 | 88 | /** 89 | * 捕捉校验异常(MethodArgumentNotValidException) 90 | * @return 91 | */ 92 | @ResponseStatus(HttpStatus.BAD_REQUEST) 93 | @ExceptionHandler(MethodArgumentNotValidException.class) 94 | public ResponseBean validException(MethodArgumentNotValidException e) { 95 | List fieldErrors = e.getBindingResult().getFieldErrors(); 96 | Map result = this.getValidError(fieldErrors); 97 | return new ResponseBean(HttpStatus.BAD_REQUEST.value(), result.get("errorMsg").toString(), result.get("errorList")); 98 | } 99 | 100 | /** 101 | * 捕捉其他所有自定义异常 102 | * @return 103 | */ 104 | @ResponseStatus(HttpStatus.BAD_REQUEST) 105 | @ExceptionHandler(CustomException.class) 106 | public ResponseBean handle(CustomException e) { 107 | return new ResponseBean(HttpStatus.BAD_REQUEST.value(), e.getMessage(), null); 108 | } 109 | 110 | /** 111 | * 捕捉404异常 112 | * @return 113 | */ 114 | @ResponseStatus(HttpStatus.NOT_FOUND) 115 | @ExceptionHandler(NoHandlerFoundException.class) 116 | public ResponseBean handle(NoHandlerFoundException e) { 117 | return new ResponseBean(HttpStatus.NOT_FOUND.value(), e.getMessage(), null); 118 | } 119 | 120 | /** 121 | * 捕捉其他所有异常 122 | * @param request 123 | * @param ex 124 | * @return 125 | */ 126 | @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) 127 | @ExceptionHandler(Exception.class) 128 | public ResponseBean globalException(HttpServletRequest request, Throwable ex) { 129 | return new ResponseBean(this.getStatus(request).value(), ex.toString() + ": " + ex.getMessage(), null); 130 | } 131 | 132 | /** 133 | * 获取状态码 134 | * @param request 135 | * @return 136 | */ 137 | private HttpStatus getStatus(HttpServletRequest request) { 138 | Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code"); 139 | if (statusCode == null) { 140 | return HttpStatus.INTERNAL_SERVER_ERROR; 141 | } 142 | return HttpStatus.valueOf(statusCode); 143 | } 144 | 145 | /** 146 | * 获取校验错误信息 147 | * @param fieldErrors 148 | * @return 149 | */ 150 | private Map getValidError(List fieldErrors) { 151 | Map result = new HashMap(16); 152 | List errorList = new ArrayList(); 153 | StringBuffer errorMsg = new StringBuffer("校验异常(ValidException):"); 154 | for (FieldError error : fieldErrors) { 155 | errorList.add(error.getField() + "-" + error.getDefaultMessage()); 156 | errorMsg.append(error.getField()).append("-").append(error.getDefaultMessage()).append("."); 157 | } 158 | result.put("errorList", errorList); 159 | result.put("errorMsg", errorMsg); 160 | return result; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/main/java/com/wang/config/OriginFilter.java: -------------------------------------------------------------------------------- 1 | package com.wang.config; 2 | 3 | import org.apache.shiro.web.util.WebUtils; 4 | import org.springframework.stereotype.Component; 5 | 6 | import javax.servlet.*; 7 | import javax.servlet.http.HttpServletRequest; 8 | import javax.servlet.http.HttpServletResponse; 9 | import java.io.IOException; 10 | 11 | /** 12 | * 全局跨域放开 13 | * 14 | * @author wliduo[i@dolyw.com] 15 | * @date 2019/11/26 14:29 16 | */ 17 | @Component 18 | public class OriginFilter implements Filter { 19 | 20 | @Override 21 | public void init(FilterConfig filterConfig) throws ServletException { } 22 | 23 | @Override 24 | public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { 25 | HttpServletRequest httpServletRequest = WebUtils.toHttp(request); 26 | HttpServletResponse httpServletResponse = WebUtils.toHttp(response); 27 | httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE"); 28 | httpServletResponse.setHeader("Access-Control-Max-Age", "3600"); 29 | httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin")); 30 | httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers")); 31 | filterChain.doFilter(request, response); 32 | } 33 | 34 | @Override 35 | public void destroy() { } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/wang/config/redis/JedisConfig.java: -------------------------------------------------------------------------------- 1 | package com.wang.config.redis; 2 | 3 | import com.wang.util.common.StringUtil; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 8 | import org.springframework.boot.context.properties.ConfigurationProperties; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.context.annotation.PropertySource; 12 | import redis.clients.jedis.JedisPool; 13 | import redis.clients.jedis.JedisPoolConfig; 14 | 15 | /** 16 | * Jedis配置,项目启动注入JedisPool 17 | * http://www.cnblogs.com/GodHeng/p/9301330.html 18 | * @author dolyw.com 19 | * @date 2018/9/5 10:35 20 | */ 21 | @Configuration 22 | @EnableAutoConfiguration 23 | @PropertySource("classpath:config.properties") 24 | @ConfigurationProperties(prefix = "redis") 25 | public class JedisConfig { 26 | 27 | /** 28 | * logger 29 | */ 30 | private static final Logger logger = LoggerFactory.getLogger(JedisConfig.class); 31 | 32 | private String host; 33 | 34 | private int port; 35 | 36 | private String password; 37 | 38 | private int timeout; 39 | 40 | @Value("${redis.pool.max-active}") 41 | private int maxActive; 42 | 43 | @Value("${redis.pool.max-wait}") 44 | private int maxWait; 45 | 46 | @Value("${redis.pool.max-idle}") 47 | private int maxIdle; 48 | 49 | @Value("${redis.pool.min-idle}") 50 | private int minIdle; 51 | 52 | @Bean 53 | public JedisPool redisPoolFactory() { 54 | try { 55 | JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); 56 | jedisPoolConfig.setMaxIdle(maxIdle); 57 | jedisPoolConfig.setMaxWaitMillis(maxWait); 58 | jedisPoolConfig.setMaxTotal(maxActive); 59 | jedisPoolConfig.setMinIdle(minIdle); 60 | // 密码为空设置为null 61 | if (StringUtil.isBlank(password)) { 62 | password = null; 63 | } 64 | JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout, password); 65 | logger.info("初始化Redis连接池JedisPool成功!地址: {}:{}", host, port); 66 | return jedisPool; 67 | } catch (Exception e) { 68 | logger.error("初始化Redis连接池JedisPool异常:{}", e.getMessage()); 69 | } 70 | return null; 71 | } 72 | 73 | public String getHost() { 74 | return host; 75 | } 76 | 77 | public void setHost(String host) { 78 | this.host = host; 79 | } 80 | 81 | public int getPort() { 82 | return port; 83 | } 84 | 85 | public void setPort(int port) { 86 | this.port = port; 87 | } 88 | 89 | public String getPassword() { 90 | return password; 91 | } 92 | 93 | public void setPassword(String password) { 94 | this.password = password; 95 | } 96 | 97 | public int getTimeout() { 98 | return timeout; 99 | } 100 | 101 | public void setTimeout(int timeout) { 102 | this.timeout = timeout; 103 | } 104 | 105 | public int getMaxActive() { 106 | return maxActive; 107 | } 108 | 109 | public void setMaxActive(int maxActive) { 110 | this.maxActive = maxActive; 111 | } 112 | 113 | public int getMaxWait() { 114 | return maxWait; 115 | } 116 | 117 | public void setMaxWait(int maxWait) { 118 | this.maxWait = maxWait; 119 | } 120 | 121 | public int getMaxIdle() { 122 | return maxIdle; 123 | } 124 | 125 | public void setMaxIdle(int maxIdle) { 126 | this.maxIdle = maxIdle; 127 | } 128 | 129 | public int getMinIdle() { 130 | return minIdle; 131 | } 132 | 133 | public void setMinIdle(int minIdle) { 134 | this.minIdle = minIdle; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/main/java/com/wang/config/shiro/ShiroConfig.java: -------------------------------------------------------------------------------- 1 | package com.wang.config.shiro; 2 | 3 | import com.wang.config.shiro.jwt.JwtFilter; 4 | import com.wang.config.shiro.cache.CustomCacheManager; 5 | import org.apache.shiro.mgt.DefaultSessionStorageEvaluator; 6 | import org.apache.shiro.mgt.DefaultSubjectDAO; 7 | import org.apache.shiro.spring.LifecycleBeanPostProcessor; 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.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; 12 | import org.springframework.context.annotation.Bean; 13 | import org.springframework.context.annotation.Configuration; 14 | import org.springframework.context.annotation.DependsOn; 15 | 16 | import javax.servlet.Filter; 17 | import java.util.HashMap; 18 | import java.util.LinkedHashMap; 19 | import java.util.Map; 20 | 21 | /** 22 | * Shiro配置 23 | * @author dolyw.com 24 | * @date 2018/8/30 15:49 25 | */ 26 | @Configuration 27 | public class ShiroConfig { 28 | 29 | /** 30 | * 配置使用自定义Realm,关闭Shiro自带的session 31 | * 详情见文档 http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29 32 | * @param userRealm 33 | * @return org.apache.shiro.web.mgt.DefaultWebSecurityManager 34 | * @author dolyw.com 35 | * @date 2018/8/31 10:55 36 | */ 37 | @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") 38 | @Bean("securityManager") 39 | public DefaultWebSecurityManager defaultWebSecurityManager(UserRealm userRealm) { 40 | DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); 41 | // 使用自定义Realm 42 | defaultWebSecurityManager.setRealm(userRealm); 43 | // 关闭Shiro自带的session 44 | DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO(); 45 | DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator(); 46 | defaultSessionStorageEvaluator.setSessionStorageEnabled(false); 47 | subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator); 48 | defaultWebSecurityManager.setSubjectDAO(subjectDAO); 49 | // 设置自定义Cache缓存 50 | defaultWebSecurityManager.setCacheManager(new CustomCacheManager()); 51 | return defaultWebSecurityManager; 52 | } 53 | 54 | /** 55 | * 添加自己的过滤器,自定义url规则 56 | * Shiro自带拦截器配置规则 57 | * rest:比如/admins/user/**=rest[user],根据请求的方法,相当于/admins/user/**=perms[user:method] ,其中method为post,get,delete等 58 | * port:比如/admins/user/**=port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString是你访问的url里的?后面的参数 59 | * perms:比如/admins/user/**=perms[user:add:*],perms参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,比如/admins/user/**=perms["user:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法 60 | * roles:比如/admins/user/**=roles[admin],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,比如/admins/user/**=roles["admin,guest"],每个参数通过才算通过,相当于hasAllRoles()方法。//要实现or的效果看http://zgzty.blog.163.com/blog/static/83831226201302983358670/ 61 | * anon:比如/admins/**=anon 没有参数,表示可以匿名使用 62 | * authc:比如/admins/user/**=authc表示需要认证才能使用,没有参数 63 | * authcBasic:比如/admins/user/**=authcBasic没有参数表示httpBasic认证 64 | * ssl:比如/admins/user/**=ssl没有参数,表示安全的url请求,协议为https 65 | * user:比如/admins/user/**=user没有参数表示必须存在用户,当登入操作时不做检查 66 | * 详情见文档 http://shiro.apache.org/web.html#urls- 67 | * @param securityManager 68 | * @return org.apache.shiro.spring.web.ShiroFilterFactoryBean 69 | * @author dolyw.com 70 | * @date 2018/8/31 10:57 71 | */ 72 | @Bean("shiroFilter") 73 | public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) { 74 | ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); 75 | // 添加自己的过滤器取名为jwt 76 | Map filterMap = new HashMap<>(16); 77 | filterMap.put("jwt", new JwtFilter()); 78 | factoryBean.setFilters(filterMap); 79 | factoryBean.setSecurityManager(securityManager); 80 | // 自定义url规则使用LinkedHashMap有序Map 81 | LinkedHashMap filterChainDefinitionMap = new LinkedHashMap(16); 82 | // Swagger接口文档 83 | // filterChainDefinitionMap.put("/v2/api-docs", "anon"); 84 | // filterChainDefinitionMap.put("/webjars/**", "anon"); 85 | // filterChainDefinitionMap.put("/swagger-resources/**", "anon"); 86 | // filterChainDefinitionMap.put("/swagger-ui.html", "anon"); 87 | // filterChainDefinitionMap.put("/doc.html", "anon"); 88 | // 公开接口 89 | // filterChainDefinitionMap.put("/api/**", "anon"); 90 | // 登录接口放开 91 | filterChainDefinitionMap.put("/user/login", "anon"); 92 | // 所有请求通过我们自己的JWTFilter 93 | filterChainDefinitionMap.put("/**", "jwt"); 94 | factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); 95 | return factoryBean; 96 | } 97 | 98 | /** 99 | * 下面的代码是添加注解支持 100 | */ 101 | @Bean 102 | @DependsOn("lifecycleBeanPostProcessor") 103 | public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { 104 | DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); 105 | // 强制使用cglib,防止重复代理和可能引起代理出错的问题,https://zhuanlan.zhihu.com/p/29161098 106 | defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); 107 | return defaultAdvisorAutoProxyCreator; 108 | } 109 | 110 | @Bean 111 | public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { 112 | return new LifecycleBeanPostProcessor(); 113 | } 114 | 115 | @Bean 116 | public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { 117 | AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); 118 | advisor.setSecurityManager(securityManager); 119 | return advisor; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/com/wang/config/shiro/UserRealm.java: -------------------------------------------------------------------------------- 1 | package com.wang.config.shiro; 2 | 3 | import com.wang.config.shiro.jwt.JwtToken; 4 | import com.wang.util.JedisUtil; 5 | import com.wang.mapper.PermissionMapper; 6 | import com.wang.mapper.RoleMapper; 7 | import com.wang.mapper.UserMapper; 8 | import com.wang.model.PermissionDto; 9 | import com.wang.model.RoleDto; 10 | import com.wang.model.UserDto; 11 | import com.wang.model.common.Constant; 12 | import com.wang.util.JwtUtil; 13 | import com.wang.util.common.StringUtil; 14 | import org.apache.shiro.authc.AuthenticationException; 15 | import org.apache.shiro.authc.AuthenticationInfo; 16 | import org.apache.shiro.authc.AuthenticationToken; 17 | import org.apache.shiro.authc.SimpleAuthenticationInfo; 18 | import org.apache.shiro.authz.AuthorizationInfo; 19 | import org.apache.shiro.authz.SimpleAuthorizationInfo; 20 | import org.apache.shiro.realm.AuthorizingRealm; 21 | import org.apache.shiro.subject.PrincipalCollection; 22 | import org.springframework.beans.factory.annotation.Autowired; 23 | import org.springframework.stereotype.Service; 24 | 25 | import java.util.List; 26 | 27 | /** 28 | * 自定义Realm 29 | * @author dolyw.com 30 | * @date 2018/8/30 14:10 31 | */ 32 | @Service 33 | public class UserRealm extends AuthorizingRealm { 34 | 35 | private final UserMapper userMapper; 36 | private final RoleMapper roleMapper; 37 | private final PermissionMapper permissionMapper; 38 | 39 | @Autowired 40 | public UserRealm(UserMapper userMapper, RoleMapper roleMapper, PermissionMapper permissionMapper) { 41 | this.userMapper = userMapper; 42 | this.roleMapper = roleMapper; 43 | this.permissionMapper = permissionMapper; 44 | } 45 | 46 | /** 47 | * 大坑,必须重写此方法,不然Shiro会报错 48 | */ 49 | @Override 50 | public boolean supports(AuthenticationToken authenticationToken) { 51 | return authenticationToken instanceof JwtToken; 52 | } 53 | 54 | /** 55 | * 只有当需要检测用户权限的时候才会调用此方法,例如checkRole,checkPermission之类的 56 | */ 57 | @Override 58 | protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { 59 | SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); 60 | String account = JwtUtil.getClaim(principalCollection.toString(), Constant.ACCOUNT); 61 | UserDto userDto = new UserDto(); 62 | userDto.setAccount(account); 63 | // 查询用户角色 64 | List roleDtos = roleMapper.findRoleByUser(userDto); 65 | for (RoleDto roleDto : roleDtos) { 66 | if (roleDto != null) { 67 | // 添加角色 68 | simpleAuthorizationInfo.addRole(roleDto.getName()); 69 | // 根据用户角色查询权限 70 | List permissionDtos = permissionMapper.findPermissionByRole(roleDto); 71 | for (PermissionDto permissionDto : permissionDtos) { 72 | if (permissionDto != null) { 73 | // 添加权限 74 | simpleAuthorizationInfo.addStringPermission(permissionDto.getPerCode()); 75 | } 76 | } 77 | } 78 | } 79 | return simpleAuthorizationInfo; 80 | } 81 | 82 | /** 83 | * 默认使用此方法进行用户名正确与否验证,错误抛出异常即可。 84 | */ 85 | @Override 86 | protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { 87 | String token = (String) authenticationToken.getCredentials(); 88 | // 解密获得account,用于和数据库进行对比 89 | String account = JwtUtil.getClaim(token, Constant.ACCOUNT); 90 | // 帐号为空 91 | if (StringUtil.isBlank(account)) { 92 | throw new AuthenticationException("Token中帐号为空(The account in Token is empty.)"); 93 | } 94 | // 查询用户是否存在 95 | UserDto userDto = new UserDto(); 96 | userDto.setAccount(account); 97 | userDto = userMapper.selectOne(userDto); 98 | if (userDto == null) { 99 | throw new AuthenticationException("该帐号不存在(The account does not exist.)"); 100 | } 101 | // 开始认证,要AccessToken认证通过,且Redis中存在RefreshToken,且两个Token时间戳一致 102 | if (JwtUtil.verify(token) && JedisUtil.exists(Constant.PREFIX_SHIRO_REFRESH_TOKEN + account)) { 103 | // 获取RefreshToken的时间戳 104 | String currentTimeMillisRedis = JedisUtil.getObject(Constant.PREFIX_SHIRO_REFRESH_TOKEN + account).toString(); 105 | // 获取AccessToken时间戳,与RefreshToken的时间戳对比 106 | if (JwtUtil.getClaim(token, Constant.CURRENT_TIME_MILLIS).equals(currentTimeMillisRedis)) { 107 | return new SimpleAuthenticationInfo(token, token, "userRealm"); 108 | } 109 | } 110 | throw new AuthenticationException("Token已过期(Token expired or incorrect.)"); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/com/wang/config/shiro/cache/CustomCache.java: -------------------------------------------------------------------------------- 1 | package com.wang.config.shiro.cache; 2 | 3 | import com.wang.util.JwtUtil; 4 | import com.wang.util.JedisUtil; 5 | import com.wang.model.common.Constant; 6 | import com.wang.util.common.PropertiesUtil; 7 | import com.wang.util.common.SerializableUtil; 8 | import org.apache.shiro.cache.Cache; 9 | import org.apache.shiro.cache.CacheException; 10 | 11 | import java.util.*; 12 | 13 | /** 14 | * 重写Shiro的Cache保存读取 15 | * @author dolyw.com 16 | * @date 2018/9/4 17:31 17 | */ 18 | public class CustomCache implements Cache { 19 | 20 | /** 21 | * 缓存的key名称获取为shiro:cache:account 22 | * @param key 23 | * @return java.lang.String 24 | * @author dolyw.com 25 | * @date 2018/9/4 18:33 26 | */ 27 | private String getKey(Object key) { 28 | return Constant.PREFIX_SHIRO_CACHE + JwtUtil.getClaim(key.toString(), Constant.ACCOUNT); 29 | } 30 | 31 | /** 32 | * 获取缓存 33 | */ 34 | @Override 35 | public Object get(Object key) throws CacheException { 36 | if(Boolean.FALSE.equals(JedisUtil.exists(this.getKey(key)))){ 37 | return null; 38 | } 39 | return JedisUtil.getObject(this.getKey(key)); 40 | } 41 | 42 | /** 43 | * 保存缓存 44 | */ 45 | @Override 46 | public Object put(Object key, Object value) throws CacheException { 47 | // 读取配置文件,获取Redis的Shiro缓存过期时间 48 | PropertiesUtil.readProperties("config.properties"); 49 | String shiroCacheExpireTime = PropertiesUtil.getProperty("shiroCacheExpireTime"); 50 | // 设置Redis的Shiro缓存 51 | return JedisUtil.setObject(this.getKey(key), value, Integer.parseInt(shiroCacheExpireTime)); 52 | } 53 | 54 | /** 55 | * 移除缓存 56 | */ 57 | @Override 58 | public Object remove(Object key) throws CacheException { 59 | if(Boolean.FALSE.equals(JedisUtil.exists(this.getKey(key)))){ 60 | return null; 61 | } 62 | JedisUtil.delKey(this.getKey(key)); 63 | return null; 64 | } 65 | 66 | /** 67 | * 清空所有缓存 68 | */ 69 | @Override 70 | public void clear() throws CacheException { 71 | Objects.requireNonNull(JedisUtil.getJedis()).flushDB(); 72 | } 73 | 74 | /** 75 | * 缓存的个数 76 | */ 77 | @Override 78 | public int size() { 79 | Long size = Objects.requireNonNull(JedisUtil.getJedis()).dbSize(); 80 | return size.intValue(); 81 | } 82 | 83 | /** 84 | * 获取所有的key 85 | */ 86 | @Override 87 | public Set keys() { 88 | Set keys = Objects.requireNonNull(JedisUtil.getJedis()).keys("*".getBytes()); 89 | Set set = new HashSet(); 90 | for (byte[] bs : keys) { 91 | set.add(SerializableUtil.unserializable(bs)); 92 | } 93 | return set; 94 | } 95 | 96 | /** 97 | * 获取所有的value 98 | */ 99 | @Override 100 | public Collection values() { 101 | Set keys = this.keys(); 102 | List values = new ArrayList(); 103 | for (Object key : keys) { 104 | values.add(JedisUtil.getObject(this.getKey(key))); 105 | } 106 | return values; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/com/wang/config/shiro/cache/CustomCacheManager.java: -------------------------------------------------------------------------------- 1 | package com.wang.config.shiro.cache; 2 | 3 | import org.apache.shiro.cache.Cache; 4 | import org.apache.shiro.cache.CacheException; 5 | import org.apache.shiro.cache.CacheManager; 6 | 7 | /** 8 | * 重写Shiro缓存管理器 9 | * @author dolyw.com 10 | * @date 2018/9/4 17:41 11 | */ 12 | public class CustomCacheManager implements CacheManager { 13 | @Override 14 | public Cache getCache(String s) throws CacheException { 15 | return new CustomCache(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/wang/config/shiro/jwt/JwtFilter.java: -------------------------------------------------------------------------------- 1 | package com.wang.config.shiro.jwt; 2 | 3 | import com.auth0.jwt.exceptions.SignatureVerificationException; 4 | import com.auth0.jwt.exceptions.TokenExpiredException; 5 | import com.wang.exception.CustomException; 6 | import com.wang.model.common.Constant; 7 | import com.wang.model.common.ResponseBean; 8 | import com.wang.util.JedisUtil; 9 | import com.wang.util.JwtUtil; 10 | import com.wang.util.common.JsonConvertUtil; 11 | import com.wang.util.common.PropertiesUtil; 12 | import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter; 13 | import org.apache.shiro.web.util.WebUtils; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | import org.springframework.http.HttpStatus; 17 | import org.springframework.web.bind.annotation.RequestMethod; 18 | 19 | import javax.servlet.ServletRequest; 20 | import javax.servlet.ServletResponse; 21 | import javax.servlet.http.HttpServletRequest; 22 | import javax.servlet.http.HttpServletResponse; 23 | import java.io.IOException; 24 | import java.io.PrintWriter; 25 | 26 | /** 27 | * JWT过滤 28 | * @author dolyw.com 29 | * @date 2018/8/30 15:47 30 | */ 31 | public class JwtFilter extends BasicHttpAuthenticationFilter { 32 | /** 33 | * logger 34 | */ 35 | private static final Logger logger = LoggerFactory.getLogger(JwtFilter.class); 36 | 37 | /** 38 | * 这里我们详细说明下为什么最终返回的都是true,即允许访问 39 | * 例如我们提供一个地址 GET /article 40 | * 登入用户和游客看到的内容是不同的 41 | * 如果在这里返回了false,请求会被直接拦截,用户看不到任何东西 42 | * 所以我们在这里返回true,Controller中可以通过 subject.isAuthenticated() 来判断用户是否登入 43 | * 如果有些资源只有登入用户才能访问,我们只需要在方法上面加上 @RequiresAuthentication 注解即可 44 | * 但是这样做有一个缺点,就是不能够对GET,POST等请求进行分别过滤鉴权(因为我们重写了官方的方法),但实际上对应用影响不大 45 | */ 46 | @Override 47 | protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { 48 | // 查看当前Header中是否携带Authorization属性(Token),有的话就进行登录认证授权 49 | if (this.isLoginAttempt(request, response)) { 50 | try { 51 | // 进行Shiro的登录UserRealm 52 | this.executeLogin(request, response); 53 | } catch (Exception e) { 54 | // 认证出现异常,传递错误信息msg 55 | String msg = e.getMessage(); 56 | // 获取应用异常(该Cause是导致抛出此throwable(异常)的throwable(异常)) 57 | Throwable throwable = e.getCause(); 58 | if (throwable instanceof SignatureVerificationException) { 59 | // 该异常为JWT的AccessToken认证失败(Token或者密钥不正确) 60 | msg = "Token或者密钥不正确(" + throwable.getMessage() + ")"; 61 | } else if (throwable instanceof TokenExpiredException) { 62 | // 该异常为JWT的AccessToken已过期,判断RefreshToken未过期就进行AccessToken刷新 63 | if (this.refreshToken(request, response)) { 64 | return true; 65 | } else { 66 | msg = "Token已过期(" + throwable.getMessage() + ")"; 67 | } 68 | } else { 69 | // 应用异常不为空 70 | if (throwable != null) { 71 | // 获取应用异常msg 72 | msg = throwable.getMessage(); 73 | } 74 | } 75 | // Token认证失败直接返回Response信息 76 | this.response401(response, msg); 77 | return false; 78 | } 79 | } else { 80 | // 没有携带Token 81 | HttpServletRequest httpServletRequest = WebUtils.toHttp(request); 82 | // 获取当前请求类型 83 | String httpMethod = httpServletRequest.getMethod(); 84 | // 获取当前请求URI 85 | String requestURI = httpServletRequest.getRequestURI(); 86 | logger.info("当前请求 {} Authorization属性(Token)为空 请求类型 {}", requestURI, httpMethod); 87 | // mustLoginFlag = true 开启任何请求必须登录才可访问 88 | final Boolean mustLoginFlag = false; 89 | if (mustLoginFlag) { 90 | this.response401(response, "请先登录"); 91 | return false; 92 | } 93 | } 94 | return true; 95 | } 96 | 97 | /** 98 | * 这里我们详细说明下为什么重写 99 | * 可以对比父类方法,只是将executeLogin方法调用去除了 100 | * 如果没有去除将会循环调用doGetAuthenticationInfo方法 101 | */ 102 | @Override 103 | protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { 104 | this.sendChallenge(request, response); 105 | return false; 106 | } 107 | 108 | /** 109 | * 检测Header里面是否包含Authorization字段,有就进行Token登录认证授权 110 | */ 111 | @Override 112 | protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) { 113 | // 拿到当前Header中Authorization的AccessToken(Shiro中getAuthzHeader方法已经实现) 114 | String token = this.getAuthzHeader(request); 115 | return token != null; 116 | } 117 | 118 | /** 119 | * 进行AccessToken登录认证授权 120 | */ 121 | @Override 122 | protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception { 123 | // 拿到当前Header中Authorization的AccessToken(Shiro中getAuthzHeader方法已经实现) 124 | JwtToken token = new JwtToken(this.getAuthzHeader(request)); 125 | // 提交给UserRealm进行认证,如果错误他会抛出异常并被捕获 126 | this.getSubject(request, response).login(token); 127 | // 如果没有抛出异常则代表登入成功,返回true 128 | return true; 129 | } 130 | 131 | /** 132 | * 对跨域提供支持 133 | */ 134 | @Override 135 | protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception { 136 | // 跨域已经在OriginFilter处全局配置 137 | /*HttpServletRequest httpServletRequest = WebUtils.toHttp(request); 138 | HttpServletResponse httpServletResponse = WebUtils.toHttp(response); 139 | httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin")); 140 | httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE"); 141 | httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers")); 142 | // 跨域时会首先发送一个OPTIONS请求,这里我们给OPTIONS请求直接返回正常状态 143 | if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) { 144 | httpServletResponse.setStatus(HttpStatus.OK.value()); 145 | return false; 146 | }*/ 147 | return super.preHandle(request, response); 148 | } 149 | 150 | /** 151 | * 此处为AccessToken刷新,进行判断RefreshToken是否过期,未过期就返回新的AccessToken且继续正常访问 152 | */ 153 | private boolean refreshToken(ServletRequest request, ServletResponse response) { 154 | // 拿到当前Header中Authorization的AccessToken(Shiro中getAuthzHeader方法已经实现) 155 | String token = this.getAuthzHeader(request); 156 | // 获取当前Token的帐号信息 157 | String account = JwtUtil.getClaim(token, Constant.ACCOUNT); 158 | // 判断Redis中RefreshToken是否存在 159 | if (JedisUtil.exists(Constant.PREFIX_SHIRO_REFRESH_TOKEN + account)) { 160 | // Redis中RefreshToken还存在,获取RefreshToken的时间戳 161 | String currentTimeMillisRedis = JedisUtil.getObject(Constant.PREFIX_SHIRO_REFRESH_TOKEN + account).toString(); 162 | // 获取当前AccessToken中的时间戳,与RefreshToken的时间戳对比,如果当前时间戳一致,进行AccessToken刷新 163 | if (JwtUtil.getClaim(token, Constant.CURRENT_TIME_MILLIS).equals(currentTimeMillisRedis)) { 164 | // 获取当前最新时间戳 165 | String currentTimeMillis = String.valueOf(System.currentTimeMillis()); 166 | // 读取配置文件,获取refreshTokenExpireTime属性 167 | PropertiesUtil.readProperties("config.properties"); 168 | String refreshTokenExpireTime = PropertiesUtil.getProperty("refreshTokenExpireTime"); 169 | // 设置RefreshToken中的时间戳为当前最新时间戳,且刷新过期时间重新为30分钟过期(配置文件可配置refreshTokenExpireTime属性) 170 | JedisUtil.setObject(Constant.PREFIX_SHIRO_REFRESH_TOKEN + account, currentTimeMillis, Integer.parseInt(refreshTokenExpireTime)); 171 | // 刷新AccessToken,设置时间戳为当前最新时间戳 172 | token = JwtUtil.sign(account, currentTimeMillis); 173 | // 将新刷新的AccessToken再次进行Shiro的登录 174 | JwtToken jwtToken = new JwtToken(token); 175 | // 提交给UserRealm进行认证,如果错误他会抛出异常并被捕获,如果没有抛出异常则代表登入成功,返回true 176 | this.getSubject(request, response).login(jwtToken); 177 | // 最后将刷新的AccessToken存放在Response的Header中的Authorization字段返回 178 | HttpServletResponse httpServletResponse = WebUtils.toHttp(response); 179 | httpServletResponse.setHeader("Authorization", token); 180 | httpServletResponse.setHeader("Access-Control-Expose-Headers", "Authorization"); 181 | return true; 182 | } 183 | } 184 | return false; 185 | } 186 | 187 | /** 188 | * 无需转发,直接返回Response信息 189 | */ 190 | private void response401(ServletResponse response, String msg) { 191 | HttpServletResponse httpServletResponse = WebUtils.toHttp(response); 192 | httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value()); 193 | httpServletResponse.setCharacterEncoding("UTF-8"); 194 | httpServletResponse.setContentType("application/json; charset=utf-8"); 195 | try (PrintWriter out = httpServletResponse.getWriter()) { 196 | String data = JsonConvertUtil.objectToJson(new ResponseBean(HttpStatus.UNAUTHORIZED.value(), "无权访问(Unauthorized):" + msg, null)); 197 | out.append(data); 198 | } catch (IOException e) { 199 | logger.error("直接返回Response信息出现IOException异常:{}", e.getMessage()); 200 | throw new CustomException("直接返回Response信息出现IOException异常:" + e.getMessage()); 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/main/java/com/wang/config/shiro/jwt/JwtToken.java: -------------------------------------------------------------------------------- 1 | package com.wang.config.shiro.jwt; 2 | 3 | import org.apache.shiro.authc.AuthenticationToken; 4 | 5 | /** 6 | * JwtToken 7 | * @author dolyw.com 8 | * @date 2018/8/30 14:06 9 | */ 10 | public class JwtToken implements AuthenticationToken { 11 | /** 12 | * Token 13 | */ 14 | private String token; 15 | 16 | public JwtToken(String token) { 17 | this.token = token; 18 | } 19 | 20 | @Override 21 | public Object getPrincipal() { 22 | return token; 23 | } 24 | 25 | @Override 26 | public Object getCredentials() { 27 | return token; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/wang/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package com.wang.controller; 2 | 3 | import com.github.pagehelper.PageHelper; 4 | import com.github.pagehelper.PageInfo; 5 | import com.wang.model.common.BaseDto; 6 | import com.wang.model.valid.group.UserEditValidGroup; 7 | import com.wang.model.valid.group.UserLoginValidGroup; 8 | import com.wang.util.JedisUtil; 9 | import com.wang.exception.CustomException; 10 | import com.wang.exception.CustomUnauthorizedException; 11 | import com.wang.model.UserDto; 12 | import com.wang.model.common.Constant; 13 | import com.wang.model.common.ResponseBean; 14 | import com.wang.service.IUserService; 15 | import com.wang.util.AesCipherUtil; 16 | import com.wang.util.JwtUtil; 17 | import com.wang.util.UserUtil; 18 | import com.wang.util.common.StringUtil; 19 | import org.apache.shiro.SecurityUtils; 20 | import org.apache.shiro.authz.annotation.Logical; 21 | import org.apache.shiro.authz.annotation.RequiresAuthentication; 22 | import org.apache.shiro.authz.annotation.RequiresPermissions; 23 | import org.apache.shiro.subject.Subject; 24 | import org.springframework.beans.factory.annotation.Autowired; 25 | import org.springframework.beans.factory.annotation.Value; 26 | import org.springframework.context.annotation.PropertySource; 27 | import org.springframework.http.HttpStatus; 28 | import org.springframework.validation.annotation.Validated; 29 | import org.springframework.web.bind.annotation.*; 30 | 31 | import javax.servlet.http.HttpServletResponse; 32 | import java.util.*; 33 | 34 | /** 35 | * UserController 36 | * @author dolyw.com 37 | * @date 2018/8/29 15:45 38 | */ 39 | @RestController 40 | @RequestMapping("/user") 41 | @PropertySource("classpath:config.properties") 42 | public class UserController { 43 | 44 | /** 45 | * RefreshToken过期时间 46 | */ 47 | @Value("${refreshTokenExpireTime}") 48 | private String refreshTokenExpireTime; 49 | 50 | private final UserUtil userUtil; 51 | 52 | private final IUserService userService; 53 | 54 | @Autowired 55 | public UserController(UserUtil userUtil, IUserService userService) { 56 | this.userUtil = userUtil; 57 | this.userService = userService; 58 | } 59 | 60 | /** 61 | * 获取用户列表 62 | * @param 63 | * @return java.util.Map 64 | * @author dolyw.com 65 | * @date 2018/8/30 10:41 66 | */ 67 | @GetMapping 68 | @RequiresPermissions(logical = Logical.AND, value = {"user:view"}) 69 | public ResponseBean user(@Validated BaseDto baseDto) { 70 | if (baseDto.getPage() == null || baseDto.getRows() == null) { 71 | baseDto.setPage(1); 72 | baseDto.setRows(10); 73 | } 74 | PageHelper.startPage(baseDto.getPage(), baseDto.getRows()); 75 | List userDtos = userService.selectAll(); 76 | PageInfo selectPage = new PageInfo(userDtos); 77 | if (userDtos == null || userDtos.size() < 0) { 78 | throw new CustomException("查询失败(Query Failure)"); 79 | } 80 | Map result = new HashMap(16); 81 | result.put("count", selectPage.getTotal()); 82 | result.put("data", selectPage.getList()); 83 | return new ResponseBean(HttpStatus.OK.value(), "查询成功(Query was successful)", result); 84 | } 85 | 86 | /** 87 | * 获取在线用户(查询Redis中的RefreshToken) 88 | * @param 89 | * @return com.wang.model.common.ResponseBean 90 | * @author dolyw.com 91 | * @date 2018/9/6 9:58 92 | */ 93 | @GetMapping("/online") 94 | @RequiresPermissions(logical = Logical.AND, value = {"user:view"}) 95 | public ResponseBean online() { 96 | List userDtos = new ArrayList(); 97 | // 查询所有Redis键 98 | Set keys = JedisUtil.keysS(Constant.PREFIX_SHIRO_REFRESH_TOKEN + "*"); 99 | for (String key : keys) { 100 | if (JedisUtil.exists(key)) { 101 | // 根据:分割key,获取最后一个字符(帐号) 102 | String[] strArray = key.split(":"); 103 | UserDto userDto = new UserDto(); 104 | userDto.setAccount(strArray[strArray.length - 1]); 105 | userDto = userService.selectOne(userDto); 106 | // 设置登录时间 107 | userDto.setLoginTime(new Date(Long.parseLong(JedisUtil.getObject(key).toString()))); 108 | userDtos.add(userDto); 109 | } 110 | } 111 | if (userDtos == null || userDtos.size() < 0) { 112 | throw new CustomException("查询失败(Query Failure)"); 113 | } 114 | return new ResponseBean(HttpStatus.OK.value(), "查询成功(Query was successful)", userDtos); 115 | } 116 | 117 | /** 118 | * 登录授权 119 | * @param userDto 120 | * @return com.wang.model.common.ResponseBean 121 | * @author dolyw.com 122 | * @date 2018/8/30 16:21 123 | */ 124 | @PostMapping("/login") 125 | public ResponseBean login(@Validated(UserLoginValidGroup.class) @RequestBody UserDto userDto, HttpServletResponse httpServletResponse) { 126 | // 查询数据库中的帐号信息 127 | UserDto userDtoTemp = new UserDto(); 128 | userDtoTemp.setAccount(userDto.getAccount()); 129 | userDtoTemp = userService.selectOne(userDtoTemp); 130 | if (userDtoTemp == null) { 131 | throw new CustomUnauthorizedException("该帐号不存在(The account does not exist.)"); 132 | } 133 | // 密码进行AES解密 134 | String key = AesCipherUtil.deCrypto(userDtoTemp.getPassword()); 135 | // 因为密码加密是以帐号+密码的形式进行加密的,所以解密后的对比是帐号+密码 136 | if (key.equals(userDto.getAccount() + userDto.getPassword())) { 137 | // 清除可能存在的Shiro权限信息缓存 138 | if (JedisUtil.exists(Constant.PREFIX_SHIRO_CACHE + userDto.getAccount())) { 139 | JedisUtil.delKey(Constant.PREFIX_SHIRO_CACHE + userDto.getAccount()); 140 | } 141 | // 设置RefreshToken,时间戳为当前时间戳,直接设置即可(不用先删后设,会覆盖已有的RefreshToken) 142 | String currentTimeMillis = String.valueOf(System.currentTimeMillis()); 143 | JedisUtil.setObject(Constant.PREFIX_SHIRO_REFRESH_TOKEN + userDto.getAccount(), currentTimeMillis, Integer.parseInt(refreshTokenExpireTime)); 144 | // 从Header中Authorization返回AccessToken,时间戳为当前时间戳 145 | String token = JwtUtil.sign(userDto.getAccount(), currentTimeMillis); 146 | httpServletResponse.setHeader("Authorization", token); 147 | httpServletResponse.setHeader("Access-Control-Expose-Headers", "Authorization"); 148 | return new ResponseBean(HttpStatus.OK.value(), "登录成功(Login Success.)", null); 149 | } else { 150 | throw new CustomUnauthorizedException("帐号或密码错误(Account or Password Error.)"); 151 | } 152 | } 153 | 154 | /** 155 | * 测试登录 156 | * @param 157 | * @return com.wang.model.common.ResponseBean 158 | * @author dolyw.com 159 | * @date 2018/8/30 16:18 160 | */ 161 | @GetMapping("/article") 162 | public ResponseBean article() { 163 | Subject subject = SecurityUtils.getSubject(); 164 | // 登录了返回true 165 | if (subject.isAuthenticated()) { 166 | return new ResponseBean(HttpStatus.OK.value(), "您已经登录了(You are already logged in)", null); 167 | } else { 168 | return new ResponseBean(HttpStatus.OK.value(), "你是游客(You are guest)", null); 169 | } 170 | } 171 | 172 | /** 173 | * 测试登录注解(@RequiresAuthentication和subject.isAuthenticated()返回true一个性质) 174 | * @param 175 | * @return com.wang.model.common.ResponseBean 176 | * @author dolyw.com 177 | * @date 2018/8/30 16:18 178 | */ 179 | @GetMapping("/article2") 180 | @RequiresAuthentication 181 | public ResponseBean requireAuth() { 182 | return new ResponseBean(HttpStatus.OK.value(), "您已经登录了(You are already logged in)", null); 183 | } 184 | 185 | /** 186 | * 获取当前登录用户信息 187 | * @param 188 | * @return com.wang.model.common.ResponseBean 189 | * @author dolyw.com 190 | * @date 2019/3/15 11:51 191 | */ 192 | @GetMapping("/info") 193 | @RequiresAuthentication 194 | public ResponseBean info() { 195 | // 获取当前登录用户 196 | UserDto userDto = userUtil.getUser(); 197 | // 获取当前登录用户Id 198 | Integer id = userUtil.getUserId(); 199 | // 获取当前登录用户Token 200 | String token = userUtil.getToken(); 201 | // 获取当前登录用户Account 202 | String account = userUtil.getAccount(); 203 | return new ResponseBean(HttpStatus.OK.value(), "您已经登录了(You are already logged in)", userDto); 204 | } 205 | 206 | /** 207 | * 获取指定用户 208 | * @param id 209 | * @return java.util.Map 210 | * @author dolyw.com 211 | * @date 2018/8/30 10:42 212 | */ 213 | @GetMapping("/{id}") 214 | @RequiresPermissions(logical = Logical.AND, value = {"user:view"}) 215 | public ResponseBean findById(@PathVariable("id") Integer id) { 216 | UserDto userDto = userService.selectByPrimaryKey(id); 217 | if (userDto == null) { 218 | throw new CustomException("查询失败(Query Failure)"); 219 | } 220 | return new ResponseBean(HttpStatus.OK.value(), "查询成功(Query was successful)", userDto); 221 | } 222 | 223 | /** 224 | * 新增用户 225 | * @param userDto 226 | * @return java.util.Map 227 | * @author dolyw.com 228 | * @date 2018/8/30 10:42 229 | */ 230 | @PostMapping 231 | @RequiresPermissions(logical = Logical.AND, value = {"user:edit"}) 232 | public ResponseBean add(@Validated(UserEditValidGroup.class) @RequestBody UserDto userDto) { 233 | // 判断当前帐号是否存在 234 | UserDto userDtoTemp = new UserDto(); 235 | userDtoTemp.setAccount(userDto.getAccount()); 236 | userDtoTemp = userService.selectOne(userDtoTemp); 237 | if (userDtoTemp != null && StringUtil.isNotBlank(userDtoTemp.getPassword())) { 238 | throw new CustomUnauthorizedException("该帐号已存在(Account exist.)"); 239 | } 240 | userDto.setRegTime(new Date()); 241 | // 密码以帐号+密码的形式进行AES加密 242 | if (userDto.getPassword().length() > Constant.PASSWORD_MAX_LEN) { 243 | throw new CustomException("密码最多8位(Password up to 8 bits.)"); 244 | } 245 | String key = AesCipherUtil.enCrypto(userDto.getAccount() + userDto.getPassword()); 246 | userDto.setPassword(key); 247 | int count = userService.insert(userDto); 248 | if (count <= 0) { 249 | throw new CustomException("新增失败(Insert Failure)"); 250 | } 251 | return new ResponseBean(HttpStatus.OK.value(), "新增成功(Insert Success)", userDto); 252 | } 253 | 254 | /** 255 | * 更新用户 256 | * @param userDto 257 | * @return java.util.Map 258 | * @author dolyw.com 259 | * @date 2018/8/30 10:42 260 | */ 261 | @PutMapping 262 | @RequiresPermissions(logical = Logical.AND, value = {"user:edit"}) 263 | public ResponseBean update(@Validated(UserEditValidGroup.class) @RequestBody UserDto userDto) { 264 | // 查询数据库密码 265 | UserDto userDtoTemp = new UserDto(); 266 | userDtoTemp.setAccount(userDto.getAccount()); 267 | userDtoTemp = userService.selectOne(userDtoTemp); 268 | if (userDtoTemp == null) { 269 | throw new CustomUnauthorizedException("该帐号不存在(Account not exist.)"); 270 | } else { 271 | userDto.setId(userDtoTemp.getId()); 272 | } 273 | // FIXME: 如果不一样就说明用户修改了密码,重新加密密码(这个处理不太好,但是没有想到好的处理方式) 274 | if (!userDtoTemp.getPassword().equals(userDto.getPassword())) { 275 | // 密码以帐号+密码的形式进行AES加密 276 | if (userDto.getPassword().length() > Constant.PASSWORD_MAX_LEN) { 277 | throw new CustomException("密码最多8位(Password up to 8 bits.)"); 278 | } 279 | String key = AesCipherUtil.enCrypto(userDto.getAccount() + userDto.getPassword()); 280 | userDto.setPassword(key); 281 | } 282 | int count = userService.updateByPrimaryKeySelective(userDto); 283 | if (count <= 0) { 284 | throw new CustomException("更新失败(Update Failure)"); 285 | } 286 | return new ResponseBean(HttpStatus.OK.value(), "更新成功(Update Success)", userDto); 287 | } 288 | 289 | /** 290 | * 删除用户 291 | * @param id 292 | * @return java.util.Map 293 | * @author dolyw.com 294 | * @date 2018/8/30 10:43 295 | */ 296 | @DeleteMapping("/{id}") 297 | @RequiresPermissions(logical = Logical.AND, value = {"user:edit"}) 298 | public ResponseBean delete(@PathVariable("id") Integer id) { 299 | int count = userService.deleteByPrimaryKey(id); 300 | if (count <= 0) { 301 | throw new CustomException("删除失败,ID不存在(Deletion Failed. ID does not exist.)"); 302 | } 303 | return new ResponseBean(HttpStatus.OK.value(), "删除成功(Delete Success)", null); 304 | } 305 | 306 | /** 307 | * 剔除在线用户 308 | * @param id 309 | * @return com.wang.model.common.ResponseBean 310 | * @author dolyw.com 311 | * @date 2018/9/6 10:20 312 | */ 313 | @DeleteMapping("/online/{id}") 314 | @RequiresPermissions(logical = Logical.AND, value = {"user:edit"}) 315 | public ResponseBean deleteOnline(@PathVariable("id") Integer id) { 316 | UserDto userDto = userService.selectByPrimaryKey(id); 317 | if (JedisUtil.exists(Constant.PREFIX_SHIRO_REFRESH_TOKEN + userDto.getAccount())) { 318 | if (JedisUtil.delKey(Constant.PREFIX_SHIRO_REFRESH_TOKEN + userDto.getAccount()) > 0) { 319 | return new ResponseBean(HttpStatus.OK.value(), "剔除成功(Delete Success)", null); 320 | } 321 | } 322 | throw new CustomException("剔除失败,Account不存在(Deletion Failed. Account does not exist.)"); 323 | } 324 | } -------------------------------------------------------------------------------- /src/main/java/com/wang/exception/CustomException.java: -------------------------------------------------------------------------------- 1 | package com.wang.exception; 2 | 3 | /** 4 | * 自定义异常(CustomException) 5 | * @author dolyw.com 6 | * @date 2018/8/30 13:59 7 | */ 8 | public class CustomException extends RuntimeException { 9 | 10 | public CustomException(String msg){ 11 | super(msg); 12 | } 13 | 14 | public CustomException() { 15 | super(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/wang/exception/CustomUnauthorizedException.java: -------------------------------------------------------------------------------- 1 | package com.wang.exception; 2 | 3 | /** 4 | * 自定义401无权限异常(UnauthorizedException) 5 | * @author dolyw.com 6 | * @date 2018/8/30 13:59 7 | */ 8 | public class CustomUnauthorizedException extends RuntimeException { 9 | 10 | public CustomUnauthorizedException(String msg){ 11 | super(msg); 12 | } 13 | 14 | public CustomUnauthorizedException() { 15 | super(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/wang/mapper/PermissionMapper.java: -------------------------------------------------------------------------------- 1 | package com.wang.mapper; 2 | 3 | import com.wang.model.PermissionDto; 4 | import com.wang.model.RoleDto; 5 | import tk.mybatis.mapper.common.Mapper; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * PermissionMapper 11 | * @author dolyw.com 12 | * @date 2018/8/31 14:42 13 | */ 14 | public interface PermissionMapper extends Mapper { 15 | /** 16 | * 根据Role查询Permission 17 | * @param roleDto 18 | * @return java.util.List 19 | * @author dolyw.com 20 | * @date 2018/8/31 11:30 21 | */ 22 | List findPermissionByRole(RoleDto roleDto); 23 | } -------------------------------------------------------------------------------- /src/main/java/com/wang/mapper/RoleMapper.java: -------------------------------------------------------------------------------- 1 | package com.wang.mapper; 2 | 3 | import com.wang.model.RoleDto; 4 | import com.wang.model.UserDto; 5 | import tk.mybatis.mapper.common.Mapper; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * RoleMapper 11 | * @author dolyw.com 12 | * @date 2018/8/31 14:42 13 | */ 14 | public interface RoleMapper extends Mapper { 15 | /** 16 | * 根据User查询Role 17 | * @param userDto 18 | * @return java.util.List 19 | * @author dolyw.com 20 | * @date 2018/8/31 11:30 21 | */ 22 | List findRoleByUser(UserDto userDto); 23 | } -------------------------------------------------------------------------------- /src/main/java/com/wang/mapper/RolePermissionMapper.java: -------------------------------------------------------------------------------- 1 | package com.wang.mapper; 2 | 3 | import com.wang.model.RolePermissionDto; 4 | import tk.mybatis.mapper.common.Mapper; 5 | 6 | /** 7 | * RolePermissionMapper 8 | * @author dolyw.com 9 | * @date 2018/8/31 14:43 10 | */ 11 | public interface RolePermissionMapper extends Mapper { 12 | } -------------------------------------------------------------------------------- /src/main/java/com/wang/mapper/UserMapper.java: -------------------------------------------------------------------------------- 1 | package com.wang.mapper; 2 | 3 | import com.wang.model.UserDto; 4 | import tk.mybatis.mapper.common.Mapper; 5 | 6 | /** 7 | * UserMapper 8 | * @author dolyw.com 9 | * @date 2018/8/31 14:43 10 | */ 11 | public interface UserMapper extends Mapper { 12 | } -------------------------------------------------------------------------------- /src/main/java/com/wang/mapper/UserRoleMapper.java: -------------------------------------------------------------------------------- 1 | package com.wang.mapper; 2 | 3 | import com.wang.model.UserRoleDto; 4 | import tk.mybatis.mapper.common.Mapper; 5 | 6 | /** 7 | * UserRoleMapper 8 | * @author dolyw.com 9 | * @date 2018/8/31 14:43 10 | */ 11 | public interface UserRoleMapper extends Mapper { 12 | } -------------------------------------------------------------------------------- /src/main/java/com/wang/model/PermissionDto.java: -------------------------------------------------------------------------------- 1 | package com.wang.model; 2 | 3 | import com.wang.model.entity.Permission; 4 | import javax.persistence.Table; 5 | 6 | /** 7 | * 8 | * @author dolyw.com 9 | * @date 2018/8/30 10:48 10 | */ 11 | @Table(name = "permission") 12 | public class PermissionDto extends Permission { 13 | 14 | } -------------------------------------------------------------------------------- /src/main/java/com/wang/model/RoleDto.java: -------------------------------------------------------------------------------- 1 | package com.wang.model; 2 | 3 | import com.wang.model.entity.Role; 4 | import javax.persistence.Table; 5 | 6 | /** 7 | * 8 | * @author dolyw.com 9 | * @date 2018/8/30 10:47 10 | */ 11 | @Table(name = "role") 12 | public class RoleDto extends Role { 13 | 14 | } -------------------------------------------------------------------------------- /src/main/java/com/wang/model/RolePermissionDto.java: -------------------------------------------------------------------------------- 1 | package com.wang.model; 2 | 3 | import com.wang.model.entity.RolePermission; 4 | import javax.persistence.Table; 5 | 6 | /** 7 | * 8 | * @author dolyw.com 9 | * @date 2018/8/30 10:49 10 | */ 11 | @Table(name = "role_permission") 12 | public class RolePermissionDto extends RolePermission { 13 | 14 | } -------------------------------------------------------------------------------- /src/main/java/com/wang/model/UserDto.java: -------------------------------------------------------------------------------- 1 | package com.wang.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import com.wang.model.entity.User; 5 | import javax.persistence.Table; 6 | import javax.persistence.Transient; 7 | import java.util.Date; 8 | 9 | /** 10 | * 11 | * @author dolyw.com 12 | * @date 2018/8/30 10:34 13 | */ 14 | @Table(name = "user") 15 | public class UserDto extends User { 16 | /** 17 | * 登录时间 18 | */ 19 | @Transient 20 | @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") 21 | private Date loginTime; 22 | 23 | public Date getLoginTime() { 24 | return loginTime; 25 | } 26 | 27 | public void setLoginTime(Date loginTime) { 28 | this.loginTime = loginTime; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/wang/model/UserRoleDto.java: -------------------------------------------------------------------------------- 1 | package com.wang.model; 2 | 3 | import com.wang.model.entity.UserRole; 4 | import javax.persistence.Table; 5 | 6 | /** 7 | * 8 | * @author dolyw.com 9 | * @date 2018/8/30 10:49 10 | */ 11 | @Table(name = "user_role") 12 | public class UserRoleDto extends UserRole { 13 | 14 | } -------------------------------------------------------------------------------- /src/main/java/com/wang/model/common/BaseDto.java: -------------------------------------------------------------------------------- 1 | package com.wang.model.common; 2 | 3 | import javax.persistence.Transient; 4 | import javax.validation.constraints.Max; 5 | import javax.validation.constraints.Min; 6 | import java.io.Serializable; 7 | 8 | /** 9 | * 分页排序通用Dto 10 | * @author dolyw.com 11 | * @date 2018/9/10 10:10 12 | */ 13 | public class BaseDto implements Serializable { 14 | 15 | private static final long serialVersionUID = 1L; 16 | 17 | /** 当前页数 */ 18 | @Transient 19 | @Min(value = 1, message = "当前页数不能小于1") 20 | private Integer page; 21 | 22 | /** 每页条数 */ 23 | @Transient 24 | @Min(value = 1, message = "每页条数不能小于1") 25 | @Max(value = 50, message = "每页条数不能大于50") 26 | private Integer rows; 27 | 28 | /** 排序的列名 */ 29 | @Transient 30 | private String sidx; 31 | 32 | /** 排序规则(DESC或者ESC) */ 33 | @Transient 34 | private String sord; 35 | 36 | public Integer getPage() { 37 | return page; 38 | } 39 | 40 | public void setPage(Integer page) { 41 | this.page = page; 42 | } 43 | 44 | public Integer getRows() { 45 | return rows; 46 | } 47 | 48 | public void setRows(Integer rows) { 49 | this.rows = rows; 50 | } 51 | 52 | public String getSidx() { 53 | return sidx; 54 | } 55 | 56 | public void setSidx(String sidx) { 57 | this.sidx = sidx; 58 | } 59 | 60 | public String getSord() { 61 | return sord; 62 | } 63 | 64 | public void setSord(String sord) { 65 | this.sord = sord; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/wang/model/common/Constant.java: -------------------------------------------------------------------------------- 1 | package com.wang.model.common; 2 | 3 | /** 4 | * 常量 5 | * @author dolyw.com 6 | * @date 2018/9/3 16:03 7 | */ 8 | public class Constant { 9 | 10 | private Constant() {} 11 | 12 | /** 13 | * redis-OK 14 | */ 15 | public static final String OK = "OK"; 16 | 17 | /** 18 | * redis过期时间,以秒为单位,一分钟 19 | */ 20 | public static final int EXRP_MINUTE = 60; 21 | 22 | /** 23 | * redis过期时间,以秒为单位,一小时 24 | */ 25 | public static final int EXRP_HOUR = 60 * 60; 26 | 27 | /** 28 | * redis过期时间,以秒为单位,一天 29 | */ 30 | public static final int EXRP_DAY = 60 * 60 * 24; 31 | 32 | /** 33 | * redis-key-前缀-shiro:cache: 34 | */ 35 | public static final String PREFIX_SHIRO_CACHE = "shiro:cache:"; 36 | 37 | /** 38 | * redis-key-前缀-shiro:access_token: 39 | */ 40 | public static final String PREFIX_SHIRO_ACCESS_TOKEN = "shiro:access_token:"; 41 | 42 | /** 43 | * redis-key-前缀-shiro:refresh_token: 44 | */ 45 | public static final String PREFIX_SHIRO_REFRESH_TOKEN = "shiro:refresh_token:"; 46 | 47 | /** 48 | * JWT-account: 49 | */ 50 | public static final String ACCOUNT = "account"; 51 | 52 | /** 53 | * JWT-currentTimeMillis: 54 | */ 55 | public static final String CURRENT_TIME_MILLIS = "currentTimeMillis"; 56 | 57 | /** 58 | * PASSWORD_MAX_LEN 59 | */ 60 | public static final Integer PASSWORD_MAX_LEN = 8; 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/wang/model/common/ResponseBean.java: -------------------------------------------------------------------------------- 1 | package com.wang.model.common; 2 | 3 | /** 4 | * ResponseBean 5 | * @author dolyw.com 6 | * @date 2018/8/30 11:39 7 | */ 8 | public class ResponseBean { 9 | /** 10 | * HTTP状态码 11 | */ 12 | private Integer code; 13 | 14 | /** 15 | * 返回信息 16 | */ 17 | private String msg; 18 | 19 | /** 20 | * 返回的数据 21 | */ 22 | private Object data; 23 | 24 | public ResponseBean(int code, String msg, Object data) { 25 | this.code = code; 26 | this.msg = msg; 27 | this.data = data; 28 | } 29 | 30 | public Integer getCode() { 31 | return code; 32 | } 33 | 34 | public void setCode(Integer code) { 35 | this.code = code; 36 | } 37 | 38 | public String getMsg() { 39 | return msg; 40 | } 41 | 42 | public void setMsg(String msg) { 43 | this.msg = msg; 44 | } 45 | 46 | public Object getData() { 47 | return data; 48 | } 49 | 50 | public void setData(Object data) { 51 | this.data = data; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/wang/model/entity/Permission.java: -------------------------------------------------------------------------------- 1 | package com.wang.model.entity; 2 | 3 | import javax.persistence.*; 4 | import java.io.Serializable; 5 | 6 | /** 7 | * Permission 8 | * @author dolyw.com 9 | * @date 2018/8/31 14:41 10 | */ 11 | @Table(name = "permission") 12 | public class Permission implements Serializable { 13 | 14 | private static final long serialVersionUID = -8834983208597107688L; 15 | 16 | /** 17 | * ID 18 | */ 19 | @Id 20 | @GeneratedValue(generator = "JDBC") 21 | private Integer id; 22 | 23 | /** 24 | * 资源名称 25 | */ 26 | private String name; 27 | 28 | /** 29 | * 权限代码字符串 30 | */ 31 | @Column(name = "per_code") 32 | private String perCode; 33 | 34 | /** 35 | * 获取ID 36 | * 37 | * @return id - ID 38 | */ 39 | public Integer getId() { 40 | return id; 41 | } 42 | 43 | /** 44 | * 设置ID 45 | * 46 | * @param id ID 47 | */ 48 | public void setId(Integer id) { 49 | this.id = id; 50 | } 51 | 52 | /** 53 | * 获取资源名称 54 | * 55 | * @return name - 资源名称 56 | */ 57 | public String getName() { 58 | return name; 59 | } 60 | 61 | /** 62 | * 设置资源名称 63 | * 64 | * @param name 资源名称 65 | */ 66 | public void setName(String name) { 67 | this.name = name; 68 | } 69 | 70 | /** 71 | * 获取权限代码字符串 72 | * 73 | * @return per_code - 权限代码字符串 74 | */ 75 | public String getPerCode() { 76 | return perCode; 77 | } 78 | 79 | /** 80 | * 设置权限代码字符串 81 | * 82 | * @param perCode 权限代码字符串 83 | */ 84 | public void setPerCode(String perCode) { 85 | this.perCode = perCode; 86 | } 87 | } -------------------------------------------------------------------------------- /src/main/java/com/wang/model/entity/Role.java: -------------------------------------------------------------------------------- 1 | package com.wang.model.entity; 2 | 3 | import javax.persistence.*; 4 | import java.io.Serializable; 5 | 6 | /** 7 | * Role 8 | * @author dolyw.com 9 | * @date 2018/8/31 14:42 10 | */ 11 | @Table(name = "role") 12 | public class Role implements Serializable { 13 | 14 | private static final long serialVersionUID = 6382925944937625109L; 15 | 16 | /** 17 | * ID 18 | */ 19 | @Id 20 | @GeneratedValue(generator = "JDBC") 21 | private Integer id; 22 | 23 | /** 24 | * 角色名称 25 | */ 26 | private String name; 27 | 28 | /** 29 | * 获取ID 30 | * 31 | * @return id - ID 32 | */ 33 | public Integer getId() { 34 | return id; 35 | } 36 | 37 | /** 38 | * 设置ID 39 | * 40 | * @param id ID 41 | */ 42 | public void setId(Integer id) { 43 | this.id = id; 44 | } 45 | 46 | /** 47 | * 获取角色名称 48 | * 49 | * @return name - 角色名称 50 | */ 51 | public String getName() { 52 | return name; 53 | } 54 | 55 | /** 56 | * 设置角色名称 57 | * 58 | * @param name 角色名称 59 | */ 60 | public void setName(String name) { 61 | this.name = name; 62 | } 63 | } -------------------------------------------------------------------------------- /src/main/java/com/wang/model/entity/RolePermission.java: -------------------------------------------------------------------------------- 1 | package com.wang.model.entity; 2 | 3 | import javax.persistence.*; 4 | import java.io.Serializable; 5 | 6 | /** 7 | * RolePermission 8 | * @author dolyw.com 9 | * @date 2018/8/31 14:43 10 | */ 11 | @Table(name = "role_permission") 12 | public class RolePermission implements Serializable { 13 | 14 | private static final long serialVersionUID = -8564770707000796503L; 15 | 16 | /** 17 | * ID 18 | */ 19 | @Id 20 | @GeneratedValue(generator = "JDBC") 21 | private Integer id; 22 | 23 | /** 24 | * 角色id 25 | */ 26 | @Column(name = "role_id") 27 | private Integer roleId; 28 | 29 | /** 30 | * 权限id 31 | */ 32 | @Column(name = "permission_id") 33 | private Integer permissionId; 34 | 35 | /** 36 | * 获取ID 37 | * 38 | * @return id - ID 39 | */ 40 | public Integer getId() { 41 | return id; 42 | } 43 | 44 | /** 45 | * 设置ID 46 | * 47 | * @param id ID 48 | */ 49 | public void setId(Integer id) { 50 | this.id = id; 51 | } 52 | 53 | /** 54 | * 获取角色id 55 | * 56 | * @return role_id - 角色id 57 | */ 58 | public Integer getRoleId() { 59 | return roleId; 60 | } 61 | 62 | /** 63 | * 设置角色id 64 | * 65 | * @param roleId 角色id 66 | */ 67 | public void setRoleId(Integer roleId) { 68 | this.roleId = roleId; 69 | } 70 | 71 | /** 72 | * 获取权限id 73 | * 74 | * @return permission_id - 权限id 75 | */ 76 | public Integer getPermissionId() { 77 | return permissionId; 78 | } 79 | 80 | /** 81 | * 设置权限id 82 | * 83 | * @param permissionId 权限id 84 | */ 85 | public void setPermissionId(Integer permissionId) { 86 | this.permissionId = permissionId; 87 | } 88 | } -------------------------------------------------------------------------------- /src/main/java/com/wang/model/entity/User.java: -------------------------------------------------------------------------------- 1 | package com.wang.model.entity; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import com.wang.model.valid.group.UserEditValidGroup; 5 | import com.wang.model.valid.group.UserLoginValidGroup; 6 | 7 | import java.io.Serializable; 8 | import java.util.Date; 9 | import javax.persistence.*; 10 | import javax.validation.constraints.NotNull; 11 | 12 | /** 13 | * User 14 | * @author dolyw.com 15 | * @date 2018/8/31 14:43 16 | */ 17 | @Table(name = "user") 18 | public class User implements Serializable { 19 | 20 | private static final long serialVersionUID = 3342723124953988435L; 21 | 22 | /** 23 | * ID 24 | */ 25 | @Id 26 | @GeneratedValue(generator = "JDBC") 27 | private Integer id; 28 | 29 | /** 30 | * 帐号 31 | */ 32 | @NotNull(message = "帐号不能为空", groups = { UserLoginValidGroup.class, UserEditValidGroup.class }) 33 | private String account; 34 | 35 | /** 36 | * 密码 37 | */ 38 | @NotNull(message = "密码不能为空", groups = { UserLoginValidGroup.class, UserEditValidGroup.class }) 39 | private String password; 40 | 41 | /** 42 | * 昵称 43 | */ 44 | @NotNull(message = "用户名不能为空", groups = { UserEditValidGroup.class }) 45 | private String username; 46 | 47 | /** 48 | * 注册时间 49 | */ 50 | @Column(name = "reg_time") 51 | @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") 52 | private Date regTime; 53 | 54 | /** 55 | * 获取ID 56 | * 57 | * @return id - ID 58 | */ 59 | public Integer getId() { 60 | return id; 61 | } 62 | 63 | /** 64 | * 设置ID 65 | * 66 | * @param id ID 67 | */ 68 | public void setId(Integer id) { 69 | this.id = id; 70 | } 71 | 72 | /** 73 | * 获取帐号 74 | * 75 | * @return account - 帐号 76 | */ 77 | public String getAccount() { 78 | return account; 79 | } 80 | 81 | /** 82 | * 设置帐号 83 | * 84 | * @param account 帐号 85 | */ 86 | public void setAccount(String account) { 87 | this.account = account; 88 | } 89 | 90 | /** 91 | * 获取密码 92 | * 93 | * @return password - 密码 94 | */ 95 | public String getPassword() { 96 | return password; 97 | } 98 | 99 | /** 100 | * 设置密码 101 | * 102 | * @param password 密码 103 | */ 104 | public void setPassword(String password) { 105 | this.password = password; 106 | } 107 | 108 | /** 109 | * 获取昵称 110 | * 111 | * @return username - 昵称 112 | */ 113 | public String getUsername() { 114 | return username; 115 | } 116 | 117 | /** 118 | * 设置昵称 119 | * 120 | * @param username 昵称 121 | */ 122 | public void setUsername(String username) { 123 | this.username = username; 124 | } 125 | 126 | /** 127 | * 获取注册时间 128 | * 129 | * @return reg_time - 注册时间 130 | */ 131 | public Date getRegTime() { 132 | return regTime; 133 | } 134 | 135 | /** 136 | * 设置注册时间 137 | * 138 | * @param regTime 注册时间 139 | */ 140 | public void setRegTime(Date regTime) { 141 | this.regTime = regTime; 142 | } 143 | } -------------------------------------------------------------------------------- /src/main/java/com/wang/model/entity/UserRole.java: -------------------------------------------------------------------------------- 1 | package com.wang.model.entity; 2 | 3 | import javax.persistence.*; 4 | import java.io.Serializable; 5 | 6 | /** 7 | * UserRole 8 | * @author dolyw.com 9 | * @date 2018/8/31 14:43 10 | */ 11 | @Table(name = "user_role") 12 | public class UserRole implements Serializable { 13 | 14 | private static final long serialVersionUID = -3397516891053950951L; 15 | 16 | /** 17 | * ID 18 | */ 19 | @Id 20 | @GeneratedValue(generator = "JDBC") 21 | private Integer id; 22 | 23 | /** 24 | * 用户id 25 | */ 26 | @Column(name = "user_id") 27 | private Integer userId; 28 | 29 | /** 30 | * 角色id 31 | */ 32 | @Column(name = "role_id") 33 | private Integer roleId; 34 | 35 | /** 36 | * 获取ID 37 | * 38 | * @return id - ID 39 | */ 40 | public Integer getId() { 41 | return id; 42 | } 43 | 44 | /** 45 | * 设置ID 46 | * 47 | * @param id ID 48 | */ 49 | public void setId(Integer id) { 50 | this.id = id; 51 | } 52 | 53 | /** 54 | * 获取用户id 55 | * 56 | * @return user_id - 用户id 57 | */ 58 | public Integer getUserId() { 59 | return userId; 60 | } 61 | 62 | /** 63 | * 设置用户id 64 | * 65 | * @param userId 用户id 66 | */ 67 | public void setUserId(Integer userId) { 68 | this.userId = userId; 69 | } 70 | 71 | /** 72 | * 获取角色id 73 | * 74 | * @return role_id - 角色id 75 | */ 76 | public Integer getRoleId() { 77 | return roleId; 78 | } 79 | 80 | /** 81 | * 设置角色id 82 | * 83 | * @param roleId 角色id 84 | */ 85 | public void setRoleId(Integer roleId) { 86 | this.roleId = roleId; 87 | } 88 | } -------------------------------------------------------------------------------- /src/main/java/com/wang/model/valid/group/UserEditValidGroup.java: -------------------------------------------------------------------------------- 1 | package com.wang.model.valid.group; 2 | 3 | /** 4 | * User编辑校验分组 5 | * @author dolyw.com 6 | * @date 2018/10/7 10:13 7 | */ 8 | public interface UserEditValidGroup { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/wang/model/valid/group/UserLoginValidGroup.java: -------------------------------------------------------------------------------- 1 | package com.wang.model.valid.group; 2 | 3 | /** 4 | * User登录校验分组 5 | * @author dolyw.com 6 | * @date 2018/10/7 10:12 7 | */ 8 | public interface UserLoginValidGroup { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/wang/service/IBaseService.java: -------------------------------------------------------------------------------- 1 | package com.wang.service; 2 | 3 | import org.apache.ibatis.annotations.Param; 4 | import org.apache.ibatis.session.RowBounds; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * 10 | * @author dolyw.com 11 | * @date 2018/8/9 15:45 12 | */ 13 | public interface IBaseService { 14 | 15 | // Select 16 | /** 17 | * 根据实体中的属性值进行查询,查询条件使用等号 18 | * @param record 19 | * @return java.util.List 20 | * @author dolyw.com 21 | * @date 2018/8/9 15:43 22 | */ 23 | List select(T record); 24 | 25 | /** 26 | * 根据主键字段进行查询,方法参数必须包含完整的主键属性,查询条件使用等号 27 | * @param key 28 | * @return T 29 | * @author dolyw.com 30 | * @date 2018/8/9 15:43 31 | */ 32 | T selectByPrimaryKey(Object key); 33 | 34 | /** 35 | * 查询全部结果,select(null)方法能达到同样的效果 36 | * @param 37 | * @return java.util.List 38 | * @author dolyw.com 39 | * @date 2018/8/9 15:43 40 | */ 41 | List selectAll(); 42 | 43 | /** 44 | * 根据实体中的属性进行查询,只能有一个返回值,有多个结果是抛出异常,查询条件使用等号 45 | * @param record 46 | * @return T 47 | * @author dolyw.com 48 | * @date 2018/8/9 15:43 49 | */ 50 | T selectOne(T record); 51 | 52 | /** 53 | * 根据实体中的属性查询总数,查询条件使用等号 54 | * @param record 55 | * @return int 56 | * @author dolyw.com 57 | * @date 2018/8/9 15:43 58 | */ 59 | int selectCount(T record); 60 | 61 | // Insert 62 | /** 63 | * 保存一个实体,null的属性也会保存,不会使用数据库默认值 64 | * @param record 65 | * @return int 66 | * @author dolyw.com 67 | * @date 2018/8/9 15:43 68 | */ 69 | int insert(T record); 70 | 71 | /** 72 | * 保存一个实体,null的属性不会保存,会使用数据库默认值 73 | * @param record 74 | * @return int 75 | * @author dolyw.com 76 | * @date 2018/8/9 15:43 77 | */ 78 | int insertSelective(T record); 79 | 80 | // Update 81 | /** 82 | * 根据主键更新实体全部字段,null值会被更新 83 | * @param record 84 | * @return int 85 | * @author dolyw.com 86 | * @date 2018/8/9 15:43 87 | */ 88 | int updateByPrimaryKey(T record); 89 | 90 | /** 91 | * 根据主键更新属性不为null的值 92 | * @param record 93 | * @return int 94 | * @author dolyw.com 95 | * @date 2018/8/9 15:43 96 | */ 97 | int updateByPrimaryKeySelective(T record); 98 | 99 | // Delete 100 | /** 101 | * 根据实体属性作为条件进行删除,查询条件使用等号 102 | * @param record 103 | * @return int 104 | * @author dolyw.com 105 | * @date 2018/8/9 15:43 106 | */ 107 | int delete(T record); 108 | 109 | /** 110 | * 根据主键字段进行删除,方法参数必须包含完整的主键属性 111 | * @param key 112 | * @return int 113 | * @author dolyw.com 114 | * @date 2018/8/9 15:44 115 | */ 116 | int deleteByPrimaryKey(Object key); 117 | 118 | // Example 119 | /** 120 | * 根据Example条件进行查询,这个查询支持通过Example类指定查询列,通过selectProperties方法指定查询列 121 | * @param example 122 | * @return java.util.List 123 | * @author dolyw.com 124 | * @date 2018/8/9 15:44 125 | */ 126 | List selectByExample(Object example); 127 | 128 | /** 129 | * 根据Example条件进行查询总数 130 | * @param example 131 | * @return int 132 | * @author dolyw.com 133 | * @date 2018/8/9 15:44 134 | */ 135 | int selectCountByExample(Object example); 136 | 137 | /** 138 | * 根据Example条件更新实体record包含的全部属性,null值会被更新 139 | * @param record 140 | * @param example 141 | * @return int 142 | * @author dolyw.com 143 | * @date 2018/8/9 15:44 144 | */ 145 | int updateByExample(@Param("record") T record, @Param("example") Object example); 146 | 147 | /** 148 | * 根据Example条件更新实体record包含的不是null的属性值 149 | * @param record 150 | * @param example 151 | * @return int 152 | * @author dolyw.com 153 | * @date 2018/8/9 15:44 154 | */ 155 | int updateByExampleSelective(@Param("record") T record, @Param("example") Object example); 156 | 157 | /** 158 | * 根据Example条件删除数据 159 | * @param example 160 | * @return int 161 | * @author dolyw.com 162 | * @date 2018/8/9 15:44 163 | */ 164 | int deleteByExample(Object example); 165 | 166 | // RowBounds 167 | /** 168 | * 根据实体属性和RowBounds进行分页查询 169 | * @param record 170 | * @param rowBounds 171 | * @return java.util.List 172 | * @author dolyw.com 173 | * @date 2018/8/9 15:44 174 | */ 175 | List selectByRowBounds(T record, RowBounds rowBounds); 176 | 177 | /** 178 | * 根据example条件和RowBounds进行分页查询 179 | * @param example 180 | * @param rowBounds 181 | * @return java.util.List 182 | * @author dolyw.com 183 | * @date 2018/8/9 15:44 184 | */ 185 | List selectByExampleAndRowBounds(Object example, RowBounds rowBounds); 186 | } 187 | -------------------------------------------------------------------------------- /src/main/java/com/wang/service/IUserService.java: -------------------------------------------------------------------------------- 1 | package com.wang.service; 2 | 3 | import com.wang.model.UserDto; 4 | 5 | /** 6 | * 7 | * @author dolyw.com 8 | * @date 2018/8/9 15:44 9 | */ 10 | public interface IUserService extends IBaseService { 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/wang/service/impl/BaseServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.wang.service.impl; 2 | 3 | import com.wang.service.IBaseService; 4 | import org.apache.ibatis.session.RowBounds; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import tk.mybatis.mapper.common.Mapper; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * 12 | * @author dolyw.com 13 | * @date 2018/8/9 15:45 14 | */ 15 | public abstract class BaseServiceImpl implements IBaseService { 16 | 17 | @Autowired 18 | protected Mapper mapper; 19 | 20 | public Mapper getMapper() { 21 | return mapper; 22 | } 23 | 24 | @Override 25 | public List select(T record) { 26 | return mapper.select(record); 27 | } 28 | 29 | @Override 30 | public T selectByPrimaryKey(Object key) { 31 | return mapper.selectByPrimaryKey(key); 32 | } 33 | 34 | @Override 35 | public List selectAll() { 36 | return mapper.selectAll(); 37 | } 38 | 39 | @Override 40 | public T selectOne(T record) { 41 | return mapper.selectOne(record); 42 | } 43 | 44 | @Override 45 | public int selectCount(T record) { 46 | return mapper.selectCount(record); 47 | } 48 | 49 | @Override 50 | public int insert(T record) { 51 | return mapper.insert(record); 52 | } 53 | 54 | @Override 55 | public int insertSelective(T record) { 56 | return mapper.insertSelective(record); 57 | } 58 | 59 | @Override 60 | public int updateByPrimaryKey(T record) { 61 | return mapper.updateByPrimaryKey(record); 62 | } 63 | 64 | @Override 65 | public int updateByPrimaryKeySelective(T record) { 66 | return mapper.updateByPrimaryKeySelective(record); 67 | } 68 | 69 | @Override 70 | public int delete(T record) { 71 | return mapper.delete(record); 72 | } 73 | 74 | @Override 75 | public int deleteByPrimaryKey(Object key) { 76 | return mapper.deleteByPrimaryKey(key); 77 | } 78 | 79 | @Override 80 | public List selectByExample(Object example) { 81 | return mapper.selectByExample(example); 82 | } 83 | 84 | @Override 85 | public int selectCountByExample(Object example) { 86 | return mapper.selectCountByExample(example); 87 | } 88 | 89 | @Override 90 | public int updateByExample(T record, Object example) { 91 | return mapper.updateByExample(record, example); 92 | } 93 | 94 | @Override 95 | public int updateByExampleSelective(T record, Object example) { 96 | return mapper.updateByExampleSelective(record, example); 97 | } 98 | 99 | @Override 100 | public int deleteByExample(Object example) { 101 | return mapper.deleteByExample(example); 102 | } 103 | 104 | @Override 105 | public List selectByRowBounds(T record, RowBounds rowBounds) { 106 | return mapper.selectByRowBounds(record, rowBounds); 107 | } 108 | 109 | @Override 110 | public List selectByExampleAndRowBounds(Object example, RowBounds rowBounds) { 111 | return mapper.selectByExampleAndRowBounds(example, rowBounds); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/com/wang/service/impl/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.wang.service.impl; 2 | 3 | import com.wang.model.UserDto; 4 | import com.wang.service.IUserService; 5 | import org.springframework.stereotype.Service; 6 | 7 | /** 8 | * 9 | * @author dolyw.com 10 | * @date 2018/8/9 15:45 11 | */ 12 | @Service 13 | public class UserServiceImpl extends BaseServiceImpl implements IUserService { 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/wang/util/AesCipherUtil.java: -------------------------------------------------------------------------------- 1 | package com.wang.util; 2 | 3 | import com.wang.exception.CustomUnauthorizedException; 4 | import com.wang.util.common.Base64ConvertUtil; 5 | import com.wang.util.common.HexConvertUtil; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.stereotype.Component; 10 | 11 | import javax.crypto.*; 12 | import java.io.UnsupportedEncodingException; 13 | import java.security.InvalidKeyException; 14 | import java.security.NoSuchAlgorithmException; 15 | import java.security.SecureRandom; 16 | import java.security.Security; 17 | 18 | /** 19 | * AES加密解密工具类 20 | * @author dolyw.com 21 | * @date 2018/8/31 16:39 22 | */ 23 | @Component 24 | public class AesCipherUtil { 25 | 26 | /** 27 | * AES密码加密私钥(Base64加密) 28 | */ 29 | private static String encryptAESKey; 30 | // private static final byte[] KEY = { 1, 1, 33, 82, -32, -85, -128, -65 }; 31 | 32 | @Value("${encryptAESKey}") 33 | public void setEncryptAESKey(String encryptAESKey) { 34 | AesCipherUtil.encryptAESKey = encryptAESKey; 35 | } 36 | 37 | /** 38 | * logger 39 | */ 40 | private static final Logger logger = LoggerFactory.getLogger(AesCipherUtil.class); 41 | 42 | /** 43 | * 加密 44 | * @param str 45 | * @return java.lang.String 46 | * @author dolyw.com 47 | * @date 2018/8/31 16:56 48 | */ 49 | public static String enCrypto(String str) { 50 | try { 51 | Security.addProvider(new com.sun.crypto.provider.SunJCE()); 52 | // 实例化支持AES算法的密钥生成器(算法名称命名需按规定,否则抛出异常) 53 | // KeyGenerator 提供对称密钥生成器的功能,支持各种算法 54 | KeyGenerator keygen = KeyGenerator.getInstance("AES"); 55 | // 将私钥encryptAESKey先Base64解密后转换为byte[]数组按128位初始化 56 | SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG"); 57 | secureRandom.setSeed(Base64ConvertUtil.decode(encryptAESKey).getBytes()); 58 | keygen.init(128, secureRandom); 59 | // SecretKey 负责保存对称密钥 生成密钥 60 | SecretKey desKey = keygen.generateKey(); 61 | // 生成Cipher对象,指定其支持的AES算法,Cipher负责完成加密或解密工作 62 | Cipher c = Cipher.getInstance("AES"); 63 | // 根据密钥,对Cipher对象进行初始化,ENCRYPT_MODE表示加密模式 64 | c.init(Cipher.ENCRYPT_MODE, desKey); 65 | byte[] src = str.getBytes(); 66 | // 该字节数组负责保存加密的结果 67 | byte[] cipherByte = c.doFinal(src); 68 | // 先将二进制转换成16进制,再返回Base64加密后的String 69 | return Base64ConvertUtil.encode(HexConvertUtil.parseByte2HexStr(cipherByte)); 70 | } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { 71 | logger.error("getInstance()方法异常:{}", e.getMessage()); 72 | throw new CustomUnauthorizedException("getInstance()方法异常:" + e.getMessage()); 73 | } catch (UnsupportedEncodingException e) { 74 | logger.error("Base64加密异常:{}", e.getMessage()); 75 | throw new CustomUnauthorizedException("Base64加密异常:" + e.getMessage()); 76 | } catch (InvalidKeyException e) { 77 | logger.error("初始化Cipher对象异常:{}", e.getMessage()); 78 | throw new CustomUnauthorizedException("初始化Cipher对象异常:" + e.getMessage()); 79 | } catch (IllegalBlockSizeException | BadPaddingException e) { 80 | logger.error("加密异常,密钥有误:{}", e.getMessage()); 81 | throw new CustomUnauthorizedException("加密异常,密钥有误:" + e.getMessage()); 82 | } 83 | } 84 | 85 | /** 86 | * 解密 87 | * @param str 88 | * @return java.lang.String 89 | * @author dolyw.com 90 | * @date 2018/8/31 16:56 91 | */ 92 | public static String deCrypto(String str) { 93 | try { 94 | Security.addProvider(new com.sun.crypto.provider.SunJCE()); 95 | // 实例化支持AES算法的密钥生成器(算法名称命名需按规定,否则抛出异常) 96 | // KeyGenerator 提供对称密钥生成器的功能,支持各种算法 97 | KeyGenerator keygen = KeyGenerator.getInstance("AES"); 98 | // 将私钥encryptAESKey先Base64解密后转换为byte[]数组按128位初始化 99 | SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG"); 100 | secureRandom.setSeed(Base64ConvertUtil.decode(encryptAESKey).getBytes()); 101 | keygen.init(128, secureRandom); 102 | // SecretKey 负责保存对称密钥 生成密钥 103 | SecretKey desKey = keygen.generateKey(); 104 | // 生成Cipher对象,指定其支持的AES算法,Cipher负责完成加密或解密工作 105 | Cipher c = Cipher.getInstance("AES"); 106 | // 根据密钥,对Cipher对象进行初始化,DECRYPT_MODE表示解密模式 107 | c.init(Cipher.DECRYPT_MODE, desKey); 108 | // 该字节数组负责保存解密的结果,先对str进行Base64解密,将16进制转换为二进制 109 | byte[] cipherByte = c.doFinal(HexConvertUtil.parseHexStr2Byte(Base64ConvertUtil.decode(str))); 110 | return new String(cipherByte); 111 | } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { 112 | logger.error("getInstance()方法异常:{}", e.getMessage()); 113 | throw new CustomUnauthorizedException("getInstance()方法异常:" + e.getMessage()); 114 | } catch (UnsupportedEncodingException e) { 115 | logger.error("Base64解密异常:{}", e.getMessage()); 116 | throw new CustomUnauthorizedException("Base64解密异常:" + e.getMessage()); 117 | } catch (InvalidKeyException e) { 118 | logger.error("初始化Cipher对象异常:{}", e.getMessage()); 119 | throw new CustomUnauthorizedException("初始化Cipher对象异常:" + e.getMessage()); 120 | } catch (IllegalBlockSizeException | BadPaddingException e) { 121 | logger.error("解密异常,密钥有误:{}", e.getMessage()); 122 | throw new CustomUnauthorizedException("解密异常,密钥有误:" + e.getMessage()); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/com/wang/util/JedisUtil.java: -------------------------------------------------------------------------------- 1 | package com.wang.util; 2 | 3 | import com.wang.exception.CustomException; 4 | import com.wang.model.common.Constant; 5 | import com.wang.util.common.StringUtil; 6 | import com.wang.util.common.SerializableUtil; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Component; 9 | import redis.clients.jedis.Jedis; 10 | import redis.clients.jedis.JedisPool; 11 | 12 | import java.util.Set; 13 | 14 | /** 15 | * JedisUtil(推荐存Byte数组,存Json字符串效率更慢) 16 | * @author dolyw.com 17 | * @date 2018/9/4 15:45 18 | */ 19 | @Component 20 | public class JedisUtil { 21 | 22 | /** 23 | * 静态注入JedisPool连接池 24 | * 本来是正常注入JedisUtil,可以在Controller和Service层使用,但是重写Shiro的CustomCache无法注入JedisUtil 25 | * 现在改为静态注入JedisPool连接池,JedisUtil直接调用静态方法即可 26 | * https://blog.csdn.net/W_Z_W_888/article/details/79979103 27 | */ 28 | private static JedisPool jedisPool; 29 | 30 | @Autowired 31 | public void setJedisPool(JedisPool jedisPool) { 32 | JedisUtil.jedisPool = jedisPool; 33 | } 34 | 35 | /** 36 | * 获取Jedis实例 37 | * @param 38 | * @return redis.clients.jedis.Jedis 39 | * @author dolyw.com 40 | * @date 2018/9/4 15:47 41 | */ 42 | public static synchronized Jedis getJedis() { 43 | try { 44 | if (jedisPool != null) { 45 | return jedisPool.getResource(); 46 | } else { 47 | return null; 48 | } 49 | } catch (Exception e) { 50 | throw new CustomException("获取Jedis资源异常:" + e.getMessage()); 51 | } 52 | } 53 | 54 | /** 55 | * 释放Jedis资源 56 | * @param 57 | * @return void 58 | * @author dolyw.com 59 | * @date 2018/9/5 9:16 60 | */ 61 | public static void closePool() { 62 | try { 63 | jedisPool.close(); 64 | } catch (Exception e) { 65 | throw new CustomException("释放Jedis资源异常:" + e.getMessage()); 66 | } 67 | } 68 | 69 | /** 70 | * 获取redis键值-object 71 | * @param key 72 | * @return java.lang.Object 73 | * @author dolyw.com 74 | * @date 2018/9/4 15:47 75 | */ 76 | public static Object getObject(String key) { 77 | try (Jedis jedis = jedisPool.getResource()) { 78 | byte[] bytes = jedis.get(key.getBytes()); 79 | if (StringUtil.isNotNull(bytes)) { 80 | return SerializableUtil.unserializable(bytes); 81 | } 82 | } catch (Exception e) { 83 | throw new CustomException("获取Redis键值getObject方法异常:key=" + key + " cause=" + e.getMessage()); 84 | } 85 | return null; 86 | } 87 | 88 | /** 89 | * 设置redis键值-object 90 | * @param key 91 | * @param value 92 | * @return java.lang.String 93 | * @author dolyw.com 94 | * @date 2018/9/4 15:49 95 | */ 96 | public static String setObject(String key, Object value) { 97 | try (Jedis jedis = jedisPool.getResource()) { 98 | return jedis.set(key.getBytes(), SerializableUtil.serializable(value)); 99 | } catch (Exception e) { 100 | throw new CustomException("设置Redis键值setObject方法异常:key=" + key + " value=" + value + " cause=" + e.getMessage()); 101 | } 102 | } 103 | 104 | /** 105 | * 设置redis键值-object-expiretime 106 | * @param key 107 | * @param value 108 | * @param expiretime 109 | * @return java.lang.String 110 | * @author dolyw.com 111 | * @date 2018/9/4 15:50 112 | */ 113 | public static String setObject(String key, Object value, int expiretime) { 114 | String result; 115 | try (Jedis jedis = jedisPool.getResource()) { 116 | result = jedis.set(key.getBytes(), SerializableUtil.serializable(value)); 117 | if (Constant.OK.equals(result)) { 118 | jedis.expire(key.getBytes(), expiretime); 119 | } 120 | return result; 121 | } catch (Exception e) { 122 | throw new CustomException("设置Redis键值setObject方法异常:key=" + key + " value=" + value + " cause=" + e.getMessage()); 123 | } 124 | } 125 | 126 | /** 127 | * 获取redis键值-Json 128 | * @param key 129 | * @return java.lang.Object 130 | * @author dolyw.com 131 | * @date 2018/9/4 15:47 132 | */ 133 | public static String getJson(String key) { 134 | try (Jedis jedis = jedisPool.getResource()) { 135 | return jedis.get(key); 136 | } catch (Exception e) { 137 | throw new CustomException("获取Redis键值getJson方法异常:key=" + key + " cause=" + e.getMessage()); 138 | } 139 | } 140 | 141 | /** 142 | * 设置redis键值-Json 143 | * @param key 144 | * @param value 145 | * @return java.lang.String 146 | * @author Wang926454 147 | * @date 2018/9/4 15:49 148 | */ 149 | public static String setJson(String key, String value) { 150 | try (Jedis jedis = jedisPool.getResource()) { 151 | return jedis.set(key, value); 152 | } catch (Exception e) { 153 | throw new CustomException("设置Redis键值setJson方法异常:key=" + key + " value=" + value + " cause=" + e.getMessage()); 154 | } 155 | } 156 | 157 | /** 158 | * 设置redis键值-Json-expiretime 159 | * @param key 160 | * @param value 161 | * @param expiretime 162 | * @return java.lang.String 163 | * @author Wang926454 164 | * @date 2018/9/4 15:50 165 | */ 166 | public static String setJson(String key, String value, int expiretime) { 167 | String result; 168 | try (Jedis jedis = jedisPool.getResource()) { 169 | result = jedis.set(key, value); 170 | if (Constant.OK.equals(result)) { 171 | jedis.expire(key, expiretime); 172 | } 173 | return result; 174 | } catch (Exception e) { 175 | throw new CustomException("设置Redis键值setJson方法异常:key=" + key + " value=" + value + " cause=" + e.getMessage()); 176 | } 177 | } 178 | 179 | /** 180 | * 删除key 181 | * @param key 182 | * @return java.lang.Long 183 | * @author Wang926454 184 | * @date 2018/9/4 15:50 185 | */ 186 | public static Long delKey(String key) { 187 | try (Jedis jedis = jedisPool.getResource()) { 188 | return jedis.del(key.getBytes()); 189 | } catch (Exception e) { 190 | throw new CustomException("删除Redis的键delKey方法异常:key=" + key + " cause=" + e.getMessage()); 191 | } 192 | } 193 | 194 | /** 195 | * key是否存在 196 | * @param key 197 | * @return java.lang.Boolean 198 | * @author Wang926454 199 | * @date 2018/9/4 15:51 200 | */ 201 | public static Boolean exists(String key) { 202 | try (Jedis jedis = jedisPool.getResource()) { 203 | return jedis.exists(key.getBytes()); 204 | } catch (Exception e) { 205 | throw new CustomException("查询Redis的键是否存在exists方法异常:key=" + key + " cause=" + e.getMessage()); 206 | } 207 | } 208 | 209 | /** 210 | * 模糊查询获取key集合(keys的速度非常快,但在一个大的数据库中使用它仍然可能造成性能问题,生产不推荐使用) 211 | * @param key 212 | * @return java.util.Set 213 | * @author Wang926454 214 | * @date 2018/9/6 9:43 215 | */ 216 | public static Set keysS(String key) { 217 | try (Jedis jedis = jedisPool.getResource()) { 218 | return jedis.keys(key); 219 | } catch (Exception e) { 220 | throw new CustomException("模糊查询Redis的键集合keysS方法异常:key=" + key + " cause=" + e.getMessage()); 221 | } 222 | } 223 | 224 | /** 225 | * 模糊查询获取key集合(keys的速度非常快,但在一个大的数据库中使用它仍然可能造成性能问题,生产不推荐使用) 226 | * @param key 227 | * @return java.util.Set 228 | * @author Wang926454 229 | * @date 2018/9/6 9:43 230 | */ 231 | public static Set keysB(String key) { 232 | try (Jedis jedis = jedisPool.getResource()) { 233 | return jedis.keys(key.getBytes()); 234 | } catch (Exception e) { 235 | throw new CustomException("模糊查询Redis的键集合keysB方法异常:key=" + key + " cause=" + e.getMessage()); 236 | } 237 | } 238 | 239 | /** 240 | * 获取过期剩余时间 241 | * @param key 242 | * @return java.lang.String 243 | * @author Wang926454 244 | * @date 2018/9/11 16:26 245 | */ 246 | public static Long ttl(String key) { 247 | Long result = -2L; 248 | try (Jedis jedis = jedisPool.getResource()) { 249 | result = jedis.ttl(key); 250 | return result; 251 | } catch (Exception e) { 252 | throw new CustomException("获取Redis键过期剩余时间ttl方法异常:key=" + key + " cause=" + e.getMessage()); 253 | } 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /src/main/java/com/wang/util/JwtUtil.java: -------------------------------------------------------------------------------- 1 | package com.wang.util; 2 | 3 | import com.auth0.jwt.JWT; 4 | import com.auth0.jwt.JWTVerifier; 5 | import com.auth0.jwt.algorithms.Algorithm; 6 | import com.auth0.jwt.exceptions.JWTDecodeException; 7 | import com.auth0.jwt.exceptions.TokenExpiredException; 8 | import com.auth0.jwt.interfaces.DecodedJWT; 9 | import com.wang.exception.CustomException; 10 | import com.wang.model.common.Constant; 11 | import com.wang.util.common.Base64ConvertUtil; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | import org.springframework.beans.factory.annotation.Value; 15 | import org.springframework.stereotype.Component; 16 | 17 | import java.io.UnsupportedEncodingException; 18 | import java.util.Date; 19 | 20 | /** 21 | * JAVA-JWT工具类 22 | * @author Wang926454 23 | * @date 2018/8/30 11:45 24 | */ 25 | @Component 26 | public class JwtUtil { 27 | 28 | /** 29 | * logger 30 | */ 31 | private static final Logger logger = LoggerFactory.getLogger(JwtUtil.class); 32 | 33 | /** 34 | * 过期时间改为从配置文件获取 35 | */ 36 | private static String accessTokenExpireTime; 37 | 38 | /** 39 | * JWT认证加密私钥(Base64加密) 40 | */ 41 | private static String encryptJWTKey; 42 | 43 | @Value("${accessTokenExpireTime}") 44 | public void setAccessTokenExpireTime(String accessTokenExpireTime) { 45 | JwtUtil.accessTokenExpireTime = accessTokenExpireTime; 46 | } 47 | 48 | @Value("${encryptJWTKey}") 49 | public void setEncryptJWTKey(String encryptJWTKey) { 50 | JwtUtil.encryptJWTKey = encryptJWTKey; 51 | } 52 | 53 | /** 54 | * 校验token是否正确 55 | * @param token Token 56 | * @return boolean 是否正确 57 | * @author Wang926454 58 | * @date 2018/8/31 9:05 59 | */ 60 | public static boolean verify(String token) { 61 | try { 62 | // 帐号加JWT私钥解密 63 | String secret = getClaim(token, Constant.ACCOUNT) + Base64ConvertUtil.decode(encryptJWTKey); 64 | Algorithm algorithm = Algorithm.HMAC256(secret); 65 | JWTVerifier verifier = JWT.require(algorithm).build(); 66 | verifier.verify(token); 67 | return true; 68 | } catch (UnsupportedEncodingException e) { 69 | logger.error("JWTToken认证解密出现UnsupportedEncodingException异常:{}", e.getMessage()); 70 | throw new CustomException("JWTToken认证解密出现UnsupportedEncodingException异常:" + e.getMessage()); 71 | } 72 | } 73 | 74 | /** 75 | * 获得Token中的信息无需secret解密也能获得 76 | * @param token 77 | * @param claim 78 | * @return java.lang.String 79 | * @author Wang926454 80 | * @date 2018/9/7 16:54 81 | */ 82 | public static String getClaim(String token, String claim) { 83 | try { 84 | DecodedJWT jwt = JWT.decode(token); 85 | // 只能输出String类型,如果是其他类型返回null 86 | return jwt.getClaim(claim).asString(); 87 | } catch (JWTDecodeException e) { 88 | logger.error("解密Token中的公共信息出现JWTDecodeException异常:{}", e.getMessage()); 89 | throw new CustomException("解密Token中的公共信息出现JWTDecodeException异常:" + e.getMessage()); 90 | } 91 | } 92 | 93 | /** 94 | * 生成签名 95 | * @param account 帐号 96 | * @return java.lang.String 返回加密的Token 97 | * @author Wang926454 98 | * @date 2018/8/31 9:07 99 | */ 100 | public static String sign(String account, String currentTimeMillis) { 101 | try { 102 | // 帐号加JWT私钥加密 103 | String secret = account + Base64ConvertUtil.decode(encryptJWTKey); 104 | // 此处过期时间是以毫秒为单位,所以乘以1000 105 | Date date = new Date(System.currentTimeMillis() + Long.parseLong(accessTokenExpireTime) * 1000); 106 | Algorithm algorithm = Algorithm.HMAC256(secret); 107 | // 附带account帐号信息 108 | return JWT.create() 109 | .withClaim("account", account) 110 | .withClaim("currentTimeMillis", currentTimeMillis) 111 | .withExpiresAt(date) 112 | .sign(algorithm); 113 | } catch (UnsupportedEncodingException e) { 114 | logger.error("JWTToken加密出现UnsupportedEncodingException异常:{}", e.getMessage()); 115 | throw new CustomException("JWTToken加密出现UnsupportedEncodingException异常:" + e.getMessage()); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/com/wang/util/UserUtil.java: -------------------------------------------------------------------------------- 1 | package com.wang.util; 2 | 3 | import com.wang.exception.CustomException; 4 | import com.wang.mapper.UserMapper; 5 | import com.wang.model.UserDto; 6 | import com.wang.model.common.Constant; 7 | import org.apache.shiro.SecurityUtils; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Component; 10 | 11 | /** 12 | * 获取当前登录用户工具类 13 | * 14 | * @author wliduo[i@dolyw.com] 15 | * @date 2019/3/15 11:45 16 | */ 17 | @Component 18 | public class UserUtil { 19 | 20 | private final UserMapper userMapper; 21 | 22 | @Autowired 23 | public UserUtil(UserMapper userMapper) { 24 | this.userMapper = userMapper; 25 | } 26 | 27 | /** 28 | * 获取当前登录用户 29 | * 30 | * @param 31 | * @return com.wang.model.UserDto 32 | * @author wliduo[i@dolyw.com] 33 | * @date 2019/3/15 11:48 34 | */ 35 | public UserDto getUser() { 36 | String token = SecurityUtils.getSubject().getPrincipal().toString(); 37 | // 解密获得Account 38 | String account = JwtUtil.getClaim(token, Constant.ACCOUNT); 39 | UserDto userDto = new UserDto(); 40 | userDto.setAccount(account); 41 | userDto = userMapper.selectOne(userDto); 42 | // 用户是否存在 43 | if (userDto == null) { 44 | throw new CustomException("该帐号不存在(The account does not exist.)"); 45 | } 46 | return userDto; 47 | } 48 | 49 | /** 50 | * 获取当前登录用户Id 51 | * 52 | * @param 53 | * @return com.wang.model.UserDto 54 | * @author wliduo[i@dolyw.com] 55 | * @date 2019/3/15 11:48 56 | */ 57 | public Integer getUserId() { 58 | return getUser().getId(); 59 | } 60 | 61 | /** 62 | * 获取当前登录用户Token 63 | * 64 | * @param 65 | * @return com.wang.model.UserDto 66 | * @author wliduo[i@dolyw.com] 67 | * @date 2019/3/15 11:48 68 | */ 69 | public String getToken() { 70 | return SecurityUtils.getSubject().getPrincipal().toString(); 71 | } 72 | 73 | /** 74 | * 获取当前登录用户Account 75 | * 76 | * @param 77 | * @return com.wang.model.UserDto 78 | * @author wliduo[i@dolyw.com] 79 | * @date 2019/3/15 11:48 80 | */ 81 | public String getAccount() { 82 | String token = SecurityUtils.getSubject().getPrincipal().toString(); 83 | // 解密获得Account 84 | return JwtUtil.getClaim(token, Constant.ACCOUNT); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/wang/util/common/Base64ConvertUtil.java: -------------------------------------------------------------------------------- 1 | package com.wang.util.common; 2 | 3 | import java.io.UnsupportedEncodingException; 4 | import java.util.Base64; 5 | 6 | /** 7 | * Base64工具 8 | * @author dolyw.com 9 | * @date 2018/8/21 15:14 10 | */ 11 | public class Base64ConvertUtil { 12 | 13 | private Base64ConvertUtil() {} 14 | 15 | /** 16 | * 加密JDK1.8 17 | * @param str 18 | * @return java.lang.String 19 | * @author dolyw.com 20 | * @date 2018/8/21 15:28 21 | */ 22 | public static String encode(String str) throws UnsupportedEncodingException { 23 | byte[] encodeBytes = Base64.getEncoder().encode(str.getBytes("utf-8")); 24 | return new String(encodeBytes); 25 | } 26 | 27 | /** 28 | * 解密JDK1.8 29 | * @param str 30 | * @return java.lang.String 31 | * @author dolyw.com 32 | * @date 2018/8/21 15:28 33 | */ 34 | public static String decode(String str) throws UnsupportedEncodingException { 35 | byte[] decodeBytes = Base64.getDecoder().decode(str.getBytes("utf-8")); 36 | return new String(decodeBytes); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/wang/util/common/HexConvertUtil.java: -------------------------------------------------------------------------------- 1 | package com.wang.util.common; 2 | 3 | /** 4 | * 进制转换工具 5 | * @author dolyw.com 6 | * @date 2018/8/31 17:23 7 | */ 8 | public class HexConvertUtil { 9 | 10 | private HexConvertUtil() {} 11 | 12 | /** 13 | * 1 14 | */ 15 | private static final Integer INTEGER_1 = 1; 16 | 17 | /** 18 | * 2 19 | */ 20 | private static final Integer INTEGER_2 = 2; 21 | 22 | /** 23 | * 将二进制转换成16进制 24 | * @param bytes 25 | * @return java.lang.String 26 | * @author dolyw.com 27 | * @date 2018/8/31 17:20 28 | */ 29 | public static String parseByte2HexStr(byte[] bytes) { 30 | StringBuilder sb = new StringBuilder(); 31 | for (byte buff : bytes) { 32 | String hex = Integer.toHexString(buff & 0xFF); 33 | if (hex.length() == INTEGER_1) { 34 | hex = '0' + hex; 35 | } 36 | sb.append(hex.toUpperCase()); 37 | } 38 | return sb.toString(); 39 | } 40 | 41 | /** 42 | * 将16进制转换为二进制 43 | * @param hexStr 44 | * @return byte[] 45 | * @author dolyw.com 46 | * @date 2018/8/31 17:21 47 | */ 48 | public static byte[] parseHexStr2Byte(String hexStr) { 49 | if (hexStr.length() < INTEGER_1) { 50 | return null; 51 | } 52 | byte[] result = new byte[hexStr.length() / INTEGER_2]; 53 | for (int i = 0, len = hexStr.length() / INTEGER_2; i < len; i++) { 54 | int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16); 55 | int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16); 56 | result[i] = (byte) (high * 16 + low); 57 | } 58 | return result; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/wang/util/common/JsonConvertUtil.java: -------------------------------------------------------------------------------- 1 | package com.wang.util.common; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | 5 | /** 6 | * Json和Object的互相转换,转List必须Json最外层加[],转Object,Json最外层不要加[] 7 | * @author dolyw.com 8 | * @date 2018/8/9 15:37 9 | */ 10 | public class JsonConvertUtil { 11 | 12 | private JsonConvertUtil() {} 13 | 14 | /** 15 | * JSON 转 Object 16 | */ 17 | public static T jsonToObject(String pojo, Class clazz) { 18 | return JSONObject.parseObject(pojo, clazz); 19 | } 20 | 21 | /** 22 | * Object 转 JSON 23 | */ 24 | public static String objectToJson(T t){ 25 | return JSONObject.toJSONString(t); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/wang/util/common/PropertiesUtil.java: -------------------------------------------------------------------------------- 1 | package com.wang.util.common; 2 | 3 | import com.wang.exception.CustomException; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.io.BufferedReader; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.io.InputStreamReader; 11 | import java.util.Properties; 12 | 13 | /** 14 | * Properties工具 15 | * @author dolyw.com 16 | * @date 2018/8/31 17:29 17 | */ 18 | public class PropertiesUtil { 19 | 20 | private PropertiesUtil() {} 21 | 22 | /** 23 | * logger 24 | */ 25 | private static final Logger logger = LoggerFactory.getLogger(PropertiesUtil.class); 26 | 27 | /** 28 | * PROP 29 | */ 30 | private static final Properties PROP = new Properties(); 31 | 32 | /** 33 | * 读取配置文件 34 | * @param fileName 35 | * @return void 36 | * @author dolyw.com 37 | * @date 2018/8/31 17:29 38 | */ 39 | public static void readProperties(String fileName) { 40 | InputStream in = null; 41 | try { 42 | in = PropertiesUtil.class.getResourceAsStream("/" + fileName); 43 | BufferedReader bf = new BufferedReader(new InputStreamReader(in)); 44 | PROP.load(bf); 45 | } catch (IOException e) { 46 | logger.error("PropertiesUtil工具类读取配置文件出现IOException异常:{}", e.getMessage()); 47 | throw new CustomException("PropertiesUtil工具类读取配置文件出现IOException异常:" + e.getMessage()); 48 | } finally { 49 | try { 50 | if (in != null) { 51 | in.close(); 52 | } 53 | } catch (IOException e) { 54 | logger.error("PropertiesUtil工具类读取配置文件出现IOException异常:{}", e.getMessage()); 55 | throw new CustomException("PropertiesUtil工具类读取配置文件出现IOException异常:" + e.getMessage()); 56 | } 57 | } 58 | } 59 | 60 | /** 61 | * 根据key读取对应的value 62 | * @param key 63 | * @return java.lang.String 64 | * @author dolyw.com 65 | * @date 2018/8/31 17:29 66 | */ 67 | public static String getProperty(String key){ 68 | return PROP.getProperty(key); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/wang/util/common/SerializableUtil.java: -------------------------------------------------------------------------------- 1 | package com.wang.util.common; 2 | 3 | import com.wang.exception.CustomException; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.io.*; 8 | 9 | /** 10 | * Serializable工具(JDK)(也可以使用Protobuf自行百度) 11 | * @author dolyw.com 12 | * @date 2018/9/4 15:13 13 | */ 14 | public class SerializableUtil { 15 | 16 | private SerializableUtil() {} 17 | 18 | /** 19 | * logger 20 | */ 21 | private static final Logger logger = LoggerFactory.getLogger(SerializableUtil.class); 22 | 23 | /** 24 | * 序列化 25 | * @param object 26 | * @return byte[] 27 | * @author dolyw.com 28 | * @date 2018/9/4 15:14 29 | */ 30 | public static byte[] serializable(Object object) { 31 | ByteArrayOutputStream baos = null; 32 | ObjectOutputStream oos = null; 33 | try { 34 | baos = new ByteArrayOutputStream(); 35 | oos = new ObjectOutputStream(baos); 36 | oos.writeObject(object); 37 | return baos.toByteArray(); 38 | } catch (IOException e) { 39 | logger.error("SerializableUtil工具类序列化出现IOException异常:{}", e.getMessage()); 40 | throw new CustomException("SerializableUtil工具类序列化出现IOException异常:" + e.getMessage()); 41 | } finally { 42 | try { 43 | if (oos != null) { 44 | oos.close(); 45 | } 46 | if (baos != null) { 47 | baos.close(); 48 | } 49 | } catch (IOException e) { 50 | logger.error("SerializableUtil工具类反序列化出现IOException异常:{}", e.getMessage()); 51 | throw new CustomException("SerializableUtil工具类反序列化出现IOException异常:" + e.getMessage()); 52 | } 53 | } 54 | } 55 | 56 | /** 57 | * 反序列化 58 | * @param bytes 59 | * @return java.lang.Object 60 | * @author dolyw.com 61 | * @date 2018/9/4 15:14 62 | */ 63 | public static Object unserializable(byte[] bytes) { 64 | ByteArrayInputStream bais = null; 65 | ObjectInputStream ois = null; 66 | try { 67 | bais = new ByteArrayInputStream(bytes); 68 | ois = new ObjectInputStream(bais); 69 | return ois.readObject(); 70 | } catch (ClassNotFoundException e) { 71 | logger.error("SerializableUtil工具类反序列化出现ClassNotFoundException异常:{}", e.getMessage()); 72 | throw new CustomException("SerializableUtil工具类反序列化出现ClassNotFoundException异常:" + e.getMessage()); 73 | } catch (IOException e) { 74 | logger.error("SerializableUtil工具类反序列化出现IOException异常:{}", e.getMessage()); 75 | throw new CustomException("SerializableUtil工具类反序列化出现IOException异常:" + e.getMessage()); 76 | } finally { 77 | try { 78 | if (ois != null) { 79 | ois.close(); 80 | } 81 | if (bais != null) { 82 | bais.close(); 83 | } 84 | } catch (IOException e) { 85 | logger.error("SerializableUtil工具类反序列化出现IOException异常:{}", e.getMessage()); 86 | throw new CustomException("SerializableUtil工具类反序列化出现IOException异常:" + e.getMessage()); 87 | } 88 | } 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/com/wang/util/common/StringUtil.java: -------------------------------------------------------------------------------- 1 | package com.wang.util.common; 2 | 3 | /** 4 | * String工具 5 | * @author dolyw.com 6 | * @date 2018/9/4 14:48 7 | */ 8 | public class StringUtil { 9 | 10 | private StringUtil() {} 11 | 12 | /** 13 | * 定义下划线 14 | */ 15 | private static final char UNDERLINE = '_'; 16 | 17 | /** 18 | * String为空判断(不允许空格) 19 | * @param str 20 | * @return boolean 21 | * @author dolyw.com 22 | * @date 2018/9/4 14:49 23 | */ 24 | public static boolean isBlank(String str) { 25 | return str == null || "".equals(str.trim()); 26 | } 27 | 28 | /** 29 | * String不为空判断(不允许空格) 30 | * @param str 31 | * @return boolean 32 | * @author dolyw.com 33 | * @date 2018/9/4 14:51 34 | */ 35 | public static boolean isNotBlank(String str) { 36 | return !isBlank(str); 37 | } 38 | 39 | /** 40 | * Byte数组为空判断 41 | * @param bytes 42 | * @return boolean 43 | * @author dolyw.com 44 | * @date 2018/9/4 15:39 45 | */ 46 | public static boolean isNull(byte[] bytes) { 47 | // 根据byte数组长度为0判断 48 | return bytes == null || bytes.length == 0; 49 | } 50 | 51 | /** 52 | * Byte数组不为空判断 53 | * @param bytes 54 | * @return boolean 55 | * @author dolyw.com 56 | * @date 2018/9/4 15:41 57 | */ 58 | public static boolean isNotNull(byte[] bytes) { 59 | return !isNull(bytes); 60 | } 61 | 62 | /** 63 | * 驼峰转下划线工具 64 | * @param param 65 | * @return java.lang.String 66 | * @author dolyw.com 67 | * @date 2018/9/4 14:52 68 | */ 69 | public static String camelToUnderline(String param) { 70 | if (isNotBlank(param)) { 71 | int len = param.length(); 72 | StringBuilder sb = new StringBuilder(len); 73 | for (int i = 0; i < len; i++) { 74 | char c = param.charAt(i); 75 | if (Character.isUpperCase(c)) { 76 | sb.append(UNDERLINE); 77 | sb.append(Character.toLowerCase(c)); 78 | } else { 79 | sb.append(c); 80 | } 81 | } 82 | return sb.toString(); 83 | } else { 84 | return ""; 85 | } 86 | } 87 | 88 | /** 89 | * 下划线转驼峰工具 90 | * @param param 91 | * @return java.lang.String 92 | * @author dolyw.com 93 | * @date 2018/9/4 14:52 94 | */ 95 | public static String underlineToCamel(String param) { 96 | if (isNotBlank(param)) { 97 | int len = param.length(); 98 | StringBuilder sb = new StringBuilder(len); 99 | for (int i = 0; i < len; i++) { 100 | char c = param.charAt(i); 101 | if (c == 95) { 102 | i++; 103 | if (i < len) { 104 | sb.append(Character.toUpperCase(param.charAt(i))); 105 | } 106 | } else { 107 | sb.append(c); 108 | } 109 | } 110 | return sb.toString(); 111 | } else { 112 | return ""; 113 | } 114 | } 115 | 116 | /** 117 | * 在字符串两周添加'' 118 | * @param param 119 | * @return java.lang.String 120 | * @author dolyw.com 121 | * @date 2018/9/4 14:53 122 | */ 123 | public static String addSingleQuotes(String param) { 124 | return "\'" + param + "\'"; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | 4 | spring: 5 | datasource: 6 | name: ShiroJwt 7 | url: jdbc:mysql://127.0.0.1:3306/shirojwt?useSSL=false&useUnicode=true&characterEncoding=UTF-8 8 | username: root 9 | password: root 10 | # 使用Druid数据源 11 | type: com.alibaba.druid.pool.DruidDataSource 12 | driver-class-name: com.mysql.jdbc.Driver 13 | druid: 14 | filters: stat 15 | maxActive: 20 16 | initialSize: 1 17 | maxWait: 60000 18 | minIdle: 1 19 | timeBetweenEvictionRunsMillis: 60000 20 | minEvictableIdleTimeMillis: 300000 21 | validationQuery: select 'x' 22 | testWhileIdle: true 23 | testOnBorrow: false 24 | testOnReturn: false 25 | poolPreparedStatements: true 26 | maxOpenPreparedStatements: 20 27 | # 404交给异常处理器处理 28 | mvc: 29 | throw-exception-if-no-handler-found: true 30 | # 404交给异常处理器处理 31 | resources: 32 | add-mappings: false 33 | 34 | mybatis: 35 | # Mybatis配置Mapper路径 36 | mapper-locations: classpath:mapper/*.xml 37 | # Mybatis配置Model类对应 38 | type-aliases-package: com.wang.model.entity 39 | 40 | pagehelper: 41 | params: count=countSql 42 | # 指定分页插件使用哪种方言 43 | helper-dialect: mysql 44 | # 分页合理化参数 pageNum<=0时会查询第一页 pageNum>pages(超过总数时) 会查询最后一页 45 | reasonable: 'true' 46 | support-methods-arguments: 'true' 47 | 48 | mapper: 49 | # 通用Mapper的insertSelective和updateByPrimaryKeySelective中是否判断字符串类型!='' 50 | not-empty: true 51 | 52 | logging: 53 | # Debug打印SQL 54 | level.com.wang.mapper: debug -------------------------------------------------------------------------------- /src/main/resources/config.properties: -------------------------------------------------------------------------------- 1 | # AES密码加密私钥(Base64加密) 2 | encryptAESKey=V2FuZzkyNjQ1NGRTQkFQSUpXVA== 3 | # JWT认证加密私钥(Base64加密) 4 | encryptJWTKey=U0JBUElKV1RkV2FuZzkyNjQ1NA== 5 | # AccessToken过期时间-5分钟-5*60(秒为单位) 6 | accessTokenExpireTime=300 7 | # RefreshToken过期时间-30分钟-30*60(秒为单位) 8 | refreshTokenExpireTime=1800 9 | # Shiro缓存过期时间-5分钟-5*60(秒为单位)(一般设置与AccessToken过期时间一致) 10 | shiroCacheExpireTime=300 11 | 12 | # Redis服务器地址 13 | redis.host=127.0.0.1 14 | # Redis服务器连接端口 15 | redis.port=6379 16 | # Redis服务器连接密码(默认为空) 17 | redis.password= 18 | # 连接超时时间(毫秒) 19 | redis.timeout=10000 20 | # 连接池最大连接数(使用负值表示没有限制) 21 | redis.pool.max-active=200 22 | # 连接池最大阻塞等待时间(使用负值表示没有限制) 23 | redis.pool.max-wait=-1 24 | # 连接池中的最大空闲连接 25 | redis.pool.max-idle=8 26 | # 连接池中的最小空闲连接 27 | redis.pool.min-idle=0 -------------------------------------------------------------------------------- /src/main/resources/generator/generatorConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 26 | 27 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 54 | 55 | 70 | 71 | -------------------------------------------------------------------------------- /src/main/resources/mapper/PermissionMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 30 | 31 | -------------------------------------------------------------------------------- /src/main/resources/mapper/RoleMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/main/resources/mapper/RolePermissionMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/resources/mapper/UserMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/resources/mapper/UserRoleMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/resources/sql/MySQL.sql: -------------------------------------------------------------------------------- 1 | drop database shirojwt; 2 | 3 | create database shirojwt; 4 | 5 | use shirojwt; 6 | 7 | create table user ( 8 | id int primary key auto_increment COMMENT "ID", 9 | account varchar(20) not null unique COMMENT "帐号", 10 | password varchar(80) not null COMMENT "密码", 11 | username varchar(20) not null COMMENT "昵称", 12 | reg_time datetime not null COMMENT "注册时间" 13 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT "用户表"; 14 | 15 | CREATE TABLE role ( 16 | id int primary key auto_increment COMMENT "ID", 17 | name varchar(128) not null unique COMMENT "角色名称" 18 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT "角色表"; 19 | 20 | 21 | CREATE TABLE permission ( 22 | id int primary key auto_increment COMMENT "ID", 23 | name varchar(128) COMMENT '资源名称', 24 | per_code varchar(128) not null unique COMMENT '权限代码字符串' 25 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT "资源表"; 26 | 27 | CREATE TABLE user_role ( 28 | id int primary key auto_increment COMMENT "ID", 29 | user_id int not null COMMENT '用户id', 30 | role_id int not null COMMENT '角色id', 31 | foreign key (user_id) references user (id), 32 | foreign key (role_id) references role (id) 33 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT "用户角色表"; 34 | 35 | CREATE TABLE role_permission ( 36 | id int primary key auto_increment COMMENT "ID", 37 | role_id int not null COMMENT '角色id', 38 | permission_id int not null COMMENT '权限id', 39 | foreign key (role_id) references role (id), 40 | foreign key (permission_id) references permission (id) 41 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT "角色资源表"; 42 | 43 | /* 密码都是帐号 */ 44 | insert into user values(null, "admin", "QUJBNUYyM0M3OTNEN0I4MUFBOTZBOTkwOEI1NDI0MUE=", "admin", now()); 45 | insert into user values(null, "wang", "RTM3MDJENjU0MDg5QURFNUZEQTkxNTNCOEZFQ0MzMkM=", "wang", now()); 46 | insert into user values(null, "guest", "QTNCMzMwREY3MkMwQjRGQjNBQzUyOTM0NTFFMzJCNDg=", "guest", now()); 47 | 48 | insert into role values(null, "admin"); 49 | insert into role values(null, "customer"); 50 | 51 | insert into user_role values(null, 1, 1); 52 | insert into user_role values(null, 2, 2); 53 | 54 | insert into permission values(null, "查看用户", "user:view"); 55 | insert into permission values(null, "操作用户", "user:edit"); 56 | 57 | insert into role_permission values(null, 1, 1); 58 | insert into role_permission values(null, 1, 2); 59 | insert into role_permission values(null, 2, 1); 60 | 61 | --------------------------------------------------------------------------------