diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 595d6c7..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore index 368b438..ac0bd80 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ application-*.properties build/ out/ /.idea/ +gradle/ \ No newline at end of file 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 diff --git a/diboot-core/src/main/java/com/diboot/core/mapper/BaseCrudMapper.java b/diboot-core/src/main/java/com/diboot/core/mapper/BaseCrudMapper.java index 6f66a21..635856b 100644 --- a/diboot-core/src/main/java/com/diboot/core/mapper/BaseCrudMapper.java +++ b/diboot-core/src/main/java/com/diboot/core/mapper/BaseCrudMapper.java @@ -5,8 +5,8 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; /** * 基础CRUD的父类Mapper * @author Mazhicheng - * @version 2018/12/22 - * Copyright © www.dibo.ltd + * @version v2.0 + * @date 2018/12/22 */ public interface BaseCrudMapper extends BaseMapper { diff --git a/diboot-core/src/main/java/com/diboot/core/mapper/MetadataMapper.java b/diboot-core/src/main/java/com/diboot/core/mapper/MetadataMapper.java index 41bca4d..17c0c25 100644 --- a/diboot-core/src/main/java/com/diboot/core/mapper/MetadataMapper.java +++ b/diboot-core/src/main/java/com/diboot/core/mapper/MetadataMapper.java @@ -5,8 +5,8 @@ import com.diboot.core.entity.Metadata; /** * 元数据Mapper * @author Mazhicheng - * @version 2018/12/22 - * Copyright © www.dibo.ltd + * @version v2.0 + * @date 2018/12/22 */ public interface MetadataMapper extends BaseCrudMapper { diff --git a/diboot-core/src/main/java/com/diboot/core/util/V.java b/diboot-core/src/main/java/com/diboot/core/util/V.java index 91b0a22..91493f4 100644 --- a/diboot-core/src/main/java/com/diboot/core/util/V.java +++ b/diboot-core/src/main/java/com/diboot/core/util/V.java @@ -22,7 +22,21 @@ public class V { * @return */ public static boolean isEmpty(Object obj){ - return obj == null; + if(obj instanceof String){ + return isEmpty((String)obj); + } + else if(obj instanceof Collection){ + return isEmpty((Collection)obj); + } + else if(obj instanceof Map){ + return isEmpty((Map)obj); + } + else if(obj instanceof String[]){ + return isEmpty((String[])obj); + } + else{ + return obj == null; + } } /*** @@ -67,7 +81,21 @@ public class V { * @return */ public static boolean notEmpty(Object obj){ - return obj != null; + if(obj instanceof String){ + return notEmpty((String)obj); + } + else if(obj instanceof Collection){ + return notEmpty((Collection)obj); + } + else if(obj instanceof Map){ + return notEmpty((Map)obj); + } + else if(obj instanceof String[]){ + return notEmpty((String[])obj); + } + else{ + return obj != null; + } } /*** 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 @@ + __ _ __ __ + ____/ / (_) / /_ ____ ____ / /_ + / __ / / / / __ \ / __ \ / __ \ / __/ +/ /_/ / / / / /_/ / / /_/ / / /_/ / / /_ +\__,_/ /_/ /_.___/ \____/ \____/ \__/ 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/diboot-shiro/src/main/java/com/diboot/shiro/bind/annotation/PermissionsPrefix.java b/diboot-shiro/src/main/java/com/diboot/shiro/bind/annotation/PermissionsPrefix.java new file mode 100644 index 0000000..1ec6540 --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/bind/annotation/PermissionsPrefix.java @@ -0,0 +1,45 @@ +package com.diboot.shiro.bind.annotation; + +import java.lang.annotation.*; + +/** + * 权限注解的前缀,用于controller注解 + * + * @author : wee + * @version v 2.0 + * @Date 2019-06-17 20:42 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface PermissionsPrefix { + + /** + * 名称 + * + * 设置当前权限前缀名称 + * @return + */ + String name(); + + /** + * 编码 + * + * 设置当前权限前缀编码 + * @return + */ + String code(); + + /** + *

{@link RequiresPermissionsWrapper#value()}的前缀

+ *
    + *
  • value = permissions
  • + *
  • {@link RequiresPermissionsWrapper#value()} = {"list", "get"}
  • + *
  • 实际权限为:{"permissions:list", "permissions:list"}
  • + *
+ * 注:当前注解优先级低于{@link RequiresPermissionsWrapper#prefix()}, + * 如果两者都配置优先使用{@link RequiresPermissionsWrapper#prefix()} + * @return + */ + String prefix() default ""; +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/bind/annotation/RequiresPermissionsWrapper.java b/diboot-shiro/src/main/java/com/diboot/shiro/bind/annotation/RequiresPermissionsWrapper.java new file mode 100644 index 0000000..9ad689c --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/bind/annotation/RequiresPermissionsWrapper.java @@ -0,0 +1,44 @@ +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}) +public @interface RequiresPermissionsWrapper { + + /** + * 包装 {@link RequiresPermissions#value()} + */ + String[] value(); + + /** + * 包装 {@link RequiresPermissions#logical()} + */ + Logical logical() default Logical.AND; + + /** + * 权限名称 + * @return + */ + String name(); + + /** + * 参照{@link PermissionsPrefix#prefix()}解释 + * @return + */ + String prefix() default ""; + + + + +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/bind/aop/CustomAopAllianceAnnotationsAuthorizingMethodInterceptor.java b/diboot-shiro/src/main/java/com/diboot/shiro/bind/aop/CustomAopAllianceAnnotationsAuthorizingMethodInterceptor.java new file mode 100644 index 0000000..929d5ef --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/bind/aop/CustomAopAllianceAnnotationsAuthorizingMethodInterceptor.java @@ -0,0 +1,100 @@ +package com.diboot.shiro.bind.aop; + +import com.diboot.shiro.bind.aop.PermissionWrapperAnnotationMethodInterceptor; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.apache.shiro.aop.AnnotationResolver; +import org.apache.shiro.authz.aop.*; +import org.apache.shiro.spring.aop.SpringAnnotationResolver; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +/** + * @author : wee + * @version : v2.0 + * @Date 2019-06-15 12:07 + */ +public class CustomAopAllianceAnnotationsAuthorizingMethodInterceptor extends AnnotationsAuthorizingMethodInterceptor implements MethodInterceptor { + public CustomAopAllianceAnnotationsAuthorizingMethodInterceptor() { + List interceptors = + new ArrayList(6); + AnnotationResolver resolver = new SpringAnnotationResolver(); + interceptors.add(new PermissionWrapperAnnotationMethodInterceptor(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)); + + setMethodInterceptors(interceptors); + } + /** + * Creates a {@link MethodInvocation MethodInvocation} that wraps an + * {@link org.aopalliance.intercept.MethodInvocation org.aopalliance.intercept.MethodInvocation} instance, + * enabling Shiro Annotations in AOP Alliance environments + * (Spring, etc). + * + * @param implSpecificMethodInvocation AOP Alliance {@link org.aopalliance.intercept.MethodInvocation MethodInvocation} + * @return a Shiro {@link MethodInvocation MethodInvocation} instance that wraps the AOP Alliance instance. + */ + protected org.apache.shiro.aop.MethodInvocation createMethodInvocation(Object implSpecificMethodInvocation) { + final MethodInvocation mi = (MethodInvocation) implSpecificMethodInvocation; + + return new org.apache.shiro.aop.MethodInvocation() { + @Override + public Method getMethod() { + return mi.getMethod(); + } + + @Override + public Object[] getArguments() { + return mi.getArguments(); + } + + @Override + public String toString() { + return "Method invocation [" + mi.getMethod() + "]"; + } + + @Override + public Object proceed() throws Throwable { + return mi.proceed(); + } + + @Override + public Object getThis() { + return mi.getThis(); + } + }; + } + + /** + * Simply casts the method argument to an + * {@link org.aopalliance.intercept.MethodInvocation org.aopalliance.intercept.MethodInvocation} and then + * calls methodInvocation.{@link org.aopalliance.intercept.MethodInvocation#proceed proceed}() + * + * @param aopAllianceMethodInvocation the {@link org.aopalliance.intercept.MethodInvocation org.aopalliance.intercept.MethodInvocation} + * @return the {@link org.aopalliance.intercept.MethodInvocation#proceed() org.aopalliance.intercept.MethodInvocation.proceed()} method call result. + * @throws Throwable if the underlying AOP Alliance proceed() call throws a Throwable. + */ + protected Object continueInvocation(Object aopAllianceMethodInvocation) throws Throwable { + MethodInvocation mi = (MethodInvocation) aopAllianceMethodInvocation; + return mi.proceed(); + } + + /** + * Creates a Shiro {@link MethodInvocation MethodInvocation} instance and then immediately calls + * {@link org.apache.shiro.authz.aop.AuthorizingMethodInterceptor#invoke super.invoke}. + * + * @param methodInvocation the AOP Alliance-specific methodInvocation instance. + * @return the return value from invoking the method invocation. + * @throws Throwable if the underlying AOP Alliance method invocation throws a Throwable. + */ + @Override + public Object invoke(MethodInvocation methodInvocation) throws Throwable { + org.apache.shiro.aop.MethodInvocation mi = createMethodInvocation(methodInvocation); + return super.invoke(mi); + } +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/bind/aop/CustomAuthorizationAttributeSourceAdvisor.java b/diboot-shiro/src/main/java/com/diboot/shiro/bind/aop/CustomAuthorizationAttributeSourceAdvisor.java new file mode 100644 index 0000000..86c32f7 --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/bind/aop/CustomAuthorizationAttributeSourceAdvisor.java @@ -0,0 +1,106 @@ +package com.diboot.shiro.bind.aop; + +import com.diboot.shiro.bind.annotation.RequiresPermissionsWrapper; +import org.apache.shiro.authz.annotation.*; +import org.apache.shiro.mgt.SecurityManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor; +import org.springframework.core.annotation.AnnotationUtils; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +/** + * @author : wee + * @version : v1.0 + * @Date 2019-06-15 12:27 + */ +public class CustomAuthorizationAttributeSourceAdvisor extends StaticMethodMatcherPointcutAdvisor { + + private static final Logger log = LoggerFactory.getLogger(CustomAuthorizationAttributeSourceAdvisor.class); + + private static final Class[] AUTHZ_ANNOTATION_CLASSES = + new Class[] { + RequiresPermissionsWrapper.class, + RequiresPermissions.class, RequiresRoles.class, + RequiresUser.class, RequiresGuest.class, RequiresAuthentication.class + }; + + protected SecurityManager securityManager = null; + + /** + * Create a new AuthorizationAttributeSourceAdvisor. + */ + public CustomAuthorizationAttributeSourceAdvisor() { + setAdvice(new CustomAopAllianceAnnotationsAuthorizingMethodInterceptor()); + } + + public SecurityManager getSecurityManager() { + return securityManager; + } + + public void setSecurityManager(org.apache.shiro.mgt.SecurityManager securityManager) { + this.securityManager = securityManager; + } + + /** + * Returns true if the method or the class has any Shiro annotations, false otherwise. + * The annotations inspected are: + *
    + *
  • {@link RequiresPermissionsWrapper RequiresPermissionsWrapper}
  • + *
  • {@link org.apache.shiro.authz.annotation.RequiresAuthentication RequiresAuthentication}
  • + *
  • {@link org.apache.shiro.authz.annotation.RequiresUser RequiresUser}
  • + *
  • {@link org.apache.shiro.authz.annotation.RequiresGuest RequiresGuest}
  • + *
  • {@link org.apache.shiro.authz.annotation.RequiresRoles RequiresRoles}
  • + *
  • {@link org.apache.shiro.authz.annotation.RequiresPermissions RequiresPermissions}
  • + *
+ * + * @param method the method to check for a Shiro annotation + * @param targetClass the class potentially declaring Shiro annotations + * @return true if the method has a Shiro annotation, false otherwise. + * @see org.springframework.aop.MethodMatcher#matches(java.lang.reflect.Method, Class) + */ + @Override + public boolean matches(Method method, Class targetClass) { + Method m = method; + + if ( isAuthzAnnotationPresent(m) ) { + return true; + } + + //The 'method' parameter could be from an interface that doesn't have the annotation. + //Check to see if the implementation has it. + if ( targetClass != null) { + try { + m = targetClass.getMethod(m.getName(), m.getParameterTypes()); + return isAuthzAnnotationPresent(m) || isAuthzAnnotationPresent(targetClass); + } catch (NoSuchMethodException ignored) { + //default return value is false. If we can't find the method, then obviously + //there is no annotation, so just use the default return value. + } + } + + return false; + } + + private boolean isAuthzAnnotationPresent(Class targetClazz) { + for( Class annClass : AUTHZ_ANNOTATION_CLASSES ) { + Annotation a = AnnotationUtils.findAnnotation(targetClazz, annClass); + if ( a != null ) { + return true; + } + } + return false; + } + + private boolean isAuthzAnnotationPresent(Method method) { + for( Class annClass : AUTHZ_ANNOTATION_CLASSES ) { + Annotation a = AnnotationUtils.findAnnotation(method, annClass); + if ( a != null ) { + return true; + } + } + return false; + } +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/bind/aop/PermissionWrapperAnnotationMethodInterceptor.java b/diboot-shiro/src/main/java/com/diboot/shiro/bind/aop/PermissionWrapperAnnotationMethodInterceptor.java new file mode 100644 index 0000000..1a2ba4d --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/bind/aop/PermissionWrapperAnnotationMethodInterceptor.java @@ -0,0 +1,54 @@ +package com.diboot.shiro.bind.aop; + +import com.diboot.shiro.bind.annotation.RequiresPermissionsWrapper; +import com.diboot.shiro.bind.handler.PermissionWrapperAnnotationHandler; +import org.apache.shiro.aop.AnnotationResolver; +import org.apache.shiro.aop.MethodInvocation; +import org.apache.shiro.authz.AuthorizationException; +import org.apache.shiro.authz.aop.AuthorizingAnnotationHandler; +import org.apache.shiro.authz.aop.AuthorizingAnnotationMethodInterceptor; + +/** + * {@link RequiresPermissionsWrapper} 拦截器 + * @author : wee + * @version : v2.0 + * @Date 2019-06-14 22:19 + */ +public class PermissionWrapperAnnotationMethodInterceptor 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 PermissionWrapperAnnotationMethodInterceptor() { + super( new PermissionWrapperAnnotationHandler() ); + } + + /** + * @param resolver + * @since 1.1 + */ + public PermissionWrapperAnnotationMethodInterceptor(AnnotationResolver resolver) { + super( new PermissionWrapperAnnotationHandler(), resolver); + } + + /** + * 当使用RequiresPermissionsWrapper注解进行权限验证的时候,自动的去追加前缀 + * @param mi + * @throws AuthorizationException + */ + @Override + public void assertAuthorized(MethodInvocation mi) throws AuthorizationException { + try { + + //默认是直接调用方法上注解,现在修改成 获取类和方法上的注解 +// ((AuthorizingAnnotationHandler)getHandler()).assertAuthorized(getAnnotation(mi)); + ((PermissionWrapperAnnotationHandler)getHandler()).assertAuthorized(getResolver(), mi); + } + catch(AuthorizationException ae) { + if (ae.getCause() == null) { + ae.initCause(new AuthorizationException("Not authorized to invoke method: " + mi.getMethod())); + } + throw ae; + } + } +} diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/bind/handler/PermissionWrapperAnnotationHandler.java b/diboot-shiro/src/main/java/com/diboot/shiro/bind/handler/PermissionWrapperAnnotationHandler.java new file mode 100644 index 0000000..7478dcf --- /dev/null +++ b/diboot-shiro/src/main/java/com/diboot/shiro/bind/handler/PermissionWrapperAnnotationHandler.java @@ -0,0 +1,164 @@ +package com.diboot.shiro.bind.handler; + +import com.diboot.core.util.S; +import com.diboot.core.util.V; +import com.diboot.shiro.bind.annotation.PermissionsPrefix; +import com.diboot.shiro.bind.annotation.RequiresPermissionsWrapper; +import org.apache.shiro.aop.AnnotationResolver; +import org.apache.shiro.aop.MethodInvocation; +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.aop.AuthorizingAnnotationHandler; +import org.apache.shiro.authz.aop.PermissionAnnotationHandler; +import org.apache.shiro.subject.Subject; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; +import java.util.Map; + +/** + * {@link RequiresPermissionsWrapper} 助手类, 参考{@link PermissionAnnotationHandler}实现 + * @author : wee + * @version : v2.0 + * @Date 2019-06-14 22:19 + */ +public class PermissionWrapperAnnotationHandler 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 PermissionWrapperAnnotationHandler() { + super(RequiresPermissionsWrapper.class); + } + + + /** + * Returns the annotation {@link RequiresPermissions#value value}, from which the Permission will be constructed. + * + * @param a the RequiresPermissions annotation being inspected. + * @return the annotation's value, from which the Permission will be constructed. + */ + protected String[] getAnnotationValue(Annotation a) { + RequiresPermissionsWrapper rpAnnotation = (RequiresPermissionsWrapper) a; + return rpAnnotation.value(); + } + + /** + * 校验注解{@link RequiresPermissionsWrapper} + */ + @Override + public void assertAuthorized(Annotation a) throws AuthorizationException { + if (!(a instanceof RequiresPermissionsWrapper)) { + return; + } + RequiresPermissionsWrapper rppAnnotation = (RequiresPermissionsWrapper) a; + String[] perms = getAnnotationValue(a); + Subject subject = getSubject(); + + if (perms.length == 1) { + subject.checkPermission(perms[0]); + return; + } + if (Logical.AND.equals(rppAnnotation.logical())) { + getSubject().checkPermissions(perms); + return; + } + if (Logical.OR.equals(rppAnnotation.logical())) { + boolean hasAtLeastOnePermission = false; + for (String permission : perms) { + if (getSubject().isPermitted(permission)) { + hasAtLeastOnePermission = true; + } + } + if (!hasAtLeastOnePermission) { + getSubject().checkPermission(perms[0]); + } + } + } + + /** + * 校验注解{@link RequiresPermissionsWrapper} + */ + public void assertAuthorized(AnnotationResolver resolver, MethodInvocation mi) throws AuthorizationException { + //如果方法上存在RequiresPermissionsWrapper注解,那么resolver.getAnnotation()获取的是RequiresPermissionsWrapper注解 + //优先从缓存读取 + RequiresPermissionsWrapper requiresPermissionsWrapper = (RequiresPermissionsWrapper)resolver.getAnnotation(mi, RequiresPermissionsWrapper.class); + String prefix = ""; + if (V.notEmpty(requiresPermissionsWrapper.prefix())) { + prefix = requiresPermissionsWrapper.prefix(); + } else { + //如果自身不定义,查找前缀注解,存在则设置值 + PermissionsPrefix permissionsPrefix = (PermissionsPrefix)resolver.getAnnotation(mi, PermissionsPrefix.class); + if (V.notEmpty(permissionsPrefix)) { + prefix = permissionsPrefix.prefix(); + } + } + + String[] perms = getAnnotationValue(requiresPermissionsWrapper); + Subject subject = getSubject(); + + String [] permsTemp = new String[perms.length]; + //前缀存在的时候,才做组装,其他情况不处理 + if (V.notEmpty(prefix)) { + for (int i = 0; i < perms.length; i++) { + permsTemp[i] = S.join(prefix, ":", perms[i]); + } + perms = permsTemp; + } + + if (perms.length == 1) { + subject.checkPermission(perms[0]); + return; + } + if (Logical.AND.equals(requiresPermissionsWrapper.logical())) { + getSubject().checkPermissions(perms); + return; + } + if (Logical.OR.equals(requiresPermissionsWrapper.logical())) { + boolean hasAtLeastOnePermission = false; + for (String permission : perms) { + if (getSubject().isPermitted(permission)) { + hasAtLeastOnePermission = true; + } + } + if (!hasAtLeastOnePermission) { + getSubject().checkPermission(perms[0]); + } + } + } + + /** + * 动态修改注解的值(不可用) + * @param rppAnnotation + */ + @Deprecated + private void proxy(RequiresPermissionsWrapper rppAnnotation) { + try { + //获取RequiresPermissionsProxy上的RequiresPermissions注解 + RequiresPermissions requiresPermissions = rppAnnotation.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, rppAnnotation.value()); + memberValues.put(REQUIRES_PERMISSIONS_LOGICAL, rppAnnotation.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 6eeac91..634c0e3 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,11 +1,11 @@ package com.diboot.shiro.config; +import com.diboot.shiro.bind.aop.CustomAuthorizationAttributeSourceAdvisor; 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; -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; @@ -101,8 +101,8 @@ public class ShiroConfig { } @Bean - public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() { - AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); + public CustomAuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() { + CustomAuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new CustomAuthorizationAttributeSourceAdvisor(); 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..7bdd498 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.PermissionsPrefix; +import com.diboot.shiro.bind.annotation.RequiresPermissionsWrapper; import com.diboot.shiro.entity.Permission; import com.diboot.shiro.service.PermissionService; import com.diboot.shiro.vo.PermissionVO; @@ -29,6 +30,7 @@ import java.util.List; * Copyright © www.dibo.ltd */ @RestController +@PermissionsPrefix(prefix = "permission", code = "permission", name = "权限") @RequestMapping("/permission") public class PermissionController extends BaseCrudRestController { @@ -37,6 +39,20 @@ public class PermissionController extends BaseCrudRestController { @Autowired private PermissionService permissionService; + /*** + * 查询Entity + * @param id ID + * @return + * @throws Exception + */ + @GetMapping("/{id}") + @RequiresPermissionsWrapper(prefix = "permissionSelf", value = {"get"}, name = "查看") + public JsonResult getModel(@PathVariable("id")Long id, HttpServletRequest request, ModelMap modelMap) + throws Exception{ + PermissionVO vo = permissionService.getViewObject(id, PermissionVO.class); + return new JsonResult(vo); + } + /*** * 查询ViewObject的分页数据 (此为非继承的自定义使用案例,更简化的调用父类案例请参考UserController) *

@@ -45,8 +61,8 @@ public class PermissionController extends BaseCrudRestController { * @return * @throws Exception */ - @RequiresPermissions("permission:list") @GetMapping("/list") + @RequiresPermissionsWrapper(value = {"list"}, name = "列表") public JsonResult getVOList(HttpServletRequest request) throws Exception{ QueryWrapper queryWrapper = buildQuery(request); // 构建分页 @@ -74,20 +90,6 @@ public class PermissionController extends BaseCrudRestController { 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 diff --git a/diboot-shiro/src/main/java/com/diboot/shiro/jwt/BaseJwtAuthenticationFilter.java b/diboot-shiro/src/main/java/com/diboot/shiro/jwt/BaseJwtAuthenticationFilter.java index 2e53855..dd5447a 100644 --- a/diboot-shiro/src/main/java/com/diboot/shiro/jwt/BaseJwtAuthenticationFilter.java +++ b/diboot-shiro/src/main/java/com/diboot/shiro/jwt/BaseJwtAuthenticationFilter.java @@ -56,6 +56,8 @@ public class BaseJwtAuthenticationFilter extends BasicHttpAuthenticationFilter { @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { logger.debug("Token认证: onAccessDenied"); + HttpServletRequest httpRequest = (HttpServletRequest) request; + JsonResult jsonResult = new JsonResult(Status.FAIL_INVALID_TOKEN); this.responseJson((HttpServletResponse) response, jsonResult); return false; 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<>(); diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 457aad0..0000000 Binary files a/gradle/wrapper/gradle-wrapper.jar and /dev/null differ 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