From 5df43dc27935a3060250bc5eed6d1341bff6e8b3 Mon Sep 17 00:00:00 2001 From: wuy <1311695042@qq.com> Date: Thu, 6 Jun 2019 11:02:07 +0800 Subject: [PATCH 01/11] =?UTF-8?q?=E4=BC=98=E5=8C=96lombok=E5=BC=95?= =?UTF-8?q?=E5=85=A5=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build.gradle b/build.gradle index de2fb1c..af966cc 100644 --- a/build.gradle +++ b/build.gradle @@ -35,7 +35,12 @@ subprojects { lombokVersion = "1.18.8" } dependencies { + //gradle 4.7版本以上,使用如下方式 +// annotationProcessor("org.projectlombok:lombok:$lombokVersion") +// compileOnly("org.projectlombok:lombok:$lombokVersion") + //gradle 4.7版本以下,使用如下方式 compileOnly("org.projectlombok:lombok:$lombokVersion") + compile("javax.servlet:javax.servlet-api:4.0.1") compile("org.springframework.boot:spring-boot-starter-web:$springBootVersion") // Mybatis From a99850c8bfb1b884758a4e892aa0375e63f54a5e Mon Sep 17 00:00:00 2001 From: godchao Date: Thu, 6 Jun 2019 19:41:00 +0800 Subject: [PATCH 02/11] =?UTF-8?q?1.=20=E6=96=B0=E5=BB=BA=E5=9F=BA=E4=BA=8E?= =?UTF-8?q?vuepress=E7=9A=84=E6=96=87=E6=A1=A3=E6=A8=A1=E5=9D=97=EF=BC=9B2?= =?UTF-8?q?.=20=E6=96=B0=E5=BB=BAdiboot-shiro=E8=AE=A4=E8=AF=81=E6=8E=88?= =?UTF-8?q?=E6=9D=83=E6=A8=A1=E5=9D=97=EF=BC=9B3.=20=E5=9F=BA=E4=BA=8Eshir?= =?UTF-8?q?o=E5=AE=9E=E7=8E=B0=E5=88=B0=E6=AF=8F=E4=B8=AA=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E7=BA=A7=E5=88=AB=E7=9A=84RBAC=E6=8E=88=E6=9D=83?= =?UTF-8?q?=EF=BC=9B4.=20=E5=AE=9E=E7=8E=B0=E4=BE=BF=E4=BA=8E=E6=89=A9?= =?UTF-8?q?=E5=B1=95=E8=AE=A4=E8=AF=81=E6=96=B9=E5=BC=8F=EF=BC=88=E6=AF=94?= =?UTF-8?q?=E5=A6=82=E5=BE=AE=E4=BF=A1=E5=85=AC=E4=BC=97=E5=8F=B7=E3=80=81?= =?UTF-8?q?=E4=BC=81=E4=B8=9A=E5=BE=AE=E4=BF=A1=E7=AD=89=EF=BC=89=E7=9A=84?= =?UTF-8?q?=E8=AE=A4=E8=AF=81=E6=9E=B6=E6=9E=84=E8=AE=BE=E8=AE=A1=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E4=B8=94=E4=BE=BF=E4=BA=8E=E5=A4=9A=E7=A7=8D=E8=AE=A4?= =?UTF-8?q?=E8=AF=81=E6=96=B9=E5=BC=8F=E5=90=8C=E6=97=B6=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 0 -> 6148 bytes .gitignore | 6 +- diboot-docs/README.md | 24 ++ .../diboot-core-docs/.vuepress/config.js | 5 + .../diboot-core-docs/.vuepress/enhanceApp.js | 8 + diboot-docs/diboot-core-docs/README.md | 1 + diboot-docs/diboot-core-docs/package.json | 11 + diboot-example/build.gradle | 3 + .../example/config/SpringMvcConfig.java | 2 + .../controller/AuthTokenController.java | 72 ++++++ .../controller/DepartmentController.java | 4 +- .../java/com/diboot/example/entity/User.java | 3 + .../example/mapper/OrganizationMapper.xml | 2 +- ...perties => application.properties.default} | 0 .../com/diboot/example/ApplicationTest.java | 16 ++ .../service/DepartmentServiceTest.java | 31 +++ .../com/diboot/example/test/BeansTest.java | 37 ++++ .../src/test/resources/application.properties | 45 ---- diboot-shiro/build.gradle | 9 + .../shiro/BaseJwtAuthenticationFilter.java | 90 ++++++++ .../shiro/BaseJwtAuthenticationToken.java | 207 ++++++++++++++++++ .../java/com/diboot/shiro/BaseJwtRealm.java | 142 ++++++++++++ .../com/diboot/shiro/config/AuthType.java | 44 ++++ .../com/diboot/shiro/config/ShiroConfig.java | 97 ++++++++ .../com/diboot/shiro/entity/Permission.java | 29 +++ .../java/com/diboot/shiro/entity/Role.java | 28 +++ .../diboot/shiro/entity/RolePermission.java | 23 ++ .../java/com/diboot/shiro/entity/SysUser.java | 36 +++ .../com/diboot/shiro/entity/UserRole.java | 26 +++ .../diboot/shiro/mapper/PermissionMapper.java | 15 ++ .../diboot/shiro/mapper/PermissionMapper.xml | 5 + .../com/diboot/shiro}/mapper/RoleMapper.java | 4 +- .../com/diboot/shiro/mapper/RoleMapper.xml | 5 + .../shiro/mapper/RolePermissionMapper.java | 15 ++ .../shiro/mapper/RolePermissionMapper.xml | 5 + .../diboot/shiro/mapper/SysUserMapper.java | 15 ++ .../com/diboot/shiro/mapper/SysUserMapper.xml | 5 + .../diboot/shiro/mapper/UserRoleMapper.java | 15 ++ .../diboot/shiro/mapper/UserRoleMapper.xml | 5 + .../diboot/shiro/service/AuthWayService.java | 50 +++++ .../shiro/service/PermissionService.java | 14 ++ .../shiro/service/RolePermissionService.java | 14 ++ .../diboot/shiro}/service/RoleService.java | 4 +- .../diboot/shiro/service/SysUserService.java | 14 ++ .../diboot/shiro/service/UserRoleService.java | 14 ++ .../service/impl/PermissionServiceImpl.java | 21 ++ .../impl/RolePermissionServiceImpl.java | 20 ++ .../shiro}/service/impl/RoleServiceImpl.java | 8 +- .../service/impl/SysUserServiceImpl.java | 20 ++ .../service/impl/UserRoleServiceImpl.java | 21 ++ .../UsernamePasswordAuthWayServiceImpl.java | 79 +++++++ .../java/com/diboot/shiro/util/JwtHelper.java | 194 ++++++++++++++++ .../main/java/com/diboot/shiro/vo/RoleVO.java | 24 ++ .../test/java/com/diboot/shiro/BeansTest.java | 7 + settings.gradle | 2 + 55 files changed, 1540 insertions(+), 56 deletions(-) create mode 100644 .DS_Store create mode 100644 diboot-docs/README.md create mode 100644 diboot-docs/diboot-core-docs/.vuepress/config.js create mode 100644 diboot-docs/diboot-core-docs/.vuepress/enhanceApp.js create mode 100644 diboot-docs/diboot-core-docs/README.md create mode 100644 diboot-docs/diboot-core-docs/package.json create mode 100644 diboot-example/src/main/java/com/diboot/example/controller/AuthTokenController.java rename diboot-example/src/main/resources/{application.properties => application.properties.default} (100%) create mode 100644 diboot-example/src/test/java/com/diboot/example/ApplicationTest.java create mode 100644 diboot-example/src/test/java/com/diboot/example/service/DepartmentServiceTest.java create mode 100644 diboot-example/src/test/java/com/diboot/example/test/BeansTest.java delete mode 100644 diboot-example/src/test/resources/application.properties create mode 100644 diboot-shiro/build.gradle create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtAuthenticationFilter.java create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtAuthenticationToken.java create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtRealm.java create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/config/AuthType.java create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/config/ShiroConfig.java create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/entity/Permission.java create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/entity/Role.java create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/entity/RolePermission.java create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/entity/SysUser.java create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/entity/UserRole.java create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/mapper/PermissionMapper.java create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/mapper/PermissionMapper.xml rename {diboot-example/src/main/java/com/diboot/example => diboot-shiro/src/main/java/com/diboot/shiro}/mapper/RoleMapper.java (73%) create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/mapper/RoleMapper.xml create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/mapper/RolePermissionMapper.java create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/mapper/RolePermissionMapper.xml create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/mapper/SysUserMapper.java create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/mapper/SysUserMapper.xml create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/mapper/UserRoleMapper.java create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/mapper/UserRoleMapper.xml create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/service/AuthWayService.java create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/service/PermissionService.java create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/service/RolePermissionService.java rename {diboot-example/src/main/java/com/diboot/example => diboot-shiro/src/main/java/com/diboot/shiro}/service/RoleService.java (72%) create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/service/SysUserService.java create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/service/UserRoleService.java create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/service/impl/PermissionServiceImpl.java create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/service/impl/RolePermissionServiceImpl.java rename {diboot-example/src/main/java/com/diboot/example => diboot-shiro/src/main/java/com/diboot/shiro}/service/impl/RoleServiceImpl.java (67%) create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/service/impl/SysUserServiceImpl.java create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/service/impl/UserRoleServiceImpl.java create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/service/impl/UsernamePasswordAuthWayServiceImpl.java create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/util/JwtHelper.java create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/vo/RoleVO.java create mode 100644 diboot-shiro/src/test/java/com/diboot/shiro/BeansTest.java diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..595d6c705baa1c9e7b7a9c7cf16abfe6d4ee73b5 GIT binary patch literal 6148 zcmeHKOKJl#43(NJ23lyAUHSr9cgjBODSFJ8JC(S(R9G(i?)KxBAu>cX7IKrU+}+R~JsX-SU*3;o3* z$^D8t|EdR~&doD*! z$+a`!3^)VMfHQDD130rqs!K)J&VV!E416*m`$IqzjD}gUY#mUg1OUo4x(IBkB_t*o zM#HR#83=1AP(#^D4AyXr2lI=DSy97@t@vPj^GET*yOWqvo@gJp^1oJ76SsicL_j8_K}-1sq;Z> Z_(j94D67bL2?zQ`pb+B98TbVT-T@1RHk1GW literal 0 HcmV?d00001 diff --git a/.gitignore b/.gitignore index 889ee4e..368b438 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ # Ignore Gradle project-specific cache directory .gradle +*.iml +application.properties +application-*.properties # Ignore Gradle build output directory -build +build/ +out/ /.idea/ diff --git a/diboot-docs/README.md b/diboot-docs/README.md new file mode 100644 index 0000000..4797162 --- /dev/null +++ b/diboot-docs/README.md @@ -0,0 +1,24 @@ +## 我们使用了什么? +* 这里的所有文档都使用vuepress这个工具来处理,关于vuepress的细节可以阅览[官方文档](https://vuepress.vuejs.org/zh/)。 +* 我们采用markdown来编写项目文档,如果您也要参与文档的贡献,也需要先了解markdown相关内容。 +## 我们应该怎样开始? +1. 假设您本地已经安装了node环境,npm与yarn至少有其一了,那么打开命令行,我们先运行以下命令全局安装(已经安装过的不必再安装)vuepress: + ```bash + yarn global add vuepress + # 或者: + npm install -g vuepress + ``` +2. 拉取该项目到本地,在命令行打开该目录下的相关文档目录。 +3. 运行以下命令,开始启动一个文档的server: + ```bash + yarn serve + # 或者: + npm run serve + ``` +## 我们怎样存放静态文件? +* 我们的图片文件以及其他类型的文件,建议存放在相应文档项目下的.vuepress/public/目录下。 +## 我们如何引入存放的静态文件? +* 我们统一使用$withBase()方法来获取存放在.vuepress/public目录下的静态文件,示例如下: + ```markdown + ![图片]($withBase('/image.png')) + ``` \ No newline at end of file diff --git a/diboot-docs/diboot-core-docs/.vuepress/config.js b/diboot-docs/diboot-core-docs/.vuepress/config.js new file mode 100644 index 0000000..a6e9953 --- /dev/null +++ b/diboot-docs/diboot-core-docs/.vuepress/config.js @@ -0,0 +1,5 @@ +module.exports = { + title: 'diboot-core开发文档', + description: 'diboot-core相关概念、接口、开发技巧相关的文档说明等。', + base: '/diboot-core-docs/' +} \ No newline at end of file diff --git a/diboot-docs/diboot-core-docs/.vuepress/enhanceApp.js b/diboot-docs/diboot-core-docs/.vuepress/enhanceApp.js new file mode 100644 index 0000000..c68bfaf --- /dev/null +++ b/diboot-docs/diboot-core-docs/.vuepress/enhanceApp.js @@ -0,0 +1,8 @@ +export default ({ + Vue, // VuePress 正在使用的 Vue 构造函数 + options, // 附加到根实例的一些选项 + router, // 当前应用的路由实例 + siteData // 站点元数据 +}) => { + // 做相关的处理 +} \ No newline at end of file diff --git a/diboot-docs/diboot-core-docs/README.md b/diboot-docs/diboot-core-docs/README.md new file mode 100644 index 0000000..8079186 --- /dev/null +++ b/diboot-docs/diboot-core-docs/README.md @@ -0,0 +1 @@ +# Hello VuePress! diff --git a/diboot-docs/diboot-core-docs/package.json b/diboot-docs/diboot-core-docs/package.json new file mode 100644 index 0000000..4c40505 --- /dev/null +++ b/diboot-docs/diboot-core-docs/package.json @@ -0,0 +1,11 @@ +{ + "name": "diboot-core-docs", + "version": "1.0.0", + "description": "", + "scripts": { + "serve": "vuepress dev ./", + "build": "vuepress build ./" + }, + "author": "yang", + "license": "Apache-2.0" +} diff --git a/diboot-example/build.gradle b/diboot-example/build.gradle index ef9a964..1663ce7 100644 --- a/diboot-example/build.gradle +++ b/diboot-example/build.gradle @@ -1,4 +1,7 @@ dependencies { + compile project(":diboot-core") + compile project(":diboot-shiro") + testCompile group: 'junit', name: 'junit', version: '4.12' } \ No newline at end of file diff --git a/diboot-example/src/main/java/com/diboot/example/config/SpringMvcConfig.java b/diboot-example/src/main/java/com/diboot/example/config/SpringMvcConfig.java index 6f4e9fe..bc1ba43 100644 --- a/diboot-example/src/main/java/com/diboot/example/config/SpringMvcConfig.java +++ b/diboot-example/src/main/java/com/diboot/example/config/SpringMvcConfig.java @@ -5,6 +5,7 @@ import com.alibaba.fastjson.support.config.FastJsonConfig; import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; import com.diboot.core.util.D; import com.diboot.core.util.DateConverter; +import org.mybatis.spring.annotation.MapperScan; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -31,6 +32,7 @@ import java.util.List; @EnableAutoConfiguration @EnableTransactionManagement(proxyTargetClass=true) @ComponentScan(basePackages={"com.diboot"}) +@MapperScan({"com.diboot.*.mapper"}) public class SpringMvcConfig implements WebMvcConfigurer{ private static final Logger log = LoggerFactory.getLogger(SpringMvcConfig.class); diff --git a/diboot-example/src/main/java/com/diboot/example/controller/AuthTokenController.java b/diboot-example/src/main/java/com/diboot/example/controller/AuthTokenController.java new file mode 100644 index 0000000..cd85102 --- /dev/null +++ b/diboot-example/src/main/java/com/diboot/example/controller/AuthTokenController.java @@ -0,0 +1,72 @@ +package com.diboot.example.controller; + +import com.diboot.core.vo.JsonResult; +import com.diboot.core.vo.Status; +import com.diboot.shiro.BaseJwtAuthenticationToken; +import com.diboot.shiro.config.AuthType; +import com.diboot.shiro.entity.SysUser; +import com.diboot.shiro.service.AuthWayService; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.subject.Subject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Map; + +@RestController +@RequestMapping("/token") +public class AuthTokenController { + + private static final Logger logger = LoggerFactory.getLogger(AuthTokenController.class); + + @Autowired + private Map authWayServiceMap; + + /*** + * 用户名密码登录接口 + * @param sysUser + * @param request + * @param response + * @return + * @throws Exception + */ + @PostMapping("/login") + public JsonResult login(@ModelAttribute SysUser sysUser, HttpServletRequest request, HttpServletResponse response) throws Exception{ + String errorMsg = "登录失败"; + try{ + BaseJwtAuthenticationToken authToken = new BaseJwtAuthenticationToken(authWayServiceMap, sysUser.getUsername(), sysUser.getPassword(), AuthType.USERNAME_PASSWORD); + Subject subject = SecurityUtils.getSubject(); + subject.login(authToken); + + if (subject.isAuthenticated()){ + logger.debug("申请token成功!authtoken="+authToken.getCredentials()); + String token = (String)authToken.getCredentials(); + // 跳转到首页 + return new JsonResult(token, "Token申请成功"); + } + } + catch (Exception e) { + logger.error("登录失败", e); + } + + + return new JsonResult(Status.FAIL_INVALID_TOKEN, errorMsg); + } + + @PostMapping("/logout") + public JsonResult logout(HttpServletRequest request, HttpServletResponse response) throws Exception{ + Subject subject = SecurityUtils.getSubject(); + if (subject.isAuthenticated() || subject.getPrincipals() != null){ + subject.logout(); + } + + return new JsonResult(Status.OK, new String[]{"退出登录成功"}); + } +} diff --git a/diboot-example/src/main/java/com/diboot/example/controller/DepartmentController.java b/diboot-example/src/main/java/com/diboot/example/controller/DepartmentController.java index 2504593..39ba325 100644 --- a/diboot-example/src/main/java/com/diboot/example/controller/DepartmentController.java +++ b/diboot-example/src/main/java/com/diboot/example/controller/DepartmentController.java @@ -2,7 +2,6 @@ package com.diboot.example.controller; import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.diboot.core.binding.manager.AnnotationBindingManager; import com.diboot.core.controller.BaseCrudRestController; import com.diboot.core.service.BaseService; import com.diboot.core.util.BeanUtils; @@ -14,6 +13,8 @@ import com.diboot.example.entity.Department; import com.diboot.example.entity.Organization; import com.diboot.example.service.DepartmentService; import com.diboot.example.vo.DepartmentVO; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.apache.shiro.authz.annotation.RequiresRoles; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.ui.ModelMap; import org.springframework.validation.BindingResult; @@ -43,6 +44,7 @@ public class DepartmentController extends BaseCrudRestController { * @return * @throws Exception */ + @RequiresPermissions("department:list") @GetMapping("/list") public JsonResult getVOList(HttpServletRequest request) throws Exception{ QueryWrapper queryWrapper = buildQuery(request); diff --git a/diboot-example/src/main/java/com/diboot/example/entity/User.java b/diboot-example/src/main/java/com/diboot/example/entity/User.java index a0febbe..a1a46df 100644 --- a/diboot-example/src/main/java/com/diboot/example/entity/User.java +++ b/diboot-example/src/main/java/com/diboot/example/entity/User.java @@ -19,6 +19,9 @@ public class User extends BaseEntity { @TableField private String username; + @TableField + private String password; + @TableField private String gender; diff --git a/diboot-example/src/main/java/com/diboot/example/mapper/OrganizationMapper.xml b/diboot-example/src/main/java/com/diboot/example/mapper/OrganizationMapper.xml index 507ebdd..9f6531a 100644 --- a/diboot-example/src/main/java/com/diboot/example/mapper/OrganizationMapper.xml +++ b/diboot-example/src/main/java/com/diboot/example/mapper/OrganizationMapper.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/diboot-example/src/main/resources/application.properties b/diboot-example/src/main/resources/application.properties.default similarity index 100% rename from diboot-example/src/main/resources/application.properties rename to diboot-example/src/main/resources/application.properties.default diff --git a/diboot-example/src/test/java/com/diboot/example/ApplicationTest.java b/diboot-example/src/test/java/com/diboot/example/ApplicationTest.java new file mode 100644 index 0000000..63a28fa --- /dev/null +++ b/diboot-example/src/test/java/com/diboot/example/ApplicationTest.java @@ -0,0 +1,16 @@ +package com.diboot.example; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads(){ + + } +} diff --git a/diboot-example/src/test/java/com/diboot/example/service/DepartmentServiceTest.java b/diboot-example/src/test/java/com/diboot/example/service/DepartmentServiceTest.java new file mode 100644 index 0000000..3642572 --- /dev/null +++ b/diboot-example/src/test/java/com/diboot/example/service/DepartmentServiceTest.java @@ -0,0 +1,31 @@ +package com.diboot.example.service; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.diboot.core.util.BeanUtils; +import com.diboot.core.util.V; +import com.diboot.example.ApplicationTest; +import com.diboot.example.entity.Department; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; + +import static org.junit.Assert.*; + +@Component +public class DepartmentServiceTest extends ApplicationTest { + + @Autowired + private DepartmentService departmentService; + + @Test + public void getEntityListTest() throws Exception{ + QueryWrapper query = new QueryWrapper<>(); + query.eq(BeanUtils.convertToFieldName(Department::getName), "研发部"); + List departmentList = departmentService.getEntityList(query); + Assert.assertTrue(V.notEmpty(departmentList)); + } + +} \ No newline at end of file diff --git a/diboot-example/src/test/java/com/diboot/example/test/BeansTest.java b/diboot-example/src/test/java/com/diboot/example/test/BeansTest.java new file mode 100644 index 0000000..5301bd7 --- /dev/null +++ b/diboot-example/src/test/java/com/diboot/example/test/BeansTest.java @@ -0,0 +1,37 @@ +package com.diboot.example.test; + +import com.diboot.core.util.V; +import com.diboot.example.ApplicationTest; +import com.diboot.shiro.service.AuthWayService; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; + +import java.util.Map; + +@Component +public class BeansTest extends ApplicationTest { + + @Autowired + private ApplicationContext applicationContext; + + @Autowired + Map classMap; + + @Test + public void testGetImplMap() throws Exception{ + Map authWayServiceMap = applicationContext.getBeansOfType(AuthWayService.class); + Assert.assertTrue(V.notEmpty(authWayServiceMap)); + } + + @Test + public void testAuthwiredImplMap() throws Exception{ + Assert.assertTrue(V.notEmpty(classMap)); + for (Map.Entry entry : classMap.entrySet()){ + + } + } + +} diff --git a/diboot-example/src/test/resources/application.properties b/diboot-example/src/test/resources/application.properties deleted file mode 100644 index 96584d9..0000000 --- a/diboot-example/src/test/resources/application.properties +++ /dev/null @@ -1,45 +0,0 @@ -server.port=8080 -server.servlet.context-path=/example -#10秒超时 -spring.server.connectionTimeout=10000 -spring.server.protocol=org.apache.coyote.http11.Http11Nio2Protocol -spring.server.redirectPort=443 -spring.server.compression=on - -# spring config -spring.devtools.restart.enabled=true - -#datasource config -spring.datasource.url=jdbc:mysql://localhost:3306/diboot_example?characterEncoding=utf8&serverTimezone=GMT%2B8 -spring.datasource.username=diboot -spring.datasource.password=123456 -spring.datasource.hikari.maximum-pool-size=5 -spring.datasource.hikari.data-source-properties.useInformationSchema=true -spring.datasource.hikari.data-source-properties.nullCatalogMeansCurrent=true -# 数据库驱动 -spring.datasource.hikari.driver-class-name=com.mysql.cj.jdbc.Driver - -#字符集utf-8 -spring.http.encoding.charset=UTF-8 -spring.http.encoding.enabled=true -spring.http.encoding.force=true - -# mybatis配置 -#mybatis.configuration.cache-enabled=false -#mybatis.configuration.lazy-loading-enabled=true -#mybatis.configuration.map-underscore-to-camel-case=true -#mybatis.configuration.multiple-result-sets-enabled=false -#mybatis.configuration.use-generated-keys=true -#mybatis.configuration.auto-mapping-behavior=full -#mybatis.configuration.default-statement-timeout=60 -#mybatis.configuration.log-impl=org.apache.ibatis.logging.log4j2.Log4j2Impl - -# logging config -logging.pattern.console=%clr{%d{MM-dd HH:mm:ss.SSS}}{faint} %clr{%5p} %clr{${PID}}{faint} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n%xwEx -logging.level.root=info -logging.level.org.apache=info -logging.level.org.hibernate.validator=info -logging.level.org.springframework=info -logging.level.com.zaxxer.hikari=info -logging.level.com.diboot=debug -logging.level.org.mybatis=debug \ No newline at end of file diff --git a/diboot-shiro/build.gradle b/diboot-shiro/build.gradle new file mode 100644 index 0000000..acaa5f0 --- /dev/null +++ b/diboot-shiro/build.gradle @@ -0,0 +1,9 @@ +dependencies { + compile project(":diboot-core") + + compile("org.apache.shiro:shiro-spring:1.4.1") + compile("com.auth0:java-jwt:3.4.1", + "io.jsonwebtoken:jjwt:0.9.1") + + testCompile group: 'junit', name: 'junit', version: '4.12' +} \ No newline at end of file diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtAuthenticationFilter.java b/diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtAuthenticationFilter.java new file mode 100644 index 0000000..4cfc161 --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtAuthenticationFilter.java @@ -0,0 +1,90 @@ +package com.diboot.shiro; + +import com.diboot.core.util.JSON; +import com.diboot.core.util.V; +import com.diboot.core.vo.JsonResult; +import com.diboot.core.vo.Status; +import com.diboot.shiro.util.JwtHelper; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; + +/** + * JWT 认证过滤器 + * @author Mazc@dibo.ltd + * @version 2018/1/6 + * + */ +public class BaseJwtAuthenticationFilter extends BasicHttpAuthenticationFilter { + private static final Logger logger = LoggerFactory.getLogger(BaseJwtAuthenticationFilter.class); + + /** + * Shiro权限拦截核心方法 返回true允许访问,这里使用JWT进行认证 + */ + @Override + protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { + HttpServletRequest httpRequest = (HttpServletRequest) request; + // 获取Token + String accessToken = JwtHelper.getRequestToken(httpRequest); + if (V.isEmpty(accessToken)) { + logger.warn("Token为空!url="+httpRequest.getRequestURL()); + return false; + } + //获取userId + String account = JwtHelper.getAccountFromToken(accessToken); + if(V.notEmpty(account)){ + logger.debug("Token认证成功!account="+account); + return true; + } + logger.debug("Token认证失败!"); + return false; + } + + /** + * 当访问拒绝时是否已经处理了;如果返回true表示需要继续处理;如果返回false表示该拦截器实例已经处理 + * @param + */ + @Override + protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { + logger.debug("Token认证: onAccessDenied"); + JsonResult jsonResult = new JsonResult(Status.FAIL_INVALID_TOKEN); + this.responseJson((HttpServletResponse) response, jsonResult); + return false; + } + + /*** + * 返回json格式错误信息 + * @param response + * @param jsonResult + */ + protected void responseJson(HttpServletResponse response, JsonResult jsonResult){ + // 处理异步请求 + PrintWriter pw = null; + try { + response.setStatus(HttpStatus.OK.value()); + response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); + pw = response.getWriter(); + pw.write(JSON.stringify(jsonResult)); + pw.flush(); + } + catch (IOException e) { + logger.error("处理异步请求异常", e); + } + finally { + if (pw != null) { + pw.close(); + } + } + } + +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtAuthenticationToken.java b/diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtAuthenticationToken.java new file mode 100644 index 0000000..d76ae3a --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtAuthenticationToken.java @@ -0,0 +1,207 @@ +package com.diboot.shiro; + +import com.diboot.core.util.V; +import com.diboot.shiro.config.AuthType; +import com.diboot.shiro.service.AuthWayService; +import com.diboot.shiro.util.JwtHelper; +import org.apache.shiro.authc.AuthenticationToken; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import java.util.Map; + +/** + * @author Yangz@dibo.ltd + * @version 2018/1/6 + * + */ +@Component +public class BaseJwtAuthenticationToken implements AuthenticationToken { + + private static final Logger logger = LoggerFactory.getLogger(BaseJwtAuthenticationToken.class); + + // 登录用的账号(此处的这个账号是一种抽象的概念) + private String account; + + // 登录用的密码 (此处的这个密码也是一种抽象的概念) + private String password; + + // 登录使用方式 + private AuthType authType; + + // auth token + private String authtoken; + + // 申请token的密码 + private String applyTokenSecret; + + // 签名key (默认SIGN_KEY,配置signKey, 或微信state, 密码等) + private String signKey = JwtHelper.SIGN_KEY; + + // 过期时间 + private long expiresInMinutes = JwtHelper.EXPIRES_IN_MINUTES; + + private Map authWayServiceMap; + + // 默认构造函数 + public BaseJwtAuthenticationToken(){ + + } + + public BaseJwtAuthenticationToken(Map authWayServiceMap){ + this.authWayServiceMap = authWayServiceMap; + } + + /*** + * 用户名码形式的授权 + * @param account + * @param password + */ + public BaseJwtAuthenticationToken(Map authWayServiceMap, String account, String password){ + this.authWayServiceMap = authWayServiceMap; + this.account = account; + this.password = password; + // 设置为默认登录方式 + this.authType = AuthType.USERNAME_PASSWORD; + + this.initJwtAuthenticationToken(account, signKey, false); + } + + /*** + * 以用户名密码这类形式的其他类型授权 + * @param account + * @param password + * @param authType + */ + public BaseJwtAuthenticationToken(Map authWayServiceMap, String account, String password, AuthType authType){ + this.authWayServiceMap = authWayServiceMap; + this.account = account; + this.password = password; + this.authType = authType; + + this.initJwtAuthenticationToken(account, signKey, getAuthWayService().isPreliminaryVerified()); + } + + /*** + * 其他授权种类的适配构造函数 + * @param account + * @param authType + */ + public BaseJwtAuthenticationToken(Map authWayServiceMap, String account, AuthType authType){ + this.authWayServiceMap = authWayServiceMap; + this.account = account; + this.authType = authType; + + this.initJwtAuthenticationToken(account, signKey, getAuthWayService().isPreliminaryVerified()); + } + + public AuthWayService getAuthWayService(){ + + if (V.notEmpty(authWayServiceMap)){ + + for (AuthWayService authWayService : authWayServiceMap.values()){ + if (authWayService.authType() == authType){ + authWayService.initByToken(this); + return authWayService; + } + } + } + return null; + } + + /*** + * 初始化认证token + * @param account + * @param password + * @param preliminaryVerified + */ + private void initJwtAuthenticationToken(String account, String password, boolean preliminaryVerified){ + if(this.account != null){ + Long expiresInMinutes = this.getAuthWayService().getExpiresInMinutes(); + this.expiresInMinutes = V.notEmpty(expiresInMinutes) ? expiresInMinutes : this.expiresInMinutes; + this.authtoken = JwtHelper.generateToken(this.account, this.signKey, this.expiresInMinutes); + } + } + + @Override + public Object getPrincipal() { + return account; + } + + @Override + public Object getCredentials() { + return authtoken; + } + + public String getApplyTokenSecret() { + return applyTokenSecret; + } + + /*** + * 验证失败的时候清空token + */ + public void clearAuthtoken(){ + this.authtoken = null; + } + + public void setApplyTokenSecret(String applyTokenSecret) { + this.applyTokenSecret = applyTokenSecret; + } + + public String getSignKey() { + return signKey; + } + + public void setSignKey(String signKey) { + this.signKey = signKey; + } + + public String getAccount() { + return account; + } + + public void setAccount(String account) { + this.account = account; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public AuthType getAuthType() { + return authType; + } + + public void setAuthType(AuthType authType) { + this.authType = authType; + } + + public String getAuthtoken() { + return authtoken; + } + + public void setAuthtoken(String authtoken) { + this.authtoken = authtoken; + } + + public long getExpiresInMinutes() { + return expiresInMinutes; + } + + public void setExpiresInMinutes(long expiresInMinutes) { + this.expiresInMinutes = expiresInMinutes; + } + + public Map getAuthWayServiceMap() { + return authWayServiceMap; + } + + public void setAuthWayServiceMap(Map authWayServiceMap) { + this.authWayServiceMap = authWayServiceMap; + } +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtRealm.java b/diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtRealm.java new file mode 100644 index 0000000..99c066a --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtRealm.java @@ -0,0 +1,142 @@ +package com.diboot.shiro; + +import com.baomidou.mybatisplus.core.conditions.query.Query; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.diboot.core.entity.BaseEntity; +import com.diboot.core.util.BeanUtils; +import com.diboot.core.util.V; +import com.diboot.shiro.entity.Permission; +import com.diboot.shiro.entity.Role; +import com.diboot.shiro.entity.UserRole; +import com.diboot.shiro.service.*; +import com.diboot.shiro.vo.RoleVO; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.SimpleAuthenticationInfo; +import org.apache.shiro.authz.AuthorizationInfo; +import org.apache.shiro.authz.SimpleAuthorizationInfo; +import org.apache.shiro.realm.AuthorizingRealm; +import org.apache.shiro.subject.PrincipalCollection; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.*; +import java.util.stream.Collectors; + +public class BaseJwtRealm extends AuthorizingRealm { + + @Autowired + private UserRoleService userRoleService; + + @Autowired + private RoleService roleService; + + public boolean supports(AuthenticationToken token) { + return token != null && token instanceof BaseJwtAuthenticationToken; + } + + + public Class getAuthenticationTokenClass() { + return BaseJwtRealm.class; + } + + /*** + * 认证 + * @param token + * @return + * @throws AuthenticationException + */ + @Override + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { + BaseJwtAuthenticationToken jwtToken = (BaseJwtAuthenticationToken) token; + + String account = (String) jwtToken.getPrincipal(); + + if (V.isEmpty(account)){ + throw new AuthenticationException("无效的token"); + } + else { + // 获取认证方式 + AuthWayService authWayService = jwtToken.getAuthWayService(); + + BaseEntity user = authWayService.getUser(); + + // 登录失败则抛出相关异常 + if (user == null){ + throw new AuthenticationException("用户不存在"); + } + + if (authWayService.requirePassword() && !authWayService.isPasswordMatch()){ + throw new AuthenticationException("用户名或密码错误"); + } + return new SimpleAuthenticationInfo(user, jwtToken.getCredentials(), this.getName()); + } + } + + + /*** + * 授权 + * @param principals + * @return + */ + @Override + protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { + SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); + + // 获取用户类型 + String userType = principals.getPrimaryPrincipal().getClass().getSimpleName(); + BaseEntity user = (BaseEntity) principals.getPrimaryPrincipal(); + + // 根据用户类型与用户id获取roleList + QueryWrapper query = new QueryWrapper<>(); + query.lambda() + .eq(UserRole::getUserType, userType) + .eq(UserRole::getUserId, user.getId()); + List userRoleList = userRoleService.getEntityList(query); + if (V.isEmpty(userRoleList)){ + return authorizationInfo; + } + List roleIdList = userRoleList.stream() + .map(UserRole::getRoleId) + .collect(Collectors.toList()); + if (V.isEmpty(roleIdList)){ + return authorizationInfo; + } + + // 获取角色列表,并使用VO自动多对多关联permission + QueryWrapper roleQuery = new QueryWrapper<>(); + roleQuery + .lambda() + .in(Role::getId, roleIdList); + List roleVOList = roleService.getViewObjectList(roleQuery, null, RoleVO.class); + + if (V.isEmpty(roleVOList)){ + return authorizationInfo; + } + + // 整理所有权限许可列表 + Set allStringRoleList = new HashSet<>(); + Set allStringPermissionList = new HashSet<>(); + for (RoleVO roleVO : roleVOList){ + // 添加当前角色到角色列表中 + allStringRoleList.add(roleVO.getCode()); + + if (V.isEmpty(roleVO.getPermissionList())){ + continue; + } + + // 添加当前所有权限许可到权限许可列表 + List stringPermissionList = roleVO.getPermissionList().stream() + .map(Permission::getPermissionCode) + .collect(Collectors.toList()); + allStringPermissionList.addAll(stringPermissionList); + } + + // 将所有角色和权限许可授权给用户 + authorizationInfo.setRoles(allStringRoleList); + authorizationInfo.setStringPermissions(allStringPermissionList); + + return authorizationInfo; + } + +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/config/AuthType.java b/diboot-shiro/src/main/java/com/diboot/shiro/config/AuthType.java new file mode 100644 index 0000000..89070f6 --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/config/AuthType.java @@ -0,0 +1,44 @@ +package com.diboot.shiro.config; + +import lombok.Data; + +public enum AuthType { + + USERNAME_PASSWORD(1, true, "账号密码"), + WX_MP(2, false, "公众号"), + WX_CP(3, false, "企业微信"); + + private AuthType(int code, boolean requirePassword, String label){ + this.code = code; + this.requirePassword = requirePassword; + this.label = label; + } + + private int code; + private boolean requirePassword; + private String label; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public boolean isRequirePassword() { + return requirePassword; + } + + public void setRequirePassword(boolean requirePassword) { + this.requirePassword = requirePassword; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/config/ShiroConfig.java b/diboot-shiro/src/main/java/com/diboot/shiro/config/ShiroConfig.java new file mode 100644 index 0000000..180083b --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/config/ShiroConfig.java @@ -0,0 +1,97 @@ +package com.diboot.shiro.config; + +import com.diboot.shiro.BaseJwtAuthenticationFilter; +import com.diboot.shiro.BaseJwtRealm; +import org.apache.shiro.mgt.SecurityManager; +import org.apache.shiro.realm.Realm; +import org.apache.shiro.spring.LifecycleBeanPostProcessor; +import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; +import org.apache.shiro.spring.web.ShiroFilterFactoryBean; +import org.apache.shiro.web.filter.authc.AnonymousFilter; +import org.apache.shiro.web.mgt.DefaultWebSecurityManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; + +import javax.servlet.Filter; +import java.util.LinkedHashMap; +import java.util.Map; + +@Configuration +public class ShiroConfig { + private static final Logger logger = LoggerFactory.getLogger(ShiroConfig.class); + + @Bean + public Realm realm(){ + BaseJwtRealm realm = new BaseJwtRealm(); + return realm; + } + + @Bean + public SecurityManager securityManager(){ + DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); + securityManager.setRealm(realm()); + return securityManager; + } + + @Bean + public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){ + ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); + + //Shiro securityManager + shiroFilterFactoryBean.setSecurityManager(securityManager); + + //用户访问未对其授权的资源时的错误提示页面 + shiroFilterFactoryBean.setUnauthorizedUrl("/error"); + + Map filterChainDefinitionMap = new LinkedHashMap<>(); + + filterChainDefinitionMap.put("/", "anon"); + filterChainDefinitionMap.put("/static/**", "anon"); + filterChainDefinitionMap.put("/token/login", "anon"); + filterChainDefinitionMap.put("/error", "anon"); + filterChainDefinitionMap.put("/token/logout", "logout"); + filterChainDefinitionMap.put("/**", "jwt"); + + shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); + + // 设置过滤器 + Map filters = new LinkedHashMap<>(); + filters.put("anon", new AnonymousFilter()); + filters.put("jwt", new BaseJwtAuthenticationFilter()); + shiroFilterFactoryBean.setFilters(filters); + + return shiroFilterFactoryBean; + } + + /** + * Shiro生命周期处理器 + */ + @Bean + public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { + return new LifecycleBeanPostProcessor(); + } + + /*** + * 以下两个为使用注解权限相关的配置 + * @return + */ + @Bean + @DependsOn({"lifecycleBeanPostProcessor"}) + public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() { + DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); + advisorAutoProxyCreator.setProxyTargetClass(true); + return advisorAutoProxyCreator; + } + + @Bean + public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() { + AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); + authorizationAttributeSourceAdvisor.setSecurityManager(securityManager()); + return authorizationAttributeSourceAdvisor; + } + +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/entity/Permission.java b/diboot-shiro/src/main/java/com/diboot/shiro/entity/Permission.java new file mode 100644 index 0000000..e2bf5ff --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/entity/Permission.java @@ -0,0 +1,29 @@ +package com.diboot.shiro.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.diboot.core.entity.BaseEntity; +import lombok.Data; + +/** + * @author Yangz + * @version v2.0 + * @date 2019/6/6 + */ +@Data +public class Permission extends BaseEntity { + + private static final long serialVersionUID = 7713768302925692987L; + + @TableField + private String menuCode; + + @TableField + private String menuName; + + @TableField + private String permissionCode; + + @TableField + private String permissionName; + +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/entity/Role.java b/diboot-shiro/src/main/java/com/diboot/shiro/entity/Role.java new file mode 100644 index 0000000..b2acd42 --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/entity/Role.java @@ -0,0 +1,28 @@ +package com.diboot.shiro.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.diboot.core.entity.BaseEntity; +import lombok.Data; + +import java.util.List; + +/** + * @author Yangz + * @version v2.0 + * @date 2019/6/6 + */ +@Data +public class Role extends BaseEntity { + + private static final long serialVersionUID = 5433209472424293571L; + + @TableField + private String name; + + @TableField + private String code; + + @TableField(exist = false) + private List permissionList; + +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/entity/RolePermission.java b/diboot-shiro/src/main/java/com/diboot/shiro/entity/RolePermission.java new file mode 100644 index 0000000..f2246d6 --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/entity/RolePermission.java @@ -0,0 +1,23 @@ +package com.diboot.shiro.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.diboot.core.entity.BaseEntity; +import lombok.Data; + +/** + * @author Yangz + * @version v2.0 + * @date 2019/6/6 + */ +@Data +public class RolePermission extends BaseEntity { + + private static final long serialVersionUID = -710604862356186012L; + + @TableField + private Long roleId; + + @TableField + private Long permissionId; + +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/entity/SysUser.java b/diboot-shiro/src/main/java/com/diboot/shiro/entity/SysUser.java new file mode 100644 index 0000000..2ded218 --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/entity/SysUser.java @@ -0,0 +1,36 @@ +package com.diboot.shiro.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.diboot.core.entity.BaseEntity; +import lombok.Data; + +import java.util.List; + +/** + * @author Yangz + * @version v2.0 + * @date 2019/6/6 + */ +@Data +public class SysUser extends BaseEntity { + + private static final long serialVersionUID = 466801280426981780L; + + @TableField + private Long departmentId; + + @TableField + private String username; + + @TableField + private String password; + + @TableField + private String gender; + + @TableField(exist = false) + private List roleList; + + @TableField(exist = false) + private List permissionList; +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/entity/UserRole.java b/diboot-shiro/src/main/java/com/diboot/shiro/entity/UserRole.java new file mode 100644 index 0000000..3bd68f4 --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/entity/UserRole.java @@ -0,0 +1,26 @@ +package com.diboot.shiro.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.diboot.core.entity.BaseEntity; +import lombok.Data; + +/** + * @author Yangz + * @version v2.0 + * @date 2019/6/6 + */ +@Data +public class UserRole extends BaseEntity { + + private static final long serialVersionUID = 6415425761451054775L; + + @TableField + private String userType; + + @TableField + private Long userId; + + @TableField + private Long roleId; + +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/mapper/PermissionMapper.java b/diboot-shiro/src/main/java/com/diboot/shiro/mapper/PermissionMapper.java new file mode 100644 index 0000000..77c138f --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/mapper/PermissionMapper.java @@ -0,0 +1,15 @@ +package com.diboot.shiro.mapper; + +import com.diboot.core.mapper.BaseCrudMapper; +import com.diboot.shiro.entity.Permission; + +/** + * 员工Mapper + * @author Mazhicheng + * @version 2018/12/22 + * Copyright © www.dibo.ltd + */ +public interface PermissionMapper extends BaseCrudMapper { + +} + diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/mapper/PermissionMapper.xml b/diboot-shiro/src/main/java/com/diboot/shiro/mapper/PermissionMapper.xml new file mode 100644 index 0000000..3772708 --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/mapper/PermissionMapper.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/diboot-example/src/main/java/com/diboot/example/mapper/RoleMapper.java b/diboot-shiro/src/main/java/com/diboot/shiro/mapper/RoleMapper.java similarity index 73% rename from diboot-example/src/main/java/com/diboot/example/mapper/RoleMapper.java rename to diboot-shiro/src/main/java/com/diboot/shiro/mapper/RoleMapper.java index 4ed3983..3b7903e 100644 --- a/diboot-example/src/main/java/com/diboot/example/mapper/RoleMapper.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/mapper/RoleMapper.java @@ -1,7 +1,7 @@ -package com.diboot.example.mapper; +package com.diboot.shiro.mapper; import com.diboot.core.mapper.BaseCrudMapper; -import com.diboot.example.entity.Role; +import com.diboot.shiro.entity.Role; /** * 员工Mapper diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/mapper/RoleMapper.xml b/diboot-shiro/src/main/java/com/diboot/shiro/mapper/RoleMapper.xml new file mode 100644 index 0000000..470c030 --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/mapper/RoleMapper.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/mapper/RolePermissionMapper.java b/diboot-shiro/src/main/java/com/diboot/shiro/mapper/RolePermissionMapper.java new file mode 100644 index 0000000..236d97a --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/mapper/RolePermissionMapper.java @@ -0,0 +1,15 @@ +package com.diboot.shiro.mapper; + +import com.diboot.core.mapper.BaseCrudMapper; +import com.diboot.shiro.entity.RolePermission; + +/** + * 员工Mapper + * @author Mazhicheng + * @version 2018/12/22 + * Copyright © www.dibo.ltd + */ +public interface RolePermissionMapper extends BaseCrudMapper { + +} + diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/mapper/RolePermissionMapper.xml b/diboot-shiro/src/main/java/com/diboot/shiro/mapper/RolePermissionMapper.xml new file mode 100644 index 0000000..6a8b0a4 --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/mapper/RolePermissionMapper.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/mapper/SysUserMapper.java b/diboot-shiro/src/main/java/com/diboot/shiro/mapper/SysUserMapper.java new file mode 100644 index 0000000..1231e18 --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/mapper/SysUserMapper.java @@ -0,0 +1,15 @@ +package com.diboot.shiro.mapper; + +import com.diboot.core.mapper.BaseCrudMapper; +import com.diboot.shiro.entity.SysUser; + +/** + * 员工Mapper + * @author Mazhicheng + * @version 2018/12/22 + * Copyright © www.dibo.ltd + */ +public interface SysUserMapper extends BaseCrudMapper { + +} + diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/mapper/SysUserMapper.xml b/diboot-shiro/src/main/java/com/diboot/shiro/mapper/SysUserMapper.xml new file mode 100644 index 0000000..acfdd51 --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/mapper/SysUserMapper.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/mapper/UserRoleMapper.java b/diboot-shiro/src/main/java/com/diboot/shiro/mapper/UserRoleMapper.java new file mode 100644 index 0000000..9c5979b --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/mapper/UserRoleMapper.java @@ -0,0 +1,15 @@ +package com.diboot.shiro.mapper; + +import com.diboot.core.mapper.BaseCrudMapper; +import com.diboot.shiro.entity.UserRole; + +/** + * 员工Mapper + * @author Mazhicheng + * @version 2018/12/22 + * Copyright © www.dibo.ltd + */ +public interface UserRoleMapper extends BaseCrudMapper { + +} + diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/mapper/UserRoleMapper.xml b/diboot-shiro/src/main/java/com/diboot/shiro/mapper/UserRoleMapper.xml new file mode 100644 index 0000000..5266d10 --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/mapper/UserRoleMapper.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/service/AuthWayService.java b/diboot-shiro/src/main/java/com/diboot/shiro/service/AuthWayService.java new file mode 100644 index 0000000..b668499 --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/service/AuthWayService.java @@ -0,0 +1,50 @@ +package com.diboot.shiro.service; + +import com.diboot.core.entity.BaseEntity; +import com.diboot.shiro.BaseJwtAuthenticationToken; +import com.diboot.shiro.config.AuthType; + +public interface AuthWayService { + + /*** + * 获取认证类型 + * @return + */ + AuthType authType(); + + /*** + * 根据令牌初始化认证方式 + * @param token + */ + void initByToken(BaseJwtAuthenticationToken token); + + /*** + * 获取用户信息 + * @return + */ + BaseEntity getUser(); + + /*** + * 是否需要密码 + * @return + */ + boolean requirePassword(); + + /*** + * 密码是否匹配 + * @return + */ + boolean isPasswordMatch(); + + /*** + * 是否已初步验证(如果没有初步验证,则在验证过程中会再次验证) + * @return + */ + boolean isPreliminaryVerified(); + + /*** + * 获取该认证方式过期时间(分钟) + * @return + */ + Long getExpiresInMinutes(); +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/service/PermissionService.java b/diboot-shiro/src/main/java/com/diboot/shiro/service/PermissionService.java new file mode 100644 index 0000000..f274a99 --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/service/PermissionService.java @@ -0,0 +1,14 @@ +package com.diboot.shiro.service; + +import com.diboot.core.service.BaseService; +import com.diboot.shiro.entity.Permission; + +/** + * 员工相关Service + * @author Mazhicheng + * @version v2.0 + * @date 2019/1/5 + */ +public interface PermissionService extends BaseService { + +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/service/RolePermissionService.java b/diboot-shiro/src/main/java/com/diboot/shiro/service/RolePermissionService.java new file mode 100644 index 0000000..7cb0360 --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/service/RolePermissionService.java @@ -0,0 +1,14 @@ +package com.diboot.shiro.service; + +import com.diboot.core.service.BaseService; +import com.diboot.shiro.entity.RolePermission; + +/** + * 员工相关Service + * @author Mazhicheng + * @version v2.0 + * @date 2019/1/5 + */ +public interface RolePermissionService extends BaseService { + +} diff --git a/diboot-example/src/main/java/com/diboot/example/service/RoleService.java b/diboot-shiro/src/main/java/com/diboot/shiro/service/RoleService.java similarity index 72% rename from diboot-example/src/main/java/com/diboot/example/service/RoleService.java rename to diboot-shiro/src/main/java/com/diboot/shiro/service/RoleService.java index 95e7c0f..69902d6 100644 --- a/diboot-example/src/main/java/com/diboot/example/service/RoleService.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/service/RoleService.java @@ -1,7 +1,7 @@ -package com.diboot.example.service; +package com.diboot.shiro.service; import com.diboot.core.service.BaseService; -import com.diboot.example.entity.Role; +import com.diboot.shiro.entity.Role; /** * 员工相关Service diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/service/SysUserService.java b/diboot-shiro/src/main/java/com/diboot/shiro/service/SysUserService.java new file mode 100644 index 0000000..fd13c3d --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/service/SysUserService.java @@ -0,0 +1,14 @@ +package com.diboot.shiro.service; + +import com.diboot.core.service.BaseService; +import com.diboot.shiro.entity.SysUser; + +/** + * 员工相关Service + * @author Mazhicheng + * @version v2.0 + * @date 2019/1/5 + */ +public interface SysUserService extends BaseService { + +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/service/UserRoleService.java b/diboot-shiro/src/main/java/com/diboot/shiro/service/UserRoleService.java new file mode 100644 index 0000000..24fb8f5 --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/service/UserRoleService.java @@ -0,0 +1,14 @@ +package com.diboot.shiro.service; + +import com.diboot.core.service.BaseService; +import com.diboot.shiro.entity.UserRole; + +/** + * 员工相关Service + * @author Mazhicheng + * @version v2.0 + * @date 2019/1/5 + */ +public interface UserRoleService extends BaseService { + +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/PermissionServiceImpl.java b/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/PermissionServiceImpl.java new file mode 100644 index 0000000..d640890 --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/PermissionServiceImpl.java @@ -0,0 +1,21 @@ +package com.diboot.shiro.service.impl; + +import com.diboot.core.service.impl.BaseServiceImpl; +import com.diboot.shiro.entity.Permission; +import com.diboot.shiro.mapper.PermissionMapper; +import com.diboot.shiro.service.PermissionService; +import com.diboot.shiro.service.SysUserService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** + * 员工相关Service + * @author Mazhicheng + * @version 2018/12/23 + * Copyright © www.dibo.ltd + */ +@Service +@Slf4j +public class PermissionServiceImpl extends BaseServiceImpl implements PermissionService { + +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/RolePermissionServiceImpl.java b/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/RolePermissionServiceImpl.java new file mode 100644 index 0000000..4422787 --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/RolePermissionServiceImpl.java @@ -0,0 +1,20 @@ +package com.diboot.shiro.service.impl; + +import com.diboot.core.service.impl.BaseServiceImpl; +import com.diboot.shiro.entity.RolePermission; +import com.diboot.shiro.mapper.RolePermissionMapper; +import com.diboot.shiro.service.RolePermissionService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** + * 员工相关Service + * @author Mazhicheng + * @version 2018/12/23 + * Copyright © www.dibo.ltd + */ +@Service +@Slf4j +public class RolePermissionServiceImpl extends BaseServiceImpl implements RolePermissionService { + +} diff --git a/diboot-example/src/main/java/com/diboot/example/service/impl/RoleServiceImpl.java b/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/RoleServiceImpl.java similarity index 67% rename from diboot-example/src/main/java/com/diboot/example/service/impl/RoleServiceImpl.java rename to diboot-shiro/src/main/java/com/diboot/shiro/service/impl/RoleServiceImpl.java index 9f999ae..0b756fa 100644 --- a/diboot-example/src/main/java/com/diboot/example/service/impl/RoleServiceImpl.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/RoleServiceImpl.java @@ -1,9 +1,9 @@ -package com.diboot.example.service.impl; +package com.diboot.shiro.service.impl; import com.diboot.core.service.impl.BaseServiceImpl; -import com.diboot.example.entity.Role; -import com.diboot.example.mapper.RoleMapper; -import com.diboot.example.service.RoleService; +import com.diboot.shiro.entity.Role; +import com.diboot.shiro.mapper.RoleMapper; +import com.diboot.shiro.service.RoleService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/SysUserServiceImpl.java b/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/SysUserServiceImpl.java new file mode 100644 index 0000000..373feb4 --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/SysUserServiceImpl.java @@ -0,0 +1,20 @@ +package com.diboot.shiro.service.impl; + +import com.diboot.core.service.impl.BaseServiceImpl; +import com.diboot.shiro.entity.SysUser; +import com.diboot.shiro.mapper.SysUserMapper; +import com.diboot.shiro.service.SysUserService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** + * 员工相关Service + * @author Mazhicheng + * @version 2018/12/23 + * Copyright © www.dibo.ltd + */ +@Service +@Slf4j +public class SysUserServiceImpl extends BaseServiceImpl implements SysUserService { + +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/UserRoleServiceImpl.java b/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/UserRoleServiceImpl.java new file mode 100644 index 0000000..7c6370c --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/UserRoleServiceImpl.java @@ -0,0 +1,21 @@ +package com.diboot.shiro.service.impl; + +import com.diboot.core.service.impl.BaseServiceImpl; +import com.diboot.shiro.entity.UserRole; +import com.diboot.shiro.mapper.UserRoleMapper; +import com.diboot.shiro.service.PermissionService; +import com.diboot.shiro.service.UserRoleService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** + * 员工相关Service + * @author Mazhicheng + * @version 2018/12/23 + * Copyright © www.dibo.ltd + */ +@Service +@Slf4j +public class UserRoleServiceImpl extends BaseServiceImpl implements UserRoleService { + +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/UsernamePasswordAuthWayServiceImpl.java b/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/UsernamePasswordAuthWayServiceImpl.java new file mode 100644 index 0000000..196591f --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/UsernamePasswordAuthWayServiceImpl.java @@ -0,0 +1,79 @@ +package com.diboot.shiro.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.diboot.core.entity.BaseEntity; +import com.diboot.core.util.BeanUtils; +import com.diboot.core.util.V; +import com.diboot.shiro.BaseJwtAuthenticationToken; +import com.diboot.shiro.config.AuthType; +import com.diboot.shiro.entity.SysUser; +import com.diboot.shiro.service.AuthWayService; +import com.diboot.shiro.service.SysUserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class UsernamePasswordAuthWayServiceImpl implements AuthWayService { + + @Autowired + private SysUserService sysUserService; + + private AuthType authType = AuthType.USERNAME_PASSWORD; + + private BaseJwtAuthenticationToken token; + + @Override + public AuthType authType() { + return authType; + } + + @Override + public void initByToken(BaseJwtAuthenticationToken token) { + this.token = token; + } + + @Override + public BaseEntity getUser() { + QueryWrapper query = new QueryWrapper(); + query.lambda() + .eq(SysUser::getUsername, token.getAccount()); + List userList = sysUserService.getEntityList(query); + if (V.isEmpty(userList)){ + return null; + } + return userList.get(0); + } + + @Override + public boolean requirePassword() { + return authType.isRequirePassword(); + } + + @Override + public boolean isPasswordMatch() { + String password = new String(token.getPassword()); + + // 构建查询条件 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.lambda() + .eq(SysUser::getUsername, token.getAccount()) + .eq(SysUser::getPassword, password); + + // 获取单条用户记录 + List userList = sysUserService.getEntityList(queryWrapper); + + return V.notEmpty(userList); + } + + @Override + public boolean isPreliminaryVerified() { + return false; + } + + @Override + public Long getExpiresInMinutes() { + return null; + } +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/util/JwtHelper.java b/diboot-shiro/src/main/java/com/diboot/shiro/util/JwtHelper.java new file mode 100644 index 0000000..0c43057 --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/util/JwtHelper.java @@ -0,0 +1,194 @@ +package com.diboot.shiro.util; + +import com.diboot.core.config.BaseConfig; +import com.diboot.core.util.V; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.http.HttpServletRequest; +import java.util.Date; + +/** + * Token相关操作类 + * @author Mazc@dibo.ltd + * @version 2017/9/2 + * + */ +public class JwtHelper { + private static final Logger logger = LoggerFactory.getLogger(JwtHelper.class); + + private static final String ISSUER = V.notEmpty(BaseConfig.getProperty("diboot.shiro.jwt.issuer")) ? BaseConfig.getProperty("diboot.shiro.jwt.issuer") : "diboot.com"; + private static final String AUTH_HEADER = V.notEmpty(BaseConfig.getProperty("diboot.shiro.jwt.auth.header.key")) ? BaseConfig.getProperty("diboot.shiro.jwt.auth.header.key") : "authtoken"; + private static final String TOKEN_PREFIX = V.notEmpty(BaseConfig.getProperty("diboot.shiro.jwt.token.prefix")) ? BaseConfig.getProperty("diboot.shiro.jwt.token.prefix") : "Bearer "; + public static final String SIGN_KEY = V.notEmpty(BaseConfig.getProperty("diboot.shiro.jwt.signkey"))? BaseConfig.getProperty("diboot.shiro.jwt.signkey") : "Dibo2016Mazc"; + + // 默认过期时间 2小时 + public static final int EXPIRES_IN_MINUTES = V.notEmpty(BaseConfig.getProperty("diboot.shiro.jwt.token.expires.hours")) ? Integer.valueOf(BaseConfig.getProperty("diboot.shiro.jwt.token.expires.hours")) * 60 : 2 * 60; + private static final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256; + + /*** + * 从token中获取用户名 + * @param token + * @return + */ + public static String getAccountFromToken(String token){ + return getAccountFromToken(token, SIGN_KEY); + } + + /*** + * 从token中获取用户名 + * @param token + * @return + */ + public static String getAccountFromToken(String token, String key){ + String username; + try { + Claims claims = getClaimsFromToken(token, key); + // 校验过期时间 + if(claims.getExpiration().getTime() >= System.currentTimeMillis()){ + username = claims.getSubject(); + logger.debug("token有效,username=" + username); + } + else{ + logger.warn("token已过期:" + token); + username = null; + } + } + catch (Exception e) { + logger.warn("解析token异常,无效的token:" + token); + username = null; + } + return username; + } + + /*** + * 从请求头中获取客户端发来的token + * @param request + * @return + */ + public static String getRequestToken(HttpServletRequest request) { + String authHeader = request.getHeader(AUTH_HEADER); + if(authHeader != null){ + if(authHeader.startsWith(TOKEN_PREFIX)){ + return authHeader.substring(7); + } + return authHeader.trim(); + } + return null; + } + + /*** + * 请求头的token是否处于有效期限内 + */ + public static boolean isRequestTokenEffective(HttpServletRequest request){ + String authToken = getRequestToken(request); + if(V.notEmpty(authToken)){ + String account = getAccountFromToken(authToken); + return V.notEmpty(account); + } + return false; + } + + /*** + * 生成Token + * @param username + * @param signKey + * @return + */ + public static String generateToken(String username, String signKey) { + return generateToken(username, ISSUER, SIGNATURE_ALGORITHM, signKey, EXPIRES_IN_MINUTES); + } + + /*** + * 生成Token + * @param username + * @param signKey + * @param expiresInMinutes + * @return + */ + public static String generateToken(String username, String signKey, long expiresInMinutes) { + return generateToken(username, ISSUER, SIGNATURE_ALGORITHM, signKey, expiresInMinutes); + } + + /*** + * 生成token + * @param user + * @param issuer + * @param signAlgorithm + * @param signKey + * @param expiresInMinutes + * @return + */ + public static String generateToken(String user, String issuer, SignatureAlgorithm signAlgorithm, String signKey, long expiresInMinutes) { + Date currentTime = generateCurrentDate(); + Date expiration = generateExpirationDate(currentTime, expiresInMinutes); + String jwsToken = Jwts.builder() + .setIssuer(issuer) + .setSubject(user) + .setIssuedAt(currentTime) + .setExpiration(expiration) + .signWith(signAlgorithm, signKey) + .compact(); + return jwsToken; + } + + /*** + * 获取Token + * @param token + * @return + */ + public static Claims getClaimsFromToken(String token, String key) { + Claims claims; + try { + claims = Jwts.parser().setSigningKey(key) + .parseClaimsJws(token).getBody(); + } + catch (Exception e) { + claims = null; + } + return claims; + } + + /*** + * 校验Token + * @param authToken + * @param key + * @return + */ + public static boolean isValidToken(String authToken, String key, String expectedUsername) { + try { + final Claims claims = getClaimsFromToken(authToken, key); + if(claims != null){ + String authUsername = claims.getSubject(); + // 校验用户名 + if(authUsername != null && authUsername.equals(expectedUsername)){ + // 校验过期时间 + return claims.getExpiration().getTime() >= System.currentTimeMillis(); + } + } + } + catch (Exception e) { + logger.warn("校验token异常", e); + } + return false; + } + + /*** + * 生成当前时间戳 + * @return + */ + public static Date generateCurrentDate() { + return new Date(System.currentTimeMillis()); + } + + /*** + * 生成过期时间戳 + * @return + */ + public static Date generateExpirationDate(Date currentTime, long expiresInMinutes) { + return new Date(currentTime.getTime() + (expiresInMinutes*60000)); + } +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/vo/RoleVO.java b/diboot-shiro/src/main/java/com/diboot/shiro/vo/RoleVO.java new file mode 100644 index 0000000..3d6f717 --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/vo/RoleVO.java @@ -0,0 +1,24 @@ +package com.diboot.shiro.vo; + +import com.diboot.core.binding.annotation.BindEntityList; +import com.diboot.shiro.entity.Permission; +import com.diboot.shiro.entity.Role; +import lombok.Data; + +import java.util.List; + +/** + * @author Yangzhao + * @version v2.0 + * @date 2019/6/6 + */ +@Data +public class RoleVO extends Role { + + private static final long serialVersionUID = 860775286174387052L; + + // 支持通过中间表的多-多Entity实体关联 + @BindEntityList(entity = Permission.class, condition="this.id=role_permission.role_id AND role_permission.permission_id=id") + private List permissionList; + +} \ No newline at end of file diff --git a/diboot-shiro/src/test/java/com/diboot/shiro/BeansTest.java b/diboot-shiro/src/test/java/com/diboot/shiro/BeansTest.java new file mode 100644 index 0000000..e0fa297 --- /dev/null +++ b/diboot-shiro/src/test/java/com/diboot/shiro/BeansTest.java @@ -0,0 +1,7 @@ +package com.diboot.shiro; + +public class BeansTest { + public static void main(String[] args) { + + } +} diff --git a/settings.gradle b/settings.gradle index 2b58b61..c167669 100644 --- a/settings.gradle +++ b/settings.gradle @@ -10,4 +10,6 @@ rootProject.name = 'diboot-v2' include 'diboot-core' include 'diboot-example' +include 'diboot-shiro' +include 'diboot-docs' From 4f9f057d211ee24f67833f9a8ed99325c7ad01bd Mon Sep 17 00:00:00 2001 From: godchao Date: Thu, 6 Jun 2019 19:54:22 +0800 Subject: [PATCH 03/11] =?UTF-8?q?=E6=9B=B4=E6=94=B9=E6=B3=A8=E9=87=8A?= =?UTF-8?q?=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/diboot/example/test/BeansTest.java | 37 ------------------- .../shiro/BaseJwtAuthenticationFilter.java | 6 +-- .../shiro/BaseJwtAuthenticationToken.java | 6 +-- .../java/com/diboot/shiro/BaseJwtRealm.java | 7 +++- .../com/diboot/shiro/config/AuthType.java | 6 +++ .../com/diboot/shiro/config/ShiroConfig.java | 6 +++ .../com/diboot/shiro/entity/Permission.java | 2 +- .../java/com/diboot/shiro/entity/Role.java | 2 +- .../diboot/shiro/entity/RolePermission.java | 2 +- .../java/com/diboot/shiro/entity/SysUser.java | 2 +- .../com/diboot/shiro/entity/UserRole.java | 2 +- .../diboot/shiro/mapper/PermissionMapper.java | 8 ++-- .../com/diboot/shiro/mapper/RoleMapper.java | 8 ++-- .../shiro/mapper/RolePermissionMapper.java | 8 ++-- .../diboot/shiro/mapper/SysUserMapper.java | 8 ++-- .../diboot/shiro/mapper/UserRoleMapper.java | 8 ++-- .../diboot/shiro/service/AuthWayService.java | 6 +++ .../shiro/service/PermissionService.java | 6 +-- .../shiro/service/RolePermissionService.java | 6 +-- .../com/diboot/shiro/service/RoleService.java | 6 +-- .../diboot/shiro/service/SysUserService.java | 6 +-- .../diboot/shiro/service/UserRoleService.java | 6 +-- .../service/impl/PermissionServiceImpl.java | 2 +- .../impl/RolePermissionServiceImpl.java | 8 ++-- .../shiro/service/impl/RoleServiceImpl.java | 8 ++-- .../service/impl/SysUserServiceImpl.java | 8 ++-- .../service/impl/UserRoleServiceImpl.java | 8 ++-- .../UsernamePasswordAuthWayServiceImpl.java | 6 +++ .../java/com/diboot/shiro/util/JwtHelper.java | 6 +-- .../test/java/com/diboot/shiro/BeansTest.java | 7 ---- 30 files changed, 95 insertions(+), 112 deletions(-) delete mode 100644 diboot-example/src/test/java/com/diboot/example/test/BeansTest.java delete mode 100644 diboot-shiro/src/test/java/com/diboot/shiro/BeansTest.java diff --git a/diboot-example/src/test/java/com/diboot/example/test/BeansTest.java b/diboot-example/src/test/java/com/diboot/example/test/BeansTest.java deleted file mode 100644 index 5301bd7..0000000 --- a/diboot-example/src/test/java/com/diboot/example/test/BeansTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.diboot.example.test; - -import com.diboot.core.util.V; -import com.diboot.example.ApplicationTest; -import com.diboot.shiro.service.AuthWayService; -import org.junit.Assert; -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.stereotype.Component; - -import java.util.Map; - -@Component -public class BeansTest extends ApplicationTest { - - @Autowired - private ApplicationContext applicationContext; - - @Autowired - Map classMap; - - @Test - public void testGetImplMap() throws Exception{ - Map authWayServiceMap = applicationContext.getBeansOfType(AuthWayService.class); - Assert.assertTrue(V.notEmpty(authWayServiceMap)); - } - - @Test - public void testAuthwiredImplMap() throws Exception{ - Assert.assertTrue(V.notEmpty(classMap)); - for (Map.Entry entry : classMap.entrySet()){ - - } - } - -} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtAuthenticationFilter.java b/diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtAuthenticationFilter.java index 4cfc161..2b7974f 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtAuthenticationFilter.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtAuthenticationFilter.java @@ -21,9 +21,9 @@ import java.io.PrintWriter; /** * JWT 认证过滤器 - * @author Mazc@dibo.ltd - * @version 2018/1/6 - * + * @author Yangzhao + * @version v2.0 + * @date 2019/6/6 */ public class BaseJwtAuthenticationFilter extends BasicHttpAuthenticationFilter { private static final Logger logger = LoggerFactory.getLogger(BaseJwtAuthenticationFilter.class); diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtAuthenticationToken.java b/diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtAuthenticationToken.java index d76ae3a..f6dad2f 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtAuthenticationToken.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtAuthenticationToken.java @@ -12,9 +12,9 @@ import org.springframework.stereotype.Component; import java.util.Map; /** - * @author Yangz@dibo.ltd - * @version 2018/1/6 - * + * @author Yangzhao + * @version v2.0 + * @date 2019/6/6 */ @Component public class BaseJwtAuthenticationToken implements AuthenticationToken { diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtRealm.java b/diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtRealm.java index 99c066a..757e56a 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtRealm.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtRealm.java @@ -1,9 +1,7 @@ package com.diboot.shiro; -import com.baomidou.mybatisplus.core.conditions.query.Query; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.diboot.core.entity.BaseEntity; -import com.diboot.core.util.BeanUtils; import com.diboot.core.util.V; import com.diboot.shiro.entity.Permission; import com.diboot.shiro.entity.Role; @@ -23,6 +21,11 @@ import org.springframework.beans.factory.annotation.Autowired; import java.util.*; import java.util.stream.Collectors; +/** + * @author Yangzhao + * @version v2.0 + * @date 2019/6/6 + */ public class BaseJwtRealm extends AuthorizingRealm { @Autowired diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/config/AuthType.java b/diboot-shiro/src/main/java/com/diboot/shiro/config/AuthType.java index 89070f6..113e2ad 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/config/AuthType.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/config/AuthType.java @@ -2,6 +2,12 @@ package com.diboot.shiro.config; import lombok.Data; +/*** + * 认证方式 + * @author Yangzhao + * @version v2.0 + * @date 2019/6/6 + */ public enum AuthType { USERNAME_PASSWORD(1, true, "账号密码"), diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/config/ShiroConfig.java b/diboot-shiro/src/main/java/com/diboot/shiro/config/ShiroConfig.java index 180083b..29c0759 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/config/ShiroConfig.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/config/ShiroConfig.java @@ -20,6 +20,12 @@ import javax.servlet.Filter; import java.util.LinkedHashMap; import java.util.Map; +/*** + * + * @author Yangzhao + * @version v2.0 + * @date 2019/6/6 + */ @Configuration public class ShiroConfig { private static final Logger logger = LoggerFactory.getLogger(ShiroConfig.class); diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/entity/Permission.java b/diboot-shiro/src/main/java/com/diboot/shiro/entity/Permission.java index e2bf5ff..f652a7a 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/entity/Permission.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/entity/Permission.java @@ -5,7 +5,7 @@ import com.diboot.core.entity.BaseEntity; import lombok.Data; /** - * @author Yangz + * @author Yangzhao * @version v2.0 * @date 2019/6/6 */ diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/entity/Role.java b/diboot-shiro/src/main/java/com/diboot/shiro/entity/Role.java index b2acd42..6574e37 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/entity/Role.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/entity/Role.java @@ -7,7 +7,7 @@ import lombok.Data; import java.util.List; /** - * @author Yangz + * @author Yangzhao * @version v2.0 * @date 2019/6/6 */ diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/entity/RolePermission.java b/diboot-shiro/src/main/java/com/diboot/shiro/entity/RolePermission.java index f2246d6..567fb6a 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/entity/RolePermission.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/entity/RolePermission.java @@ -5,7 +5,7 @@ import com.diboot.core.entity.BaseEntity; import lombok.Data; /** - * @author Yangz + * @author Yangzhao * @version v2.0 * @date 2019/6/6 */ diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/entity/SysUser.java b/diboot-shiro/src/main/java/com/diboot/shiro/entity/SysUser.java index 2ded218..94f77a1 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/entity/SysUser.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/entity/SysUser.java @@ -7,7 +7,7 @@ import lombok.Data; import java.util.List; /** - * @author Yangz + * @author Yangzhao * @version v2.0 * @date 2019/6/6 */ diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/entity/UserRole.java b/diboot-shiro/src/main/java/com/diboot/shiro/entity/UserRole.java index 3bd68f4..4c4ff2a 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/entity/UserRole.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/entity/UserRole.java @@ -5,7 +5,7 @@ import com.diboot.core.entity.BaseEntity; import lombok.Data; /** - * @author Yangz + * @author Yangzhao * @version v2.0 * @date 2019/6/6 */ diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/mapper/PermissionMapper.java b/diboot-shiro/src/main/java/com/diboot/shiro/mapper/PermissionMapper.java index 77c138f..01eb116 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/mapper/PermissionMapper.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/mapper/PermissionMapper.java @@ -4,10 +4,10 @@ import com.diboot.core.mapper.BaseCrudMapper; import com.diboot.shiro.entity.Permission; /** - * 员工Mapper - * @author Mazhicheng - * @version 2018/12/22 - * Copyright © www.dibo.ltd + * 授权Mapper + * @author Yangzhao + * @version v2.0 + * @date 2019/6/6 */ public interface PermissionMapper extends BaseCrudMapper { diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/mapper/RoleMapper.java b/diboot-shiro/src/main/java/com/diboot/shiro/mapper/RoleMapper.java index 3b7903e..8cac9dd 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/mapper/RoleMapper.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/mapper/RoleMapper.java @@ -4,10 +4,10 @@ import com.diboot.core.mapper.BaseCrudMapper; import com.diboot.shiro.entity.Role; /** - * 员工Mapper - * @author Mazhicheng - * @version 2018/12/22 - * Copyright © www.dibo.ltd + * 角色Mapper + * @author Yangzhao + * @version v2.0 + * @date 2019/6/6 */ public interface RoleMapper extends BaseCrudMapper { diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/mapper/RolePermissionMapper.java b/diboot-shiro/src/main/java/com/diboot/shiro/mapper/RolePermissionMapper.java index 236d97a..becd4c7 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/mapper/RolePermissionMapper.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/mapper/RolePermissionMapper.java @@ -4,10 +4,10 @@ import com.diboot.core.mapper.BaseCrudMapper; import com.diboot.shiro.entity.RolePermission; /** - * 员工Mapper - * @author Mazhicheng - * @version 2018/12/22 - * Copyright © www.dibo.ltd + * 角色授权Mapper + * @author Yangzhao + * @version v2.0 + * @date 2019/6/6 */ public interface RolePermissionMapper extends BaseCrudMapper { diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/mapper/SysUserMapper.java b/diboot-shiro/src/main/java/com/diboot/shiro/mapper/SysUserMapper.java index 1231e18..78718e0 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/mapper/SysUserMapper.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/mapper/SysUserMapper.java @@ -4,10 +4,10 @@ import com.diboot.core.mapper.BaseCrudMapper; import com.diboot.shiro.entity.SysUser; /** - * 员工Mapper - * @author Mazhicheng - * @version 2018/12/22 - * Copyright © www.dibo.ltd + * 用户Mapper + * @author Yangzhao + * @version v2.0 + * @date 2019/6/6 */ public interface SysUserMapper extends BaseCrudMapper { diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/mapper/UserRoleMapper.java b/diboot-shiro/src/main/java/com/diboot/shiro/mapper/UserRoleMapper.java index 9c5979b..6d984cc 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/mapper/UserRoleMapper.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/mapper/UserRoleMapper.java @@ -4,10 +4,10 @@ import com.diboot.core.mapper.BaseCrudMapper; import com.diboot.shiro.entity.UserRole; /** - * 员工Mapper - * @author Mazhicheng - * @version 2018/12/22 - * Copyright © www.dibo.ltd + * 用户角色Mapper + * @author Yangzhao + * @version v2.0 + * @date 2019/6/6 */ public interface UserRoleMapper extends BaseCrudMapper { diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/service/AuthWayService.java b/diboot-shiro/src/main/java/com/diboot/shiro/service/AuthWayService.java index b668499..cc71ebf 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/service/AuthWayService.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/service/AuthWayService.java @@ -4,6 +4,12 @@ import com.diboot.core.entity.BaseEntity; import com.diboot.shiro.BaseJwtAuthenticationToken; import com.diboot.shiro.config.AuthType; +/*** + * 认证方式接口 + * @author Yangzhao + * @version v2.0 + * @date 2019/6/6 + */ public interface AuthWayService { /*** diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/service/PermissionService.java b/diboot-shiro/src/main/java/com/diboot/shiro/service/PermissionService.java index f274a99..ce0492a 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/service/PermissionService.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/service/PermissionService.java @@ -4,10 +4,10 @@ import com.diboot.core.service.BaseService; import com.diboot.shiro.entity.Permission; /** - * 员工相关Service - * @author Mazhicheng + * 许可授权相关Service + * @author Yangzhao * @version v2.0 - * @date 2019/1/5 + * @date 2019/6/6 */ public interface PermissionService extends BaseService { diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/service/RolePermissionService.java b/diboot-shiro/src/main/java/com/diboot/shiro/service/RolePermissionService.java index 7cb0360..af14e8e 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/service/RolePermissionService.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/service/RolePermissionService.java @@ -4,10 +4,10 @@ import com.diboot.core.service.BaseService; import com.diboot.shiro.entity.RolePermission; /** - * 员工相关Service - * @author Mazhicheng + * 角色授权相关Service + * @author Yangzhao * @version v2.0 - * @date 2019/1/5 + * @date 2019/6/6 */ public interface RolePermissionService extends BaseService { diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/service/RoleService.java b/diboot-shiro/src/main/java/com/diboot/shiro/service/RoleService.java index 69902d6..43ad2a9 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/service/RoleService.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/service/RoleService.java @@ -4,10 +4,10 @@ import com.diboot.core.service.BaseService; import com.diboot.shiro.entity.Role; /** - * 员工相关Service - * @author Mazhicheng + * 角色相关Service + * @author Yangzhao * @version v2.0 - * @date 2019/1/5 + * @date 2019/6/6 */ public interface RoleService extends BaseService { diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/service/SysUserService.java b/diboot-shiro/src/main/java/com/diboot/shiro/service/SysUserService.java index fd13c3d..b71346c 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/service/SysUserService.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/service/SysUserService.java @@ -4,10 +4,10 @@ import com.diboot.core.service.BaseService; import com.diboot.shiro.entity.SysUser; /** - * 员工相关Service - * @author Mazhicheng + * 用户相关Service + * @author Yangzhao * @version v2.0 - * @date 2019/1/5 + * @date 2019/6/6 */ public interface SysUserService extends BaseService { diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/service/UserRoleService.java b/diboot-shiro/src/main/java/com/diboot/shiro/service/UserRoleService.java index 24fb8f5..8945eca 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/service/UserRoleService.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/service/UserRoleService.java @@ -4,10 +4,10 @@ import com.diboot.core.service.BaseService; import com.diboot.shiro.entity.UserRole; /** - * 员工相关Service - * @author Mazhicheng + * 用户角色Service + * @author Yangzhao * @version v2.0 - * @date 2019/1/5 + * @date 2019/6/6 */ public interface UserRoleService extends BaseService { diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/PermissionServiceImpl.java b/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/PermissionServiceImpl.java index d640890..19cb82d 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/PermissionServiceImpl.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/PermissionServiceImpl.java @@ -9,7 +9,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; /** - * 员工相关Service + * 许可授权相关Service * @author Mazhicheng * @version 2018/12/23 * Copyright © www.dibo.ltd diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/RolePermissionServiceImpl.java b/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/RolePermissionServiceImpl.java index 4422787..3560a8c 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/RolePermissionServiceImpl.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/RolePermissionServiceImpl.java @@ -8,10 +8,10 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; /** - * 员工相关Service - * @author Mazhicheng - * @version 2018/12/23 - * Copyright © www.dibo.ltd + * 角色授权相关Service + * @author Yangzhao + * @version v2.0 + * @date 2019/6/6 */ @Service @Slf4j diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/RoleServiceImpl.java b/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/RoleServiceImpl.java index 0b756fa..6844a4a 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/RoleServiceImpl.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/RoleServiceImpl.java @@ -8,10 +8,10 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; /** - * 员工相关Service - * @author Mazhicheng - * @version 2018/12/23 - * Copyright © www.dibo.ltd + * 角色相关Service + * @author Yangzhao + * @version v2.0 + * @date 2019/6/6 */ @Service @Slf4j diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/SysUserServiceImpl.java b/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/SysUserServiceImpl.java index 373feb4..731290b 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/SysUserServiceImpl.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/SysUserServiceImpl.java @@ -8,10 +8,10 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; /** - * 员工相关Service - * @author Mazhicheng - * @version 2018/12/23 - * Copyright © www.dibo.ltd + * 用户相关Service + * @author Yangzhao + * @version v2.0 + * @date 2019/6/6 */ @Service @Slf4j diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/UserRoleServiceImpl.java b/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/UserRoleServiceImpl.java index 7c6370c..440cfcb 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/UserRoleServiceImpl.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/UserRoleServiceImpl.java @@ -9,10 +9,10 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; /** - * 员工相关Service - * @author Mazhicheng - * @version 2018/12/23 - * Copyright © www.dibo.ltd + * 用户角色相关Service + * @author Yangzhao + * @version v2.0 + * @date 2019/6/6 */ @Service @Slf4j diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/UsernamePasswordAuthWayServiceImpl.java b/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/UsernamePasswordAuthWayServiceImpl.java index 196591f..72f18dc 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/UsernamePasswordAuthWayServiceImpl.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/UsernamePasswordAuthWayServiceImpl.java @@ -14,6 +14,12 @@ import org.springframework.stereotype.Service; import java.util.List; +/*** + * 用户名密码认证实现 + * @author Yangzhao + * @version v2.0 + * @date 2019/6/6 + */ @Service public class UsernamePasswordAuthWayServiceImpl implements AuthWayService { diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/util/JwtHelper.java b/diboot-shiro/src/main/java/com/diboot/shiro/util/JwtHelper.java index 0c43057..f7608fc 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/util/JwtHelper.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/util/JwtHelper.java @@ -13,9 +13,9 @@ import java.util.Date; /** * Token相关操作类 - * @author Mazc@dibo.ltd - * @version 2017/9/2 - * + * @author Yangzhao + * @version v2.0 + * @date 2019/6/6 */ public class JwtHelper { private static final Logger logger = LoggerFactory.getLogger(JwtHelper.class); diff --git a/diboot-shiro/src/test/java/com/diboot/shiro/BeansTest.java b/diboot-shiro/src/test/java/com/diboot/shiro/BeansTest.java deleted file mode 100644 index e0fa297..0000000 --- a/diboot-shiro/src/test/java/com/diboot/shiro/BeansTest.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.diboot.shiro; - -public class BeansTest { - public static void main(String[] args) { - - } -} From 8718506433d2447b530780f0c1345d7368d05135 Mon Sep 17 00:00:00 2001 From: godchao Date: Thu, 13 Jun 2019 10:09:16 +0800 Subject: [PATCH 04/11] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AuthTokenController.java | 11 +- .../example/controller/SysUserController.java | 69 ++++++++++ .../com/diboot/shiro/config/ShiroConfig.java | 8 +- .../controller/PermissionController.java | 121 ++++++++++++++++++ .../java/com/diboot/shiro/entity/SysUser.java | 4 + .../BaseJwtAuthenticationFilter.java | 5 +- .../{ => jwt}/BaseJwtAuthenticationToken.java | 2 +- .../diboot/shiro/{ => jwt}/BaseJwtRealm.java | 27 +--- .../diboot/shiro/service/AuthWayService.java | 2 +- .../com/diboot/shiro/service/RoleService.java | 12 ++ .../shiro/service/impl/RoleServiceImpl.java | 42 ++++++ .../UsernamePasswordAuthWayServiceImpl.java | 3 +- .../com/diboot/shiro/util/AuthHelper.java | 44 +++++++ .../com/diboot/shiro/vo/PermissionVO.java | 20 +++ .../main/java/com/diboot/shiro/vo/RoleVO.java | 1 + 15 files changed, 328 insertions(+), 43 deletions(-) create mode 100644 diboot-example/src/main/java/com/diboot/example/controller/SysUserController.java create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/controller/PermissionController.java rename diboot-shiro/src/main/java/com/diboot/shiro/{ => jwt}/BaseJwtAuthenticationFilter.java (97%) rename diboot-shiro/src/main/java/com/diboot/shiro/{ => jwt}/BaseJwtAuthenticationToken.java (99%) rename diboot-shiro/src/main/java/com/diboot/shiro/{ => jwt}/BaseJwtRealm.java (78%) create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/util/AuthHelper.java create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/vo/PermissionVO.java diff --git a/diboot-example/src/main/java/com/diboot/example/controller/AuthTokenController.java b/diboot-example/src/main/java/com/diboot/example/controller/AuthTokenController.java index cd85102..d4c05f2 100644 --- a/diboot-example/src/main/java/com/diboot/example/controller/AuthTokenController.java +++ b/diboot-example/src/main/java/com/diboot/example/controller/AuthTokenController.java @@ -2,7 +2,7 @@ package com.diboot.example.controller; import com.diboot.core.vo.JsonResult; import com.diboot.core.vo.Status; -import com.diboot.shiro.BaseJwtAuthenticationToken; +import com.diboot.shiro.jwt.BaseJwtAuthenticationToken; import com.diboot.shiro.config.AuthType; import com.diboot.shiro.entity.SysUser; import com.diboot.shiro.service.AuthWayService; @@ -11,17 +11,14 @@ import org.apache.shiro.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Map; @RestController -@RequestMapping("/token") +@RequestMapping("/auth") public class AuthTokenController { private static final Logger logger = LoggerFactory.getLogger(AuthTokenController.class); @@ -38,7 +35,7 @@ public class AuthTokenController { * @throws Exception */ @PostMapping("/login") - public JsonResult login(@ModelAttribute SysUser sysUser, HttpServletRequest request, HttpServletResponse response) throws Exception{ + public JsonResult login(@RequestBody SysUser sysUser, HttpServletRequest request, HttpServletResponse response) throws Exception{ String errorMsg = "登录失败"; try{ BaseJwtAuthenticationToken authToken = new BaseJwtAuthenticationToken(authWayServiceMap, sysUser.getUsername(), sysUser.getPassword(), AuthType.USERNAME_PASSWORD); diff --git a/diboot-example/src/main/java/com/diboot/example/controller/SysUserController.java b/diboot-example/src/main/java/com/diboot/example/controller/SysUserController.java new file mode 100644 index 0000000..e5dae18 --- /dev/null +++ b/diboot-example/src/main/java/com/diboot/example/controller/SysUserController.java @@ -0,0 +1,69 @@ +package com.diboot.example.controller; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.diboot.core.util.V; +import com.diboot.core.vo.JsonResult; +import com.diboot.core.vo.Status; +import com.diboot.shiro.entity.SysUser; +import com.diboot.shiro.service.RoleService; +import com.diboot.shiro.service.SysUserService; +import com.diboot.shiro.util.JwtHelper; +import com.diboot.shiro.vo.RoleVO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +@RestController +@RequestMapping("/sysUser") +public class SysUserController { + + private static final Logger logger = LoggerFactory.getLogger(SysUserController.class); + + @Autowired + private RoleService roleService; + + @Autowired + private SysUserService sysUserService; + + /*** + * 获取登录用户信息 + * @param request + * @return + * @throws Exception + */ + @GetMapping("/info") + public JsonResult info(HttpServletRequest request) throws Exception{ + String token = JwtHelper.getRequestToken(request); + if (V.isEmpty(token)){ + return new JsonResult(Status.FAIL_OPERATION, new String[]{"获取数据失败"}); + } + + String username = JwtHelper.getAccountFromToken(token); + if (V.isEmpty(username)){ + return new JsonResult(Status.FAIL_OPERATION, new String[]{"获取数据失败"}); + } + + QueryWrapper query = new QueryWrapper<>(); + query.lambda() + .eq(SysUser::getUsername, username); + List userList = sysUserService.getEntityList(query); + if (V.isEmpty(userList)){ + return new JsonResult(Status.FAIL_OPERATION, new String[]{"获取数据失败"}); + } + + SysUser user = userList.get(0); + + List roleVOList = roleService.getRelatedRoleAndPermissionListByUser(SysUser.class.getSimpleName(), user.getId()); + if (V.isEmpty(roleVOList)){ + return new JsonResult(Status.FAIL_OPERATION, new String[]{"获取用户角色失败"}); + } + + user.setRoleVOList(roleVOList); + + return new JsonResult(Status.OK, user, new String[]{"获取角色列表成功"}); + } +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/config/ShiroConfig.java b/diboot-shiro/src/main/java/com/diboot/shiro/config/ShiroConfig.java index 29c0759..681eed0 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/config/ShiroConfig.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/config/ShiroConfig.java @@ -1,7 +1,7 @@ package com.diboot.shiro.config; -import com.diboot.shiro.BaseJwtAuthenticationFilter; -import com.diboot.shiro.BaseJwtRealm; +import com.diboot.shiro.jwt.BaseJwtAuthenticationFilter; +import com.diboot.shiro.jwt.BaseJwtRealm; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.realm.Realm; import org.apache.shiro.spring.LifecycleBeanPostProcessor; @@ -57,9 +57,9 @@ public class ShiroConfig { filterChainDefinitionMap.put("/", "anon"); filterChainDefinitionMap.put("/static/**", "anon"); - filterChainDefinitionMap.put("/token/login", "anon"); + filterChainDefinitionMap.put("/auth/login", "anon"); filterChainDefinitionMap.put("/error", "anon"); - filterChainDefinitionMap.put("/token/logout", "logout"); + filterChainDefinitionMap.put("/auth/logout", "logout"); filterChainDefinitionMap.put("/**", "jwt"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/controller/PermissionController.java b/diboot-shiro/src/main/java/com/diboot/shiro/controller/PermissionController.java new file mode 100644 index 0000000..b4094ad --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/controller/PermissionController.java @@ -0,0 +1,121 @@ +package com.diboot.shiro.controller; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.diboot.core.controller.BaseCrudRestController; +import com.diboot.core.service.BaseService; +import com.diboot.core.util.BeanUtils; +import com.diboot.core.util.V; +import com.diboot.core.vo.JsonResult; +import com.diboot.core.vo.Pagination; +import com.diboot.core.vo.Status; +import com.diboot.shiro.entity.Permission; +import com.diboot.shiro.service.PermissionService; +import com.diboot.shiro.vo.PermissionVO; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.ui.ModelMap; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +/** + * Organization相关Controller + * @author Mazhicheng + * @version 2018/12/23 + * Copyright © www.dibo.ltd + */ +@RestController +@RequestMapping("/permission") +public class PermissionController extends BaseCrudRestController { + + private static final Logger logger = LoggerFactory.getLogger(PermissionService.class); + + @Autowired + private PermissionService permissionService; + + /*** + * 查询ViewObject的分页数据 (此为非继承的自定义使用案例,更简化的调用父类案例请参考UserController) + *

+ * url参数示例: /list?_pageSize=20&_pageIndex=1&_orderBy=id&code=TST + *

+ * @return + * @throws Exception + */ + @RequiresPermissions("permission:list") + @GetMapping("/list") + public JsonResult getVOList(HttpServletRequest request) throws Exception{ + QueryWrapper queryWrapper = buildQuery(request); + // 构建分页 + Pagination pagination = buildPagination(request); + // 查询当前页的Entity主表数据 + List entityList = getService().getEntityList(queryWrapper, pagination); + // 自动转换VO中注解绑定的关联 + List voList = super.convertToVoAndBindRelations(entityList, PermissionVO.class); + + return new JsonResult(Status.OK, voList).bindPagination(pagination); + } + + /*** + * 创建Entity + * @return + * @throws Exception + */ + @RequiresPermissions("permission:add") + @PostMapping("/") + public JsonResult createEntity(@ModelAttribute PermissionVO viewObject, BindingResult result, HttpServletRequest request, ModelMap modelMap) + throws Exception{ + // 转换 + Permission entity = BeanUtils.convert(viewObject, Permission.class); + // 创建 + return super.createEntity(entity, result, modelMap); + } + + /*** + * 查询Entity + * @param id ID + * @return + * @throws Exception + */ + @RequiresPermissions("permission:get") + @GetMapping("/{id}") + public JsonResult getModel(@PathVariable("id")Long id, HttpServletRequest request, ModelMap modelMap) + throws Exception{ + PermissionVO vo = permissionService.getViewObject(id, PermissionVO.class); + return new JsonResult(vo); + } + + /*** + * 更新Entity + * @param id ID + * @return + * @throws Exception + */ + @RequiresPermissions("permission:update") + @PutMapping("/{id}") + public JsonResult updateModel(@PathVariable("id")Long id, @ModelAttribute Permission entity, BindingResult result, + HttpServletRequest request, ModelMap modelMap) throws Exception{ + return super.updateEntity(entity, result, modelMap); + } + + /*** + * 删除用户 + * @param id 用户ID + * @return + * @throws Exception + */ + @RequiresPermissions("permission:delete") + @DeleteMapping("/{id}") + public JsonResult deleteModel(@PathVariable("id")Long id, HttpServletRequest request) throws Exception{ + return super.deleteEntity(id); + } + + @Override + protected BaseService getService() { + return permissionService; + } + +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/entity/SysUser.java b/diboot-shiro/src/main/java/com/diboot/shiro/entity/SysUser.java index 94f77a1..4ae9ea2 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/entity/SysUser.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/entity/SysUser.java @@ -2,6 +2,7 @@ package com.diboot.shiro.entity; import com.baomidou.mybatisplus.annotation.TableField; import com.diboot.core.entity.BaseEntity; +import com.diboot.shiro.vo.RoleVO; import lombok.Data; import java.util.List; @@ -31,6 +32,9 @@ public class SysUser extends BaseEntity { @TableField(exist = false) private List roleList; + @TableField(exist = false) + private List roleVOList; + @TableField(exist = false) private List permissionList; } diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtAuthenticationFilter.java b/diboot-shiro/src/main/java/com/diboot/shiro/jwt/BaseJwtAuthenticationFilter.java similarity index 97% rename from diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtAuthenticationFilter.java rename to diboot-shiro/src/main/java/com/diboot/shiro/jwt/BaseJwtAuthenticationFilter.java index 2b7974f..2e53855 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtAuthenticationFilter.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/jwt/BaseJwtAuthenticationFilter.java @@ -1,11 +1,10 @@ -package com.diboot.shiro; +package com.diboot.shiro.jwt; import com.diboot.core.util.JSON; import com.diboot.core.util.V; import com.diboot.core.vo.JsonResult; import com.diboot.core.vo.Status; import com.diboot.shiro.util.JwtHelper; -import org.apache.shiro.SecurityUtils; import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,7 +39,7 @@ public class BaseJwtAuthenticationFilter extends BasicHttpAuthenticationFilter { logger.warn("Token为空!url="+httpRequest.getRequestURL()); return false; } - //获取userId + //获取username String account = JwtHelper.getAccountFromToken(accessToken); if(V.notEmpty(account)){ logger.debug("Token认证成功!account="+account); diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtAuthenticationToken.java b/diboot-shiro/src/main/java/com/diboot/shiro/jwt/BaseJwtAuthenticationToken.java similarity index 99% rename from diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtAuthenticationToken.java rename to diboot-shiro/src/main/java/com/diboot/shiro/jwt/BaseJwtAuthenticationToken.java index f6dad2f..8de06ed 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtAuthenticationToken.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/jwt/BaseJwtAuthenticationToken.java @@ -1,4 +1,4 @@ -package com.diboot.shiro; +package com.diboot.shiro.jwt; import com.diboot.core.util.V; import com.diboot.shiro.config.AuthType; diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtRealm.java b/diboot-shiro/src/main/java/com/diboot/shiro/jwt/BaseJwtRealm.java similarity index 78% rename from diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtRealm.java rename to diboot-shiro/src/main/java/com/diboot/shiro/jwt/BaseJwtRealm.java index 757e56a..59450f1 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/BaseJwtRealm.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/jwt/BaseJwtRealm.java @@ -1,11 +1,8 @@ -package com.diboot.shiro; +package com.diboot.shiro.jwt; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.diboot.core.entity.BaseEntity; import com.diboot.core.util.V; import com.diboot.shiro.entity.Permission; -import com.diboot.shiro.entity.Role; -import com.diboot.shiro.entity.UserRole; import com.diboot.shiro.service.*; import com.diboot.shiro.vo.RoleVO; import org.apache.shiro.authc.AuthenticationException; @@ -91,27 +88,7 @@ public class BaseJwtRealm extends AuthorizingRealm { BaseEntity user = (BaseEntity) principals.getPrimaryPrincipal(); // 根据用户类型与用户id获取roleList - QueryWrapper query = new QueryWrapper<>(); - query.lambda() - .eq(UserRole::getUserType, userType) - .eq(UserRole::getUserId, user.getId()); - List userRoleList = userRoleService.getEntityList(query); - if (V.isEmpty(userRoleList)){ - return authorizationInfo; - } - List roleIdList = userRoleList.stream() - .map(UserRole::getRoleId) - .collect(Collectors.toList()); - if (V.isEmpty(roleIdList)){ - return authorizationInfo; - } - - // 获取角色列表,并使用VO自动多对多关联permission - QueryWrapper roleQuery = new QueryWrapper<>(); - roleQuery - .lambda() - .in(Role::getId, roleIdList); - List roleVOList = roleService.getViewObjectList(roleQuery, null, RoleVO.class); + List roleVOList = roleService.getRelatedRoleAndPermissionListByUser(userType, user.getId()); if (V.isEmpty(roleVOList)){ return authorizationInfo; diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/service/AuthWayService.java b/diboot-shiro/src/main/java/com/diboot/shiro/service/AuthWayService.java index cc71ebf..2dae3eb 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/service/AuthWayService.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/service/AuthWayService.java @@ -1,7 +1,7 @@ package com.diboot.shiro.service; import com.diboot.core.entity.BaseEntity; -import com.diboot.shiro.BaseJwtAuthenticationToken; +import com.diboot.shiro.jwt.BaseJwtAuthenticationToken; import com.diboot.shiro.config.AuthType; /*** diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/service/RoleService.java b/diboot-shiro/src/main/java/com/diboot/shiro/service/RoleService.java index 43ad2a9..5a6569e 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/service/RoleService.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/service/RoleService.java @@ -2,6 +2,10 @@ package com.diboot.shiro.service; import com.diboot.core.service.BaseService; import com.diboot.shiro.entity.Role; +import com.diboot.shiro.vo.RoleVO; + +import java.util.List; +import java.util.Set; /** * 角色相关Service @@ -11,4 +15,12 @@ import com.diboot.shiro.entity.Role; */ public interface RoleService extends BaseService { + /*** + * 根据用户类型和用户id获取角色关联权限列表 + * @param userType + * @param userId + * @return + */ + List getRelatedRoleAndPermissionListByUser(String userType, Long userId); + } diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/RoleServiceImpl.java b/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/RoleServiceImpl.java index 6844a4a..74d64f2 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/RoleServiceImpl.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/RoleServiceImpl.java @@ -1,12 +1,25 @@ package com.diboot.shiro.service.impl; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.diboot.core.service.impl.BaseServiceImpl; +import com.diboot.core.util.V; +import com.diboot.shiro.entity.Permission; import com.diboot.shiro.entity.Role; +import com.diboot.shiro.entity.UserRole; import com.diboot.shiro.mapper.RoleMapper; import com.diboot.shiro.service.RoleService; +import com.diboot.shiro.service.UserRoleService; +import com.diboot.shiro.vo.RoleVO; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + /** * 角色相关Service * @author Yangzhao @@ -17,4 +30,33 @@ import org.springframework.stereotype.Service; @Slf4j public class RoleServiceImpl extends BaseServiceImpl implements RoleService { + @Autowired + private UserRoleService userRoleService; + + @Override + public List getRelatedRoleAndPermissionListByUser(String userType, Long userId) { + // 根据用户类型与用户id获取roleList + QueryWrapper query = new QueryWrapper<>(); + query.lambda() + .eq(UserRole::getUserType, userType) + .eq(UserRole::getUserId, userId); + List userRoleList = userRoleService.getEntityList(query); + if (V.isEmpty(userRoleList)){ + return Collections.emptyList(); + } + List roleIdList = userRoleList.stream() + .map(UserRole::getRoleId) + .collect(Collectors.toList()); + if (V.isEmpty(roleIdList)){ + return Collections.emptyList(); + } + + // 获取角色列表,并使用VO自动多对多关联permission + QueryWrapper roleQuery = new QueryWrapper<>(); + roleQuery + .lambda() + .in(Role::getId, roleIdList); + List roleVOList = this.getViewObjectList(roleQuery, null, RoleVO.class); + return roleVOList; + } } diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/UsernamePasswordAuthWayServiceImpl.java b/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/UsernamePasswordAuthWayServiceImpl.java index 72f18dc..bce9496 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/UsernamePasswordAuthWayServiceImpl.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/UsernamePasswordAuthWayServiceImpl.java @@ -2,9 +2,8 @@ package com.diboot.shiro.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.diboot.core.entity.BaseEntity; -import com.diboot.core.util.BeanUtils; import com.diboot.core.util.V; -import com.diboot.shiro.BaseJwtAuthenticationToken; +import com.diboot.shiro.jwt.BaseJwtAuthenticationToken; import com.diboot.shiro.config.AuthType; import com.diboot.shiro.entity.SysUser; import com.diboot.shiro.service.AuthWayService; diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/util/AuthHelper.java b/diboot-shiro/src/main/java/com/diboot/shiro/util/AuthHelper.java new file mode 100644 index 0000000..2dbb46d --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/util/AuthHelper.java @@ -0,0 +1,44 @@ +package com.diboot.shiro.util; + +import com.diboot.core.entity.BaseEntity; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.subject.Subject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AuthHelper { + private static final Logger logger = LoggerFactory.getLogger(AuthHelper.class); + + /** + * 得到当前登录的用户名 + * @return + */ + public static T getCurrentUser(){ + try{ + Subject subject = SecurityUtils.getSubject(); + if(subject != null && subject.isAuthenticated()){ + return (T)subject.getPrincipal(); + } + } + catch (Exception e){ + logger.warn("获取用户信息异常", e); + } + return null; + } + + /** + * 得到当前登录的用户id + * @return + */ + public static Long getCurrentUserId(){ + BaseEntity user = getCurrentUser(); + if(user != null){ + return (Long)user.getId(); + } + if(logger.isDebugEnabled()){ + logger.warn("无法获取当前用户Id!"); + } + return null; + } + +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/vo/PermissionVO.java b/diboot-shiro/src/main/java/com/diboot/shiro/vo/PermissionVO.java new file mode 100644 index 0000000..d80cb76 --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/vo/PermissionVO.java @@ -0,0 +1,20 @@ +package com.diboot.shiro.vo; + +import com.diboot.core.binding.annotation.BindEntityList; +import com.diboot.shiro.entity.Permission; +import com.diboot.shiro.entity.Role; +import lombok.Data; + +import java.util.List; + +/** + * @author Yangzhao + * @version v2.0 + * @date 2019/6/6 + */ +@Data +public class PermissionVO extends Permission { + + private static final long serialVersionUID = 860775286174387052L; + +} \ No newline at end of file diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/vo/RoleVO.java b/diboot-shiro/src/main/java/com/diboot/shiro/vo/RoleVO.java index 3d6f717..923438f 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/vo/RoleVO.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/vo/RoleVO.java @@ -6,6 +6,7 @@ import com.diboot.shiro.entity.Role; import lombok.Data; import java.util.List; +import java.util.Set; /** * @author Yangzhao From d62699f755d57aabfe6bd4fa8a945285a2b8b728 Mon Sep 17 00:00:00 2001 From: wuy <1311695042@qq.com> Date: Fri, 14 Jun 2019 11:06:55 +0800 Subject: [PATCH 05/11] =?UTF-8?q?=E5=BF=BD=E7=95=A5gradle=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shiro/service/impl/UsernamePasswordAuthWayServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/UsernamePasswordAuthWayServiceImpl.java b/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/UsernamePasswordAuthWayServiceImpl.java index bce9496..2a3476c 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/UsernamePasswordAuthWayServiceImpl.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/service/impl/UsernamePasswordAuthWayServiceImpl.java @@ -58,7 +58,7 @@ public class UsernamePasswordAuthWayServiceImpl implements AuthWayService { @Override public boolean isPasswordMatch() { - String password = new String(token.getPassword()); + String password = token.getPassword(); // 构建查询条件 QueryWrapper queryWrapper = new QueryWrapper<>(); From af48589cbf31a0bc68e6af52d1eee544ae758a3a Mon Sep 17 00:00:00 2001 From: wuy <1311695042@qq.com> Date: Fri, 14 Jun 2019 11:07:35 +0800 Subject: [PATCH 06/11] =?UTF-8?q?=E5=A2=9E=E5=8A=A0gradle=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E6=96=87=E4=BB=B6=E5=BF=BD=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 368b438..28db903 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ application-*.properties build/ out/ /.idea/ +gradle \ No newline at end of file From bb8e1578c482e05af571ce6c8ebc6e8f69d56e3a Mon Sep 17 00:00:00 2001 From: wuy <1311695042@qq.com> Date: Fri, 14 Jun 2019 11:11:39 +0800 Subject: [PATCH 07/11] update .gitignore --- .DS_Store | Bin 6148 -> 0 bytes .gitignore | 4 +++- diboot-example/src/test/java/MainTest.java | 9 +++++++++ gradle/wrapper/gradle-wrapper.jar | Bin 55741 -> 0 bytes gradle/wrapper/gradle-wrapper.properties | 5 ----- 5 files changed, 12 insertions(+), 6 deletions(-) delete mode 100644 .DS_Store create mode 100644 diboot-example/src/test/java/MainTest.java delete mode 100644 gradle/wrapper/gradle-wrapper.jar delete mode 100644 gradle/wrapper/gradle-wrapper.properties diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 595d6c705baa1c9e7b7a9c7cf16abfe6d4ee73b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKOKJl#43(NJ23lyAUHSr9cgjBODSFJ8JC(S(R9G(i?)KxBAu>cX7IKrU+}+R~JsX-SU*3;o3* z$^D8t|EdR~&doD*! z$+a`!3^)VMfHQDD130rqs!K)J&VV!E416*m`$IqzjD}gUY#mUg1OUo4x(IBkB_t*o zM#HR#83=1AP(#^D4AyXr2lI=DSy97@t@vPj^GET*yOWqvo@gJp^1oJ76SsicL_j8_K}-1sq;Z> Z_(j94D67bL2?zQ`pb+B98TbVT-T@1RHk1GW diff --git a/.gitignore b/.gitignore index 28db903..9c9c57a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,6 @@ application-*.properties build/ out/ /.idea/ -gradle \ No newline at end of file +gradle/ + + git commit -m 'update .gitignore' \ No newline at end of file diff --git a/diboot-example/src/test/java/MainTest.java b/diboot-example/src/test/java/MainTest.java new file mode 100644 index 0000000..5075edc --- /dev/null +++ b/diboot-example/src/test/java/MainTest.java @@ -0,0 +1,9 @@ +/** + * @author : wee + * @Description: todo + * @Date 2019-05-17 19:15 + */ +public class MainTest { + public static void main(String[] args) { + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 457aad0d98108420a977756b7145c93c8910b076..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55741 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 O(FVj@M23F*_WuFL)$*MH diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 75b8c7c..0000000 --- a/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,5 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.0-bin.zip -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists From eb4cd5569f8582ec03a37d55c6d37a440c83d160 Mon Sep 17 00:00:00 2001 From: wuy <1311695042@qq.com> Date: Fri, 14 Jun 2019 11:12:07 +0800 Subject: [PATCH 08/11] =?UTF-8?q?=E5=A2=9E=E5=8A=A0git=E5=BF=BD=E7=95=A5?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 9c9c57a..ac0bd80 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,4 @@ application-*.properties build/ out/ /.idea/ -gradle/ - - git commit -m 'update .gitignore' \ No newline at end of file +gradle/ \ No newline at end of file From 8689848fb173c09c8063e9cd5cb84d5b7437cad1 Mon Sep 17 00:00:00 2001 From: wuy <1311695042@qq.com> Date: Fri, 14 Jun 2019 23:21:54 +0800 Subject: [PATCH 09/11] =?UTF-8?q?=E5=A2=9E=E5=8A=A0RequiresPermissions?= =?UTF-8?q?=E4=BB=A3=E7=90=86=E6=B3=A8=E8=A7=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bind/annotation/PermissionsMenu.java | 25 +++++++ .../annotation/RequiresPermissionsProxy.java | 47 +++++++++++++ ...ssionProxyAnnotationMethodInterceptor.java | 30 ++++++++ .../PermissionProxyAnnotationHandler.java | 69 +++++++++++++++++++ .../com/diboot/shiro/config/ShiroConfig.java | 19 +++++ .../controller/PermissionController.java | 9 ++- 6 files changed, 196 insertions(+), 3 deletions(-) create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/bind/annotation/PermissionsMenu.java create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/bind/annotation/RequiresPermissionsProxy.java create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/bind/aop/PermissionProxyAnnotationMethodInterceptor.java create mode 100644 diboot-shiro/src/main/java/com/diboot/shiro/bind/handler/PermissionProxyAnnotationHandler.java diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/bind/annotation/PermissionsMenu.java b/diboot-shiro/src/main/java/com/diboot/shiro/bind/annotation/PermissionsMenu.java new file mode 100644 index 0000000..1307e81 --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/bind/annotation/PermissionsMenu.java @@ -0,0 +1,25 @@ +package com.diboot.shiro.bind.annotation; + +import java.lang.annotation.*; + +/** + * controller上注解,用于标记权限的菜单分类 + * @author : wee + * @version v2.0 + * @Date 2019-06-14 23:00 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface PermissionsMenu { + /** + * 菜单编码 + * @return + */ + String menuCode(); + /** + * 菜单名称 + * @return + */ + String menuName(); +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/bind/annotation/RequiresPermissionsProxy.java b/diboot-shiro/src/main/java/com/diboot/shiro/bind/annotation/RequiresPermissionsProxy.java new file mode 100644 index 0000000..f2e5347 --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/bind/annotation/RequiresPermissionsProxy.java @@ -0,0 +1,47 @@ +package com.diboot.shiro.bind.annotation; + +import org.apache.shiro.authz.annotation.Logical; +import org.apache.shiro.authz.annotation.RequiresPermissions; + +import java.lang.annotation.*; + +/** + * @author : wee + * @version v2.0 + * @Date 2019-06-14 17:50 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +@RequiresPermissions("default") +public @interface RequiresPermissionsProxy { + + /** + * 代理 {@link RequiresPermissions#value()} + */ + String[] value(); + + /** + * 代理 {@link RequiresPermissions#logical()} + */ + Logical logical() default Logical.AND; + + /** + * 菜单编码 + * @return + */ + String menuCode(); + /** + * 菜单名称 + * @return + */ + String menuName(); + + /** + * 权限名称 + * @return + */ + String permissionName(); + + +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/bind/aop/PermissionProxyAnnotationMethodInterceptor.java b/diboot-shiro/src/main/java/com/diboot/shiro/bind/aop/PermissionProxyAnnotationMethodInterceptor.java new file mode 100644 index 0000000..71e7d7c --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/bind/aop/PermissionProxyAnnotationMethodInterceptor.java @@ -0,0 +1,30 @@ +package com.diboot.shiro.bind.aop; + +import com.diboot.shiro.bind.annotation.RequiresPermissionsProxy; +import com.diboot.shiro.bind.handler.PermissionProxyAnnotationHandler; +import org.apache.shiro.aop.AnnotationResolver; +import org.apache.shiro.authz.aop.AuthorizingAnnotationMethodInterceptor; + +/** + * {@link RequiresPermissionsProxy} 拦截器 + * @author : wee + * @version : v2.0 + * @Date 2019-06-14 22:19 + */ +public class PermissionProxyAnnotationMethodInterceptor extends AuthorizingAnnotationMethodInterceptor { + /** + * Default no-argument constructor that ensures this interceptor looks for + * {@link org.apache.shiro.authz.annotation.RequiresPermissions RequiresPermissions} annotations in a method declaration. + */ + public PermissionProxyAnnotationMethodInterceptor() { + super( new PermissionProxyAnnotationHandler() ); + } + + /** + * @param resolver + * @since 1.1 + */ + public PermissionProxyAnnotationMethodInterceptor(AnnotationResolver resolver) { + super( new PermissionProxyAnnotationHandler(), resolver); + } +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/bind/handler/PermissionProxyAnnotationHandler.java b/diboot-shiro/src/main/java/com/diboot/shiro/bind/handler/PermissionProxyAnnotationHandler.java new file mode 100644 index 0000000..d303482 --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/bind/handler/PermissionProxyAnnotationHandler.java @@ -0,0 +1,69 @@ +package com.diboot.shiro.bind.handler; + +import com.diboot.shiro.bind.annotation.RequiresPermissionsProxy; +import org.apache.shiro.aop.AnnotationResolver; +import org.apache.shiro.authz.AuthorizationException; +import org.apache.shiro.authz.annotation.Logical; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.apache.shiro.authz.annotation.RequiresRoles; +import org.apache.shiro.authz.aop.AuthorizingAnnotationHandler; +import org.apache.shiro.authz.aop.AuthorizingAnnotationMethodInterceptor; +import org.apache.shiro.authz.aop.PermissionAnnotationHandler; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; +import java.util.Arrays; +import java.util.Map; + +/** + * {@link RequiresPermissionsProxy} 助手类, 参考{@link PermissionAnnotationHandler}实现 + * @author : wee + * @version : v2.0 + * @Date 2019-06-14 22:19 + */ +public class PermissionProxyAnnotationHandler extends AuthorizingAnnotationHandler { + + private final static String REQUIRES_PERMISSIONS_VALUE = "value"; + + private final static String REQUIRES_PERMISSIONS_LOGICAL = "logical"; + + private final static String JDK_MEMBER_VALUES = "memberValues"; + + /** + * 标记服务的注解 + */ + public PermissionProxyAnnotationHandler() { + super(RequiresPermissionsProxy.class); + } + + /** + * 将{@link RequiresPermissionsProxy} 代理的内容赋值给{@link RequiresPermissions} + */ + @Override + public void assertAuthorized(Annotation a) throws AuthorizationException { + if (!(a instanceof RequiresPermissionsProxy)) { + return; + } + RequiresPermissionsProxy rrAnnotation = (RequiresPermissionsProxy) a; + try { + //获取RequiresPermissionsProxy上的RequiresPermissions注解 + RequiresPermissions requiresPermissions = rrAnnotation.annotationType().getAnnotation(RequiresPermissions.class); + + InvocationHandler invocationHandler = Proxy.getInvocationHandler(requiresPermissions); + /* memberValues 为JDK中存储所有成员变量值的Map {@link AnnotationInvocationHandler#memberValues}*/ + Field jdkValue = invocationHandler.getClass().getDeclaredField(JDK_MEMBER_VALUES); + jdkValue.setAccessible(true); + /*获取RequiresPermissions对应的代理属性值*/ + Map memberValues = (Map) jdkValue.get(invocationHandler); + /*动态设置RequiresPermissions注解的内容*/ + memberValues.put(REQUIRES_PERMISSIONS_VALUE, rrAnnotation.value()); + memberValues.put(REQUIRES_PERMISSIONS_LOGICAL, rrAnnotation.logical()); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/config/ShiroConfig.java b/diboot-shiro/src/main/java/com/diboot/shiro/config/ShiroConfig.java index 681eed0..e755168 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/config/ShiroConfig.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/config/ShiroConfig.java @@ -1,10 +1,15 @@ package com.diboot.shiro.config; +import com.diboot.shiro.bind.aop.PermissionProxyAnnotationMethodInterceptor; import com.diboot.shiro.jwt.BaseJwtAuthenticationFilter; import com.diboot.shiro.jwt.BaseJwtRealm; +import org.apache.shiro.aop.AnnotationResolver; +import org.apache.shiro.authz.aop.*; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.realm.Realm; import org.apache.shiro.spring.LifecycleBeanPostProcessor; +import org.apache.shiro.spring.aop.SpringAnnotationResolver; +import org.apache.shiro.spring.security.interceptor.AopAllianceAnnotationsAuthorizingMethodInterceptor; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.filter.authc.AnonymousFilter; @@ -17,7 +22,9 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import javax.servlet.Filter; +import java.util.ArrayList; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; /*** @@ -96,6 +103,18 @@ public class ShiroConfig { @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); + AopAllianceAnnotationsAuthorizingMethodInterceptor advice = (AopAllianceAnnotationsAuthorizingMethodInterceptor)authorizationAttributeSourceAdvisor.getAdvice(); + //重置拦截器,添加新的PermissionProxyAnnotationMethodInterceptor + List interceptors =new ArrayList<>(6); + AnnotationResolver resolver = new SpringAnnotationResolver(); + interceptors.add(new PermissionProxyAnnotationMethodInterceptor(resolver)); + interceptors.add(new RoleAnnotationMethodInterceptor(resolver)); + interceptors.add(new PermissionAnnotationMethodInterceptor(resolver)); + interceptors.add(new AuthenticatedAnnotationMethodInterceptor(resolver)); + interceptors.add(new UserAnnotationMethodInterceptor(resolver)); + interceptors.add(new GuestAnnotationMethodInterceptor(resolver)); + advice.setMethodInterceptors(interceptors); + authorizationAttributeSourceAdvisor.setSecurityManager(securityManager()); return authorizationAttributeSourceAdvisor; } diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/controller/PermissionController.java b/diboot-shiro/src/main/java/com/diboot/shiro/controller/PermissionController.java index b4094ad..f06c135 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/controller/PermissionController.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/controller/PermissionController.java @@ -4,10 +4,11 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.diboot.core.controller.BaseCrudRestController; import com.diboot.core.service.BaseService; import com.diboot.core.util.BeanUtils; -import com.diboot.core.util.V; import com.diboot.core.vo.JsonResult; import com.diboot.core.vo.Pagination; import com.diboot.core.vo.Status; +import com.diboot.shiro.bind.annotation.PermissionsMenu; +import com.diboot.shiro.bind.annotation.RequiresPermissionsProxy; import com.diboot.shiro.entity.Permission; import com.diboot.shiro.service.PermissionService; import com.diboot.shiro.vo.PermissionVO; @@ -28,6 +29,7 @@ import java.util.List; * @version 2018/12/23 * Copyright © www.dibo.ltd */ +@PermissionsMenu(menuName = "权限", menuCode = "permission") @RestController @RequestMapping("/permission") public class PermissionController extends BaseCrudRestController { @@ -45,8 +47,9 @@ public class PermissionController extends BaseCrudRestController { * @return * @throws Exception */ - @RequiresPermissions("permission:list") @GetMapping("/list") + @RequiresPermissionsProxy(value = {"permission:list"}, menuCode = "permission", + menuName = "权限", permissionName = "列表") public JsonResult getVOList(HttpServletRequest request) throws Exception{ QueryWrapper queryWrapper = buildQuery(request); // 构建分页 @@ -80,8 +83,8 @@ public class PermissionController extends BaseCrudRestController { * @return * @throws Exception */ - @RequiresPermissions("permission:get") @GetMapping("/{id}") + @RequiresPermissionsProxy(value = {"permission:get"}, menuCode = "permission", menuName = "权限", permissionName = "查看") public JsonResult getModel(@PathVariable("id")Long id, HttpServletRequest request, ModelMap modelMap) throws Exception{ PermissionVO vo = permissionService.getViewObject(id, PermissionVO.class); From cc8479112622be470e726d1e73ddf19bffdc1838 Mon Sep 17 00:00:00 2001 From: wuy <1311695042@qq.com> Date: Fri, 14 Jun 2019 23:26:04 +0800 Subject: [PATCH 10/11] =?UTF-8?q?=E5=A2=9E=E5=8A=A0RequiresPermissions?= =?UTF-8?q?=E4=BB=A3=E7=90=86=E6=B3=A8=E8=A7=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../diboot/shiro/bind/annotation/RequiresPermissionsProxy.java | 1 + 1 file changed, 1 insertion(+) diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/bind/annotation/RequiresPermissionsProxy.java b/diboot-shiro/src/main/java/com/diboot/shiro/bind/annotation/RequiresPermissionsProxy.java index f2e5347..9577887 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/bind/annotation/RequiresPermissionsProxy.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/bind/annotation/RequiresPermissionsProxy.java @@ -6,6 +6,7 @@ import org.apache.shiro.authz.annotation.RequiresPermissions; import java.lang.annotation.*; /** + * 注解{@link RequiresPermissions}的增强注解,增加权限描述等字段 * @author : wee * @version v2.0 * @Date 2019-06-14 17:50 From 136e83d33ad932c55a570d9b970347816d9bb47a Mon Sep 17 00:00:00 2001 From: mazhicheng Date: Sat, 15 Jun 2019 15:40:54 +0800 Subject: [PATCH 11/11] =?UTF-8?q?+BaseService=E7=9B=B8=E5=85=B3=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E7=9A=84=E5=8D=95=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../diboot/core/test/StartupApplication.java | 17 ++++ .../core/test/config/SpringMvcConfig.java | 76 ++++++++++++++++ .../core/test/service/BaseServiceTest.java | 89 +++++++++++++++++++ .../src/test/resources/application.properties | 32 +++++++ diboot-core/src/test/resources/banner.txt | 5 ++ 5 files changed, 219 insertions(+) create mode 100644 diboot-core/src/test/java/diboot/core/test/StartupApplication.java create mode 100644 diboot-core/src/test/java/diboot/core/test/config/SpringMvcConfig.java create mode 100644 diboot-core/src/test/java/diboot/core/test/service/BaseServiceTest.java create mode 100644 diboot-core/src/test/resources/application.properties create mode 100644 diboot-core/src/test/resources/banner.txt diff --git a/diboot-core/src/test/java/diboot/core/test/StartupApplication.java b/diboot-core/src/test/java/diboot/core/test/StartupApplication.java new file mode 100644 index 0000000..1945235 --- /dev/null +++ b/diboot-core/src/test/java/diboot/core/test/StartupApplication.java @@ -0,0 +1,17 @@ +package diboot.core.test; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +/** + * @author Administrator + */ +@SpringBootApplication +public class StartupApplication extends SpringBootServletInitializer { + + public static void main(String[] args) { + SpringApplication.run(StartupApplication.class, args); + } + +} \ No newline at end of file diff --git a/diboot-core/src/test/java/diboot/core/test/config/SpringMvcConfig.java b/diboot-core/src/test/java/diboot/core/test/config/SpringMvcConfig.java new file mode 100644 index 0000000..f22963e --- /dev/null +++ b/diboot-core/src/test/java/diboot/core/test/config/SpringMvcConfig.java @@ -0,0 +1,76 @@ +package diboot.core.test.config; + +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.alibaba.fastjson.support.config.FastJsonConfig; +import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; +import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor; +import com.diboot.core.util.D; +import com.diboot.core.util.DateConverter; +import org.mybatis.spring.annotation.MapperScan; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.http.HttpMessageConverters; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.format.FormatterRegistry; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.ArrayList; +import java.util.List; + +/*** + * Spring配置文件 + * @author Mazhicheng + * @version v2.0 + * @date 2019/6/10 + */ +@Configuration +@EnableAutoConfiguration +@EnableTransactionManagement(proxyTargetClass=true) +@ComponentScan(basePackages={"com.diboot"}) +@MapperScan({"com.diboot.**.mapper"}) +public class SpringMvcConfig implements WebMvcConfigurer{ + private static final Logger log = LoggerFactory.getLogger(SpringMvcConfig.class); + + /** + * JSON转换组件替换为fastJson + */ + @Bean + public HttpMessageConverters fastJsonHttpMessageConverters() { + FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter(); + //处理中文乱码问题 + List fastMediaTypes = new ArrayList<>(); + fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8); + converter.setSupportedMediaTypes(fastMediaTypes); + // 配置转换格式 + FastJsonConfig fastJsonConfig = new FastJsonConfig(); + // 设置fastjson的序列化参数:禁用循环依赖检测,数据兼容浏览器端(避免JS端Long精度丢失问题) + fastJsonConfig.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect, + SerializerFeature.BrowserCompatible); + fastJsonConfig.setDateFormat(D.FORMAT_DATETIME_Y4MDHM); + converter.setFastJsonConfig(fastJsonConfig); + + HttpMessageConverter httpMsgConverter = converter; + return new HttpMessageConverters(httpMsgConverter); + } + + @Override + public void addFormatters(FormatterRegistry registry) { + registry.addConverter(new DateConverter()); + } + + /** + * Mybatis-plus分页插件 + */ + @Bean + public PaginationInterceptor paginationInterceptor() { + PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); + return paginationInterceptor; + } + +} \ No newline at end of file diff --git a/diboot-core/src/test/java/diboot/core/test/service/BaseServiceTest.java b/diboot-core/src/test/java/diboot/core/test/service/BaseServiceTest.java new file mode 100644 index 0000000..8b78510 --- /dev/null +++ b/diboot-core/src/test/java/diboot/core/test/service/BaseServiceTest.java @@ -0,0 +1,89 @@ +package diboot.core.test.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.diboot.core.config.BaseConfig; +import com.diboot.core.entity.Metadata; +import com.diboot.core.service.MetadataService; +import com.diboot.core.util.V; +import com.diboot.core.vo.Pagination; +import diboot.core.test.StartupApplication; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.List; + +/** + * BaseService接口实现测试 (需先执行example中的初始化SQL) + * @author Mazhicheng + * @version v2.0 + * @date 2019/06/15 + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = {StartupApplication.class}) +public class BaseServiceTest { + + @Autowired + MetadataService metadataService; + + @Test + public void testGet(){ + // 查询总数 + int count = metadataService.getEntityListCount(null); + Assert.assertTrue(count > 0); + // 查询list + List metadataList = metadataService.getEntityList(null); + Assert.assertTrue(V.notEmpty(metadataList)); + Assert.assertTrue(metadataList.size() == count); + // 第一页数据 + List pageList = metadataService.getEntityList(null, new Pagination()); + Assert.assertTrue(pageList.size() > 0 && pageList.size() <= BaseConfig.getPageSize()); + + // 查询单个记录 + Long id = metadataList.get(0).getId(); + Metadata first = metadataService.getEntity(id); + Assert.assertTrue(first != null); + + // 只查询第一条记录对应type类型的 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(Metadata::getType, first.getType()); + metadataList = metadataService.getEntityList(queryWrapper); + Assert.assertTrue(V.notEmpty(metadataList)); + // 结果type值一致 + metadataList.stream().forEach( m -> { + Assert.assertTrue(m.getType().equals(first.getType())); + }); + } + + @Test + public void testCreateUpdateAndDelete(){ + // 创建 + String TYPE = "ID_TYPE"; + Metadata metadata = new Metadata(); + metadata.setType(TYPE); + metadata.setItemName("证件品类"); + metadata.setParentId(0L); + metadataService.createEntity(metadata); + + // 查询是否创建成功 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(Metadata::getType, TYPE); + List metadataList = metadataService.getEntityList(queryWrapper); + Assert.assertTrue(V.notEmpty(metadataList)); + + // 更新 + metadata.setItemName("证件类型"); + metadataService.updateEntity(metadata); + Metadata metadata2 = metadataService.getEntity(metadata.getId()); + Assert.assertTrue(metadata2.getItemName().equals(metadata.getItemName())); + + // 删除 + metadataService.deleteEntity(metadata.getId()); + metadata2 = metadataService.getEntity(metadata.getId()); + Assert.assertTrue(metadata2 == null); + } + +} \ No newline at end of file diff --git a/diboot-core/src/test/resources/application.properties b/diboot-core/src/test/resources/application.properties new file mode 100644 index 0000000..20e84da --- /dev/null +++ b/diboot-core/src/test/resources/application.properties @@ -0,0 +1,32 @@ +# spring config +spring.devtools.restart.enabled=true + +#datasource config +spring.datasource.url=jdbc:mysql://localhost:3306/diboot_example?characterEncoding=utf8&serverTimezone=GMT%2B8 +spring.datasource.username=diboot +spring.datasource.password=123456 +spring.datasource.hikari.maximum-pool-size=5 +spring.datasource.hikari.data-source-properties.useInformationSchema=true +spring.datasource.hikari.data-source-properties.nullCatalogMeansCurrent=true +# 数据库驱动 +spring.datasource.hikari.driver-class-name=com.mysql.cj.jdbc.Driver + +# mybatis配置 +#mybatis.configuration.cache-enabled=false +#mybatis.configuration.lazy-loading-enabled=true +#mybatis.configuration.map-underscore-to-camel-case=true +#mybatis.configuration.multiple-result-sets-enabled=false +#mybatis.configuration.use-generated-keys=true +#mybatis.configuration.auto-mapping-behavior=full +#mybatis.configuration.default-statement-timeout=60 +#mybatis.configuration.log-impl=org.apache.ibatis.logging.log4j2.Log4j2Impl + +# logging config +logging.pattern.console=%clr{%d{MM-dd HH:mm:ss.SSS}}{faint} %clr{%5p} %clr{${PID}}{faint} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n%xwEx +logging.level.root=info +logging.level.org.apache=info +logging.level.org.hibernate.validator=info +logging.level.org.springframework=info +logging.level.com.zaxxer.hikari=info +logging.level.com.diboot=debug +logging.level.org.mybatis=debug diff --git a/diboot-core/src/test/resources/banner.txt b/diboot-core/src/test/resources/banner.txt new file mode 100644 index 0000000..0bad421 --- /dev/null +++ b/diboot-core/src/test/resources/banner.txt @@ -0,0 +1,5 @@ + __ _ __ __ + ____/ / (_) / /_ ____ ____ / /_ + / __ / / / / __ \ / __ \ / __ \ / __/ +/ /_/ / / / / /_/ / / /_/ / / /_/ / / /_ +\__,_/ /_/ /_.___/ \____/ \____/ \__/