springboot通用分支處理超級管理員權(quán)限邏輯
前言
當(dāng)引入登錄模塊后我們需要做菜單。而菜單自然需要權(quán)限的參與,我們在springboot中設(shè)計(jì)的權(quán)限細(xì)粒度還算是比較細(xì)的。當(dāng)我們查詢菜單是需要根據(jù)權(quán)限查找對應(yīng)的菜單。但是在springboot中我設(shè)計(jì)了一個底層超級管理員
- 先來看看我一開始實(shí)現(xiàn)這個超級管理員菜單獲取的部分代碼
if (SecurityUtils.getSubject().hasRole(RoleList.SUPERADMIN)) {
listemp = customMapper.selectRootMenusByRoleIdList(null,null, null, null);
} else {
listemp = customMapper.selectRootMenusByRoleIdList(roleList, oauthClientId,null, moduleCodes);
}
這樣實(shí)現(xiàn)是很正常的思路,通過判斷角色是否是超級管理員來做分支執(zhí)行思路,但是超級管理員可能涉及到多個地方如果在每個地方都這樣if else執(zhí)行的,我覺得有點(diǎn)low, 所以我決定改造一下。不夠最終執(zhí)行的思路依然是if else判斷 。 只不過讓我們在代碼層面上功能間不在那么雜糅在一起
自定義注解
首先我需要兩個注解,SuperDirection和SuperDirectionHandler分別表示需要判斷超級管理員分支和具體管理員分支的目標(biāo)函數(shù) 。 這句話說的還是有點(diǎn)抽象的,容我慢慢道來!
SuperDirection
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SuperDirection {
String value() default StringUtils.EMPTY;
}
SuperDirectionHandler
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface SuperDirectionHandler {
}
作用
SuperDirection是用于表明該方法需要進(jìn)行判斷超級管理員,而value值存儲的就是判斷的表達(dá)式。關(guān)于這個表達(dá)式我們后面介紹
SuperDirectionHandler我們不難發(fā)現(xiàn)他沒有實(shí)際屬性但是多了一個@Component注解。目的是方便Spring管理該注解;這樣我們就可以通過Spring來獲取被該注解標(biāo)注的類了。
位置
該注解釋放給全局使用的,在maltcloud結(jié)構(gòu)介紹中我們知道org.framework.core模塊是所有模塊的基石,所以這兩個注解我選擇在org.framework.core模塊中

