Merge pull request #8 from dibo-software/develop

Develop
This commit is contained in:
Mazc 2019-06-15 15:42:00 +08:00 committed by GitHub
commit de3078ae9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
71 changed files with 2246 additions and 71 deletions

7
.gitignore vendored
View File

@ -1,6 +1,11 @@
# Ignore Gradle project-specific cache directory
.gradle
*.iml
application.properties
application-*.properties
# Ignore Gradle build output directory
build
build/
out/
/.idea/
gradle/

View File

@ -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

View File

@ -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);
}
}

View File

@ -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<MediaType> 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;
}
}

View File

@ -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<Metadata> metadataList = metadataService.getEntityList(null);
Assert.assertTrue(V.notEmpty(metadataList));
Assert.assertTrue(metadataList.size() == count);
// 第一页数据
List<Metadata> 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<Metadata> 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<Metadata> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Metadata::getType, TYPE);
List<Metadata> 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);
}
}

View File

@ -1,11 +1,3 @@
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
@ -19,11 +11,6 @@ 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
@ -42,4 +29,4 @@ 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
logging.level.org.mybatis=debug

View File

@ -0,0 +1,5 @@
__ _ __ __
____/ / (_) / /_ ____ ____ / /_
/ __ / / / / __ \ / __ \ / __ \ / __/
/ /_/ / / / / /_/ / / /_/ / / /_/ / / /_
\__,_/ /_/ /_.___/ \____/ \____/ \__/

24
diboot-docs/README.md Normal file
View File

