springMVC springBoot shiro 自定义shiro授权注解,自定义授权解析器

it2025-08-23  3

  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> { }

  这样可在父类创建通用的对外接口,以及接口的基础访问权限。子类只需规定业务类别即可。有自定义的业务可重写相应的增强方法。

最新回复(0)