切面
- 我們想在方法執(zhí)行前進(jìn)行條件判斷,可選方案有很多我們可以在filter中攔截方法進(jìn)行判斷選擇執(zhí)行哪一個,但是過濾器中我們無法直接獲取到方法的相關(guān)信息,注意這里說的是無法直接獲取,如果你想在filter中實(shí)現(xiàn)也不是不行,這種方案感興趣的可以試試
- spring的另外一個特性切面正好符合我們的需求,我們只需要在aroud環(huán)繞方法中實(shí)現(xiàn)我們的需要。
- 首先我們定義一個切點(diǎn),切點(diǎn)攔截所有被
SuperDirection注解標(biāo)注的類或者方法。
/**定義一個切點(diǎn); 攔截所有帶有SuperDirection注解的類和方法*/
@Pointcut("@annotation(com.github.zxhtom.core.annotaion.SuperDirection) || @within(com.github.zxhtom.core.annotaion.SuperDirection)")
public void direction() {
}
- 這里稍作一下解釋
@annotation用于標(biāo)識方法上的SuperDirection,@within用于標(biāo)識在類上的SuperDirection。 - 正常我們的一個業(yè)務(wù)處理都是放在service層的,spring中的三層架構(gòu)的service正常是實(shí)現(xiàn)一個接口然后實(shí)現(xiàn)。所以我們這里在切面中先獲取被攔截對象實(shí)現(xiàn)的接口。獲取到接口信息我們通過接口信息獲取該接口在spring中的其它實(shí)現(xiàn)類
- 在spring容器中提供了獲取bean集合的方法,加上我們maltcloud中實(shí)現(xiàn)了獲取
ApplicationContext的工具類,所以我們通過如下來獲取bean集合
Map<String, ?> beansOfType = ApplicationContextUtil.getApplicationContext().getBeansOfType(接口class);
- 獲取到集合了,此時我們還是無法確定哪一個實(shí)現(xiàn)類使我們替補(bǔ)執(zhí)行超級管理員的bean 。 這就需要我們
SuperDirectionHandler注解了。 - 這個時候我們在通過Spring獲取被該注解標(biāo)識的類。這個時候獲取到很多不想關(guān)類,我們在和上面的
beansOfType進(jìn)行比對。就可以確定哪一個實(shí)現(xiàn)bean是我們需要的。
@Around("direction()")
public Object aroud(ProceedingJoinPoint pjp) throws Throwable {
Class<?>[] inters = pjp.getTarget().getClass().getInterfaces();
for (Class<?> inter : inters) {
Map<String, ?> beansOfType = ApplicationContextUtil.getApplicationContext().getBeansOfType(inter);
Map<String, Object> beansWithAnnotation = ApplicationContextUtil.getApplicationContext().getBeansWithAnnotation(SuperDirectionHandler.class);
for (Map.Entry<String, ?> entry : beansOfType.entrySet()) {
if (beansWithAnnotation.containsKey(entry.getKey())) {
try {
return doOthersHandler(entry.getValue(), pjp);
} catch (Exception e) {
log.error("分支執(zhí)行失敗,系統(tǒng)判定執(zhí)行原有分支....");
}
}
}
}
return pjp.proceed();
}
- 當(dāng)確定執(zhí)行類之后,我們只需要攜帶著該bean ,在根據(jù)
SuperDirection上的表達(dá)式進(jìn)行判斷是執(zhí)行超級管理員實(shí)現(xiàn)類還是原有實(shí)現(xiàn)類的方法了。
條件判斷
- 在Aspect執(zhí)行中原有方法的執(zhí)行很簡單,只需要
pjp.proceed()就可以了。所以這里我們先獲取一下上面獲取到的超級管理員實(shí)現(xiàn)bean的對應(yīng)方法吧。
MethodSignature msig = (MethodSignature) pjp.getSignature(); Method targetMethod = value.getClass().getDeclaredMethod(msig.getName(),msig.getParameterTypes());
- 然后我們在獲取
SuperDirection注解信息,因?yàn)樵撟⒔饪赡茉陬惿?,也可能在方法上,所以這里我們需要處理下
SuperDirection superDirection = null;
superDirection = targetMethod.getAnnotation(SuperDirection.class);
if (superDirection == null) {
superDirection = pjp.getTarget().getClass().getAnnotation(SuperDirection.class);
}
- 最終我們通過注解表達(dá)式判斷執(zhí)行情況
if(selectAnnotationChoiceDo(superDirection)){
//如果表達(dá)式驗(yàn)證通過,則執(zhí)行替補(bǔ)bean實(shí)現(xiàn)類
return targetMethod.invoke(value,pjp.getArgs());
}
//否則執(zhí)行原有bean實(shí)現(xiàn)類
return pjp.proceed();
表達(dá)式解析
- 表達(dá)式解析涉及兩個模塊,一個是登錄模塊中獲取當(dāng)前登錄用戶的角色,另外一個是我們上面提到的表達(dá)式解析
- 獲取當(dāng)前登錄用戶信息類我在
org.framework.web中提供了bean 。 具體的實(shí)現(xiàn)由各個登錄子模塊負(fù)責(zé)去實(shí)現(xiàn),這里我們只需要引入spirng bean使用就要可以了。這里充分體現(xiàn)了模塊拆分的好處了。 - 至于表達(dá)式解析,我選擇放在本模塊中
org.framework.commons。 這里目前簡單提供了幾個表達(dá)式解析。
public interface RootChoiceExpression {
public boolean haslogined();
public boolean hasRole(String role);
public boolean hasAnyRole(String... roles);
}
- 他們分別是驗(yàn)證是否登錄、是否擁有角色和角色組。關(guān)于他的視線最終也還是依賴上面提到的
org.framework.web模塊中的登錄用戶的信息類
@Service
public class DefaultChoiceExpression implements RootChoiceExpression {
@Autowired
OnlineSecurity onlineSecurity;
@Override
public boolean haslogined() {
return onlineSecurity.getOnlinePrincipal()!=null;
}
@Override
public boolean hasRole(String role) {
return onlineSecurity.hasAnyRole(role);
}
@Override
public boolean hasAnyRole(String... roles) {
return onlineSecurity.hasAnyRole(roles);
}
}
- 這里也算是流出擴(kuò)展吧,后面根據(jù)項(xiàng)目需求我們可以重寫該表達(dá)式解析,在根據(jù)自己的業(yè)務(wù)進(jìn)行表達(dá)式新增。這里僅作為基礎(chǔ)功能
private boolean selectAnnotationChoiceDo(SuperDirection superDirection) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
String value = superDirection.value();
if (StringUtils.isEmpty(value)) {
return onlineSecurity.getRoleNames().contains(MaltcloudConstant.SUPERADMIN);
}
MethodInfo info = selectInfoFromExpression(value);
Method declaredMethod = expression.getClass().getDeclaredMethod(info.getMethodName(), String.class);
Object invoke = declaredMethod.invoke(expression, info.getArgs());
if (invoke != null && invoke.toString().equals("true")) {
return true;
}
return false;
}
- 首先根據(jù)正則解析出方法名和參數(shù)。然后根據(jù)反射調(diào)用我們spring中表達(dá)式bean去執(zhí)行我們在
SuperDirection配置的表達(dá)式。通過上面我們又能發(fā)現(xiàn)目前表達(dá)式僅支持String傳參。因?yàn)樵?code>SuperDireciton傳遞過來的已經(jīng)是String了,所以在這里目前我還沒想到如何支持更多類型的數(shù)據(jù)。先埋坑吧! - 該方法最終決定執(zhí)行原生方法還是替補(bǔ)方法。
演示使用
- 上面說的那么枯燥主要是因?yàn)槭俏业囊粋€設(shè)計(jì)思路,下面我們來實(shí)操感受一下吧。
controller
- 首先我在controller中開發(fā)一個接口 。這里需要注意下因?yàn)槲覀兩厦鏁霈F(xiàn)多個實(shí)現(xiàn)bean在spring中,所以我們在使用這些接口的時候就不能單純的使用
@Autowired了, 而需要通過beanName來使用了。這里名叫commonTestServiceImpl的CommonTestService接口的普通實(shí)現(xiàn)類,用于實(shí)現(xiàn)我們正常的操作。
@RestController
@RequestMapping(value = "/demo/common")
public class CommonController {
@Qualifier(value = "commonTestServiceImpl")
@Autowired
CommonTestService commonTestService;
@RequestMapping(value = "/test",method = RequestMethod.GET)
public void test() {
commonTestService.test();
}
}
service
- 這里有兩個實(shí)現(xiàn)類分別是
CommonTestServiceImpl、CommonTest2ServiceImpl
@Service
@SuperDirectionHandler
public class CommonTest2ServiceImpl implements CommonTestService {
@Override
public void test() {
System.out.println("hello test 2");
}
}
@Service
@SuperDirection(value = "")
public class CommonTestServiceImpl implements CommonTestService {
@Override
public void test() {
System.out.println("hello i am test ing ...");
}
}
- 在controller層我們使用的是
CommonTestServiceImpl用來實(shí)現(xiàn)正常的邏輯。而CommonTest2ServiceImpl是針對超級管理員做的操作。我們就可以進(jìn)行如上的配置。在SuperDirection中配置空值標(biāo)識判斷超級管理員進(jìn)行分支執(zhí)行。你也可以配置目前支持的表達(dá)式,我這里簡單點(diǎn)了。 - 然后通過
SuperDirectionHandler標(biāo)識ConmmonTest2ServiceImpl是替補(bǔ)執(zhí)行。
測試
- 由于為了簡單測試,我在org.framework.demo.common模塊中還沒有引入login模塊,所以此時登錄用戶獲取類還是我們默認(rèn)的
OnlineSecurityImpl

