shiro支持注解式的授权控制,共有5个:
- @RequiresAuthentication:
当前 Subject 已经通过 login 进行了身份验证;即 Subject.isAuthenticated() 返回 true。 - @RequiresUser:
当前 Subject 已经身份验证或者通过记住我登录的。 - @RequiresGuest:
当前 Subject 没有身份验证或通过记住我登录过,即是游客身份。 - @RequiresRoles:
当前 Subject 需要的角色。 - @RequiresPermissions:
当前 Subject 需要的权限
但这些不满足当前的一些需求。父controller有方法权限的通用规则(CRUD),子类规定具体业务(的CRUD),即类似**@RequestMapping**的拼接。查看shiro源码看看他是如何实现注解解析的,决定自行解决
/**
* 自定义shiro授权注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DefaultPermission {
String value();
}
/**
* 自定义MVC注解
* 用以配合自定义shiro注解使用(@DefaultPermission )
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RestController
@RequestMapping
public @interface MController {
@AliasFor(annotation = RequestMapping.class)
String[] value() default {};
//排除需要生成的API,加上后该业务将不再生成其中的API。
DefaultMethod[] excludeMethod() default {};
//排除需要授权的API,加上后该方法将不再需要授权。
DefaultMethod[] excludePermission() default {};
}
/**
* controller的默认对外API方法名
*/
public enum DefaultMethod {
ALL("all"),
PAGE("defaultPage"),
LIST("defaultList"),
DETAIL("defaultDetail"),
UPDATE("defaultUpdate"),
DEL("defaultDel"),
DELS("defaultDels"),
DELETE("defaultDelete"),
DELETES("defaultDeletes");
private String methodName;
DefaultMethod(String methodName) {
this.methodName=methodName;
}
public String getMethodName(){
return this.methodName;
}
public static DefaultMethod getEnum(String methodName){
for(DefaultMethod enm:DefaultMethod.values()){
if (enm.getMethodName().equals(methodName))return enm;
}
return null;
}
}
public class BaseController<T extends BaseService,M extends BaseModel> {
@Autowired
protected T service;
/**
* 默认对外接口
* @param modelMap
* @param param
* @return
*/
@ApiOperation("分页查询")
@GetMapping("/page")
@DefaultPermission(":read")
public Object defaultPage(ModelMap modelMap, @RequestParam Map<String, Object> param) {
//业务增强,子类注入的service可重写【beforePage】方法实现查询参数的处理功能
service.beforePage(param);
//同样可重写【pageHandler】方法,可对查询后的结果进行增强处理。
//【getPage】方法传入两个参数,第一个为查询条件;第二个为【Consumer】可遍历处理查询后的结果集
return setSuccessModelMap(modelMap, service.getPage(param,service.pageHandler()));
}
... ...
}
/**
* 自定义shiro注解授权处理类
*/
public class DefaultAuthorizationAttributeSourceAdvisor extends AuthorizationAttributeSourceAdvisor{
private static final Class<? extends Annotation>[] AUTHZ_ANNOTATION_CLASSES =
new Class[] {
//注入自定义注解
DefaultPermission.class,
RequiresPermissions.class, RequiresRoles.class,
RequiresUser.class, RequiresGuest.class, RequiresAuthentication.class
};
public DefaultAuthorizationAttributeSourceAdvisor() {
setAdvice(new DefaultPermissionAnnotationAopInterceptor());
}
@Override
public boolean matches(Method method, Class targetClass) {
Method m = method;
if ( isAuthzAnnotationPresent(m) ) {
return true;
}
if ( targetClass != null) {
try {
m = targetClass.getMethod(m.getName(), m.getParameterTypes());
return isAuthzAnnotationPresent(m) || isAuthzAnnotationPresent(targetClass);
} catch (NoSuchMethodException ignored) {
}
}
return false;
}
private boolean isAuthzAnnotationPresent(Class<?> targetClazz) {
for( Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES ) {
Annotation a = AnnotationUtils.findAnnotation(targetClazz, annClass);
if ( a != null ) {
return true;
}
}
return false;
}
private boolean isAuthzAnnotationPresent(Method method) {
for( Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES ) {
Annotation a = AnnotationUtils.findAnnotation(method, annClass);
if ( a != null ) {
return true;
}
}
return false;
}
}
/**
* 自定义shiro的AOP拦截器
* 用以注入自定义的授权拦截器
*/
public class DefaultPermissionAnnotationAopInterceptor extends AopAllianceAnnotationsAuthorizingMethodInterceptor {
public DefaultPermissionAnnotationAopInterceptor() {
//注入原注解授权拦截器
super();
//注入自定义注解授权拦截器
this.methodInterceptors.add(new DefaultPermissionAnnotationMethodInterceptor());
}
}
/**
* 自定义注解授权拦截器
*/
public class DefaultPermissionAnnotationMethodInterceptor extends AuthorizingAnnotationMethodInterceptor {
public DefaultPermissionAnnotationMethodInterceptor() {
//注入自定义注解授权处理器
super(new DefaultPermissionAnnotationHandler());
}
public void assertAuthorized(MethodInvocation mi) throws AuthorizationException {
try {
//因为需要方法所在的类,就直接在拦截器处理了授权认证了
//自定义注解授权处理逻辑
Annotation typeAnnotation=getAnnotation(mi);
if (!(typeAnnotation instanceof DefaultPermission)) return;
MController annotation= mi.getThis().getClass().getAnnotation(MController.class);
if(annotation!=null){
String method=mi.getMethod().getName();
List<DefaultMethod> excludePermission=Arrays.asList(annotation.excludePermission());
if(excludePermission.contains(DefaultMethod.ALL))return;
if(!excludePermission.contains(DefaultMethod.getEnum(method))){
DefaultPermission a=(DefaultPermission)typeAnnotation;
String base= Stream.of(annotation.value()).collect(Collectors.joining()).substring(1);
((DefaultPermissionAnnotationHandler)getHandler()).assertAuthorized(base+a.value());
}
}
}catch(AuthorizationException ae) {
if (ae.getCause() == null) ae.initCause(new AuthorizationException("Not authorized to invoke method: " + mi.getMethod()));
throw ae;
}
}
}
/**
* 自定义注解授权处理器
* 授权逻辑已在拦截器,这里直接复制父类逻辑即可
*/
public class DefaultPermissionAnnotationHandler extends AuthorizingAnnotationHandler{
public DefaultPermissionAnnotationHandler() {
super(DefaultPermission.class);
}
public void assertAuthorized(String permission) throws AuthorizationException {
this.getSubject().checkPermission(permission);
}
@Override
public void assertAuthorized(Annotation a) throws AuthorizationException {
}
}
//shiro启用注解授权,并注入自定义的注解授权处理类
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new DefaultAuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* 案例controller
* 会自动生成”/address/page“API,并且访问该API需要”address:read“权限
*/
@MController("/address")
public class AddressController extends BaseController<AddressService, Address> {
}
这样可在父类创建通用的对外接口,以及接口的基础访问权限。子类只需规定业务类别即可。有自定义的业务可重写相应的增强方法。