@ -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'))
```

View File

@ -0,0 +1,5 @@
module.exports = {
title: 'diboot-core开发文档',
description: 'diboot-core相关概念、接口、开发技巧相关的文档说明等。',
base: '/diboot-core-docs/'
}

View File

@ -0,0 +1,8 @@
export default ({
Vue, // VuePress 正在使用的 Vue 构造函数
options, // 附加到根实例的一些选项
router, // 当前应用的路由实例
siteData // 站点元数据
}) => {
// 做相关的处理
}

View File

@ -0,0 +1 @@
# Hello VuePress!

View File

@ -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"
}

View File

@ -1,4 +1,7 @@
dependencies {
compile project(":diboot-core")
compile project(":diboot-shiro")
testCompile group: 'junit', name: 'junit', version: '4.12'
}

View File

@ -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);

View File

@ -0,0 +1,69 @@
package com.diboot.example.controller;
import com.diboot.core.vo.JsonResult;
import com.diboot.core.vo.Status;
import com.diboot.shiro.jwt.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.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
@RestController
@RequestMapping("/auth")
public class AuthTokenController {
private static final Logger logger = LoggerFactory.getLogger(AuthTokenController.class);
@Autowired
private Map<String, AuthWayService> authWayServiceMap;
/***
* 用户名密码登录接口
* @param sysUser
* @param request
* @param response
* @return
* @throws Exception
*/
@PostMapping("/login")
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);
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[]{"退出登录成功"});
}
}

View File

@ -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<Department> queryWrapper = buildQuery(request);

View File

@ -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<SysUser> query = new QueryWrapper<>();
query.lambda()
.eq(SysUser::getUsername, username);
List<SysUser> userList = sysUserService.getEntityList(query);
if (V.isEmpty(userList)){
return new JsonResult(Status.FAIL_OPERATION, new String[]{"获取数据失败"});
}
SysUser user = userList.get(0);
List<RoleVO> 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[]{"获取角色列表成功"});
}
}

View File

@ -19,6 +19,9 @@ public class User extends BaseEntity {
@TableField
private String username;
@TableField
private String password;
@TableField
private String gender;

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "./mybatis-3-mapper.dtd">
<mapper namespace="com.diboot.example.mapper.OrganizationMapper">
<mapper namespace="com.diboot.example.mapper.SysUserMapper">
</mapper>

View File

@ -1,15 +0,0 @@
package com.diboot.example.mapper;
import com.diboot.core.mapper.BaseCrudMapper;
import com.diboot.example.entity.Role;
/**
* 员工Mapper
* @author Mazhicheng
* @version 2018/12/22
* Copyright © www.dibo.ltd
*/
public interface RoleMapper extends BaseCrudMapper<Role> {
}

View File

@ -1,14 +0,0 @@
package com.diboot.example.service;
import com.diboot.core.service.BaseService;
import com.diboot.example.entity.Role;
/**
* 员工相关Service
* @author Mazhicheng
* @version v2.0
* @date 2019/1/5
*/
public interface RoleService extends BaseService<Role> {
}

View File

@ -1,20 +0,0 @@
package com.diboot.example.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 lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* 员工相关Service
* @author Mazhicheng
* @version 2018/12/23
* Copyright © www.dibo.ltd
*/
@Service
@Slf4j
public class RoleServiceImpl extends BaseServiceImpl<RoleMapper, Role> implements RoleService {
}

View File

@ -0,0 +1,9 @@
/**
* @author : wee
* @Description: todo
* @Date 2019-05-17 19:15
*/
public class MainTest {
public static void main(String[] args) {
}
}

View File

@ -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(){
}
}

View File

@ -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<Department> query = new QueryWrapper<>();
query.eq(BeanUtils.convertToFieldName(Department::getName), "研发部");
List<Department> departmentList = departmentService.getEntityList(query);
Assert.assertTrue(V.notEmpty(departmentList));
}
}

View File

@ -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'
}

View File

@ -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();
}

View File

@ -0,0 +1,48 @@
package com.diboot.shiro.bind.annotation;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import java.lang.annotation.*;
/**
* 注解{@link RequiresPermissions}的增强注解增加权限描述等字段
* @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();
}

View File

@ -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);
}
}

View File

@ -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<String, Object> memberValues = (Map<String, Object>) 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();
}
}
}

View File

@ -0,0 +1,50 @@
package com.diboot.shiro.config;
import lombok.Data;
/***
* 认证方式
* @author Yangzhao
* @version v2.0
* @date 2019/6/6
*/
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;
}
}

View File

@ -0,0 +1,122 @@
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;
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.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
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);
@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<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/auth/login", "anon");
filterChainDefinitionMap.put("/error", "anon");
filterChainDefinitionMap.put("/auth/logout", "logout");
filterChainDefinitionMap.put("/**", "jwt");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
// 设置过滤器
Map<String, Filter> 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();
AopAllianceAnnotationsAuthorizingMethodInterceptor advice = (AopAllianceAnnotationsAuthorizingMethodInterceptor)authorizationAttributeSourceAdvisor.getAdvice();
//重置拦截器添加新的PermissionProxyAnnotationMethodInterceptor
List<AuthorizingAnnotationMethodInterceptor> 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;
}
}

View File

@ -0,0 +1,124 @@
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.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;
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
*/
@PermissionsMenu(menuName = "权限", menuCode = "permission")
@RestController
@RequestMapping("/permission")
public class PermissionController extends BaseCrudRestController {
private static final Logger logger = LoggerFactory.getLogger(PermissionService.class);
@Autowired
private PermissionService permissionService;
/***
* 查询ViewObject的分页数据 (此为非继承的自定义使用案例更简化的调用父类案例请参考UserController)
* <p>
* url参数示例: /list?_pageSize=20&_pageIndex=1&_orderBy=id&code=TST
* </p>
* @return
* @throws Exception
*/
@GetMapping("/list")
@RequiresPermissionsProxy(value = {"permission:list"}, menuCode = "permission",
menuName = "权限", permissionName = "列表")
public JsonResult getVOList(HttpServletRequest request) throws Exception{
QueryWrapper<Permission> queryWrapper = buildQuery(request);
// 构建分页
Pagination pagination = buildPagination(request);
// 查询当前页的Entity主表数据
List entityList = getService().getEntityList(queryWrapper, pagination);
// 自动转换VO中注解绑定的关联
List<PermissionVO> 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
*/
@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);
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;
}
}

View File

@ -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 Yangzhao
* @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;
}

View File

@ -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 Yangzhao
* @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<Permission> permissionList;
}

View File

@ -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 Yangzhao
* @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;
}

View File

@ -0,0 +1,40 @@
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;
/**
* @author Yangzhao
* @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<Role> roleList;
@TableField(exist = false)
private List<RoleVO> roleVOList;
@TableField(exist = false)
private List<Permission> permissionList;
}

View File

@ -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 Yangzhao
* @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;
}

View File

@ -0,0 +1,89 @@
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.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 Yangzhao
* @version v2.0
* @date 2019/6/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;
}
//获取username
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();
}
}
}
}

View File

@ -0,0 +1,207 @@
package com.diboot.shiro.jwt;
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 Yangzhao
* @version v2.0
* @date 2019/6/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<String, AuthWayService> authWayServiceMap;
// 默认构造函数
public BaseJwtAuthenticationToken(){
}
public BaseJwtAuthenticationToken(Map<String, AuthWayService> authWayServiceMap){
this.authWayServiceMap = authWayServiceMap;
}
/***
* 用户名码形式的授权
* @param account
* @param password
*/
public BaseJwtAuthenticationToken(Map<String, AuthWayService> 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<String, AuthWayService> 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<String, AuthWayService> 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<String, AuthWayService> getAuthWayServiceMap() {
return authWayServiceMap;
}
public void setAuthWayServiceMap(Map<String, AuthWayService> authWayServiceMap) {
this.authWayServiceMap = authWayServiceMap;
}
}

View File

@ -0,0 +1,122 @@
package com.diboot.shiro.jwt;
import com.diboot.core.entity.BaseEntity;
import com.diboot.core.util.V;
import com.diboot.shiro.entity.Permission;
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;
/**
* @author Yangzhao
* @version v2.0
* @date 2019/6/6
*/
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
List<RoleVO> roleVOList = roleService.getRelatedRoleAndPermissionListByUser(userType, user.getId());
if (V.isEmpty(roleVOList)){
return authorizationInfo;
}
// 整理所有权限许可列表
Set<String> allStringRoleList = new HashSet<>();
Set<String> 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;
}
}

View File

@ -0,0 +1,15 @@
package com.diboot.shiro.mapper;
import com.diboot.core.mapper.BaseCrudMapper;
import com.diboot.shiro.entity.Permission;
/**
* 授权Mapper
* @author Yangzhao
* @version v2.0
* @date 2019/6/6
*/
public interface PermissionMapper extends BaseCrudMapper<Permission> {
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "./mybatis-3-mapper.dtd">
<mapper namespace="com.diboot.shiro.mapper.PermissionMapper">
</mapper>

View File

@ -0,0 +1,15 @@
package com.diboot.shiro.mapper;
import com.diboot.core.mapper.BaseCrudMapper;
import com.diboot.shiro.entity.Role;
/**
* 角色Mapper
* @author Yangzhao
* @version v2.0
* @date 2019/6/6
*/
public interface RoleMapper extends BaseCrudMapper<Role> {
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "./mybatis-3-mapper.dtd">
<mapper namespace="com.diboot.shiro.mapper.RoleMapper">
</mapper>

View File

@ -0,0 +1,15 @@
package com.diboot.shiro.mapper;
import com.diboot.core.mapper.BaseCrudMapper;
import com.diboot.shiro.entity.RolePermission;
/**
* 角色授权Mapper
* @author Yangzhao
* @version v2.0
* @date 2019/6/6
*/
public interface RolePermissionMapper extends BaseCrudMapper<RolePermission> {
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "./mybatis-3-mapper.dtd">
<mapper namespace="com.diboot.shiro.mapper.RolePermissionMapper">
</mapper>

View File

@ -0,0 +1,15 @@
package com.diboot.shiro.mapper;
import com.diboot.core.mapper.BaseCrudMapper;
import com.diboot.shiro.entity.SysUser;
/**
* 用户Mapper
* @author Yangzhao
* @version v2.0
* @date 2019/6/6
*/
public interface SysUserMapper extends BaseCrudMapper<SysUser> {
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "./mybatis-3-mapper.dtd">
<mapper namespace="com.diboot.shiro.mapper.SysUserMapper">
</mapper>

View File

@ -0,0 +1,15 @@
package com.diboot.shiro.mapper;
import com.diboot.core.mapper.BaseCrudMapper;
import com.diboot.shiro.entity.UserRole;
/**
* 用户角色Mapper
* @author Yangzhao
* @version v2.0
* @date 2019/6/6
*/
public interface UserRoleMapper extends BaseCrudMapper<UserRole> {
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "./mybatis-3-mapper.dtd">
<mapper namespace="com.diboot.shiro.mapper.UserRoleMapper">
</mapper>

View File

@ -0,0 +1,56 @@
package com.diboot.shiro.service;
import com.diboot.core.entity.BaseEntity;
import com.diboot.shiro.jwt.BaseJwtAuthenticationToken;
import com.diboot.shiro.config.AuthType;
/***
* 认证方式接口
* @author Yangzhao
* @version v2.0
* @date 2019/6/6
*/
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();
}

View File

@ -0,0 +1,14 @@
package com.diboot.shiro.service;
import com.diboot.core.service.BaseService;
import com.diboot.shiro.entity.Permission;
/**
* 许可授权相关Service
* @author Yangzhao
* @version v2.0
* @date 2019/6/6
*/
public interface PermissionService extends BaseService<Permission> {
}

View File

@ -0,0 +1,14 @@
package com.diboot.shiro.service;
import com.diboot.core.service.BaseService;
import com.diboot.shiro.entity.RolePermission;
/**
* 角色授权相关Service
* @author Yangzhao
* @version v2.0
* @date 2019/6/6
*/
public interface RolePermissionService extends BaseService<RolePermission> {
}

View File

@ -0,0 +1,26 @@
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
* @author Yangzhao
* @version v2.0
* @date 2019/6/6
*/
public interface RoleService extends BaseService<Role> {
/***
* 根据用户类型和用户id获取角色关联权限列表
* @param userType
* @param userId
* @return
*/
List<RoleVO> getRelatedRoleAndPermissionListByUser(String userType, Long userId);
}

View File

@ -0,0 +1,14 @@
package com.diboot.shiro.service;
import com.diboot.core.service.BaseService;
import com.diboot.shiro.entity.SysUser;
/**
* 用户相关Service
* @author Yangzhao
* @version v2.0
* @date 2019/6/6
*/
public interface SysUserService extends BaseService<SysUser> {
}

View File

@ -0,0 +1,14 @@
package com.diboot.shiro.service;
import com.diboot.core.service.BaseService;
import com.diboot.shiro.entity.UserRole;
/**
* 用户角色Service
* @author Yangzhao
* @version v2.0
* @date 2019/6/6
*/
public interface UserRoleService extends BaseService<UserRole> {
}

View File

@ -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<PermissionMapper, Permission> implements PermissionService {
}

View File

@ -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 Yangzhao
* @version v2.0
* @date 2019/6/6
*/
@Service
@Slf4j
public class RolePermissionServiceImpl extends BaseServiceImpl<RolePermissionMapper, RolePermission> implements RolePermissionService {
}

View File

@ -0,0 +1,62 @@
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
* @version v2.0
* @date 2019/6/6
*/
@Service
@Slf4j
public class RoleServiceImpl extends BaseServiceImpl<RoleMapper, Role> implements RoleService {
@Autowired
private UserRoleService userRoleService;
@Override
public List<RoleVO> getRelatedRoleAndPermissionListByUser(String userType, Long userId) {
// 根据用户类型与用户id获取roleList
QueryWrapper<UserRole> query = new QueryWrapper<>();
query.lambda()
.eq(UserRole::getUserType, userType)
.eq(UserRole::getUserId, userId);
List<UserRole> userRoleList = userRoleService.getEntityList(query);
if (V.isEmpty(userRoleList)){
return Collections.emptyList();
}
List<Long> roleIdList = userRoleList.stream()
.map(UserRole::getRoleId)
.collect(Collectors.toList());
if (V.isEmpty(roleIdList)){
return Collections.emptyList();
}
// 获取角色列表并使用VO自动多对多关联permission
QueryWrapper<Role> roleQuery = new QueryWrapper<>();
roleQuery
.lambda()
.in(Role::getId, roleIdList);
List<RoleVO> roleVOList = this.getViewObjectList(roleQuery, null, RoleVO.class);
return roleVOList;
}
}

View File

@ -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 Yangzhao
* @version v2.0
* @date 2019/6/6
*/
@Service
@Slf4j
public class SysUserServiceImpl extends BaseServiceImpl<SysUserMapper, SysUser> implements SysUserService {
}

View File

@ -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 Yangzhao
* @version v2.0
* @date 2019/6/6
*/
@Service
@Slf4j
public class UserRoleServiceImpl extends BaseServiceImpl<UserRoleMapper, UserRole> implements UserRoleService {
}

View File

@ -0,0 +1,84 @@
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.V;
import com.diboot.shiro.jwt.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;
/***
* 用户名密码认证实现
* @author Yangzhao
* @version v2.0
* @date 2019/6/6
*/
@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<SysUser> query = new QueryWrapper();
query.lambda()
.eq(SysUser::getUsername, token.getAccount());
List<SysUser> 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 = token.getPassword();
// 构建查询条件
QueryWrapper<SysUser> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda()
.eq(SysUser::getUsername, token.getAccount())
.eq(SysUser::getPassword, password);
// 获取单条用户记录
List<SysUser> userList = sysUserService.getEntityList(queryWrapper);
return V.notEmpty(userList);
}
@Override
public boolean isPreliminaryVerified() {
return false;
}
@Override
public Long getExpiresInMinutes() {
return null;
}
}

View File

@ -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 extends BaseEntity>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;
}
}

View File

@ -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 Yangzhao
* @version v2.0
* @date 2019/6/6
*/
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));
}
}

View File

@ -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;
}

View File

@ -0,0 +1,25 @@
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;
import java.util.Set;
/**
* @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<Permission> permissionList;
}

Binary file not shown.

View File

@ -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

View File

@ -10,4 +10,6 @@
rootProject.name = 'diboot-v2'
include 'diboot-core'
include 'diboot-example'
include 'diboot-shiro'
include 'diboot-docs'