- 通過上面代碼我們可以看出來我們當(dāng)前是沒有角色的,所以我們可以理解成調(diào)用接口是沒有超級管理員接口。那么就會執(zhí)行我們
CommonTestServiceImpl中的方法。然后我們在將這里的hasAnyRole改成true , 會發(fā)現(xiàn)就會執(zhí)行CommonTest2ServiceImpl里的方法。
總結(jié)
- 經(jīng)過上面這么折騰,我們就可以在涉及到超級管理員的地方重新實(shí)現(xiàn)一下,然后再原有的實(shí)現(xiàn)類中只需要專注我們權(quán)限架構(gòu)中的規(guī)則進(jìn)行數(shù)據(jù)庫查詢等操作了,而不需要向我一開始那樣為超級管理員進(jìn)行特殊操作。如果后續(xù)我們需要為特殊用戶進(jìn)行特殊開發(fā)。我們就可以擴(kuò)展我們的表達(dá)式解析然后再開發(fā)我們備用接口就可以了。
- 這個思路主要來自于Spring Cloud 中的OpenFeign 的容災(zāi)降級的思路。
以上就是springboot通用分支處理超級管理員權(quán)限邏輯的詳細(xì)內(nèi)容,更多關(guān)于springboot通用分支處理權(quán)限的資料請關(guān)注腳本之家其它相關(guān)文章!
- SpringBoot如何整合Springsecurity實(shí)現(xiàn)數(shù)據(jù)庫登錄及權(quán)限控制
- Springboot+Spring Security實(shí)現(xiàn)前后端分離登錄認(rèn)證及權(quán)限控制的示例代碼
- SpringBoot整合Security實(shí)現(xiàn)權(quán)限控制框架(案例詳解)
- SpringBoot整合Shiro實(shí)現(xiàn)權(quán)限控制的代碼實(shí)現(xiàn)
- Springboot+mybatis-plus+注解實(shí)現(xiàn)數(shù)據(jù)權(quán)限隔離
- springboot+springsecurity如何實(shí)現(xiàn)動態(tài)url細(xì)粒度權(quán)限認(rèn)證
相關(guān)文章
利用java反射機(jī)制調(diào)用類的私有方法(推薦)
下面小編就為大家?guī)硪黄胘ava反射機(jī)制調(diào)用類的私有方法(推薦)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-08-08
關(guān)于在IDEA中SpringBoot項(xiàng)目中activiti工作流的使用詳解
這篇文章主要介紹了關(guān)于在IDEA中SpringBoot項(xiàng)目中activiti工作流的使用詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
詳解Java的Struts框架以及相關(guān)的MVC設(shè)計(jì)理念
這篇文章主要介紹了詳解Java的Struts框架以及相關(guān)的MVC設(shè)計(jì)理念,Struts是Java的SSH三大web開發(fā)框架之一,需要的朋友可以參考下2015-12-12
Java SSM框架(Spring+SpringMVC+MyBatis)搭建過程
最近一段時間搭建了ssm環(huán)境,并測試了幾個小項(xiàng)目,下面小編通過圖文并茂的形式給大家分享Java SSM框架(Spring+SpringMVC+MyBatis)搭建過程,需要的朋友參考下吧2017-11-11
IntelliJ IDEA 使用經(jīng)驗(yàn)總結(jié)(推薦)
這篇文章主要介紹了IntelliJ IDEA 使用經(jīng)驗(yàn)總結(jié),非常不錯,具有參考價值,需要的朋友可以參考下2018-02-02

