标题
一、说明
二、三、四都是推导过程,结论在五,可以直接应用。
主要有两点:
- 自定义Permission
- AOP + Shiro
二、spring-shiro如何实例化并执行对应的permission?
permission执行的片段过程:PermissionAnnotationHandler
的assertAuthorized,里面会调用DelegatingSubject
。DelegatingSubject
里面会委托SecurityManager
,默认实例化的SecurityManager
就是AuthorizingSecurityManager
,AuthorizingSecurityManager
会再把任务委托给Authorizer
,这里Authorizer
的实例是ModularRealmAuthorizer
,ModularRealmAuthorizer
会把任务再委托给AuthorizingRealm
,AuthorizingRealm
会把任务再给Permission
。
实例过程:
PermissionAnnotationHandler -> DelegatingSubject -> AuthorizingSecurityManager-> ModularRealmAuthorizer -> AuthorizingRealm -> Permission
抽象过程:
PermissionAnnotationHandler -> Subject -> SecurityManager -> Authorizer -> Realm -> Permission
如果没有这个权限,会在ModularRealmAuthorizer
的assertRealmsConfigured
抛出IllegalStateException
异常。
核心想法:自定义permission
1. 核心想法&思路
我们详细说一下,我们可以利用的过程。我们沿着一个核心的问题出发,自定义permission。
-
问题
-
在permission执行的片段过程中,它在哪里开始初始化?
-
最后我们可不可以自定义permission?
-
在哪里处理permission逻辑?
-
如果可以那如何去定义?
-
问题一
问题:在permission执行的片段过程中,它在哪里开始初始化?
经过调试,把代码定位到了AuthorizerRealm上面。其它作用看具体解答的第二处。
重点关注第一个isPermitted
的注释说明。
结论:如果要自定义Permission,就得自定义PermissionResolver,让它去实现我们自定义的Permission。但是要覆盖AuthorizingRealm的setPermissionResolver方法。
public abstract class AuthorizingRealm extends AuthenticatingRealm
implements Authorizer, Initializable, PermissionResolverAware, RolePermissionResolverAware {
// 本次问题的主角依赖。get/set不贴出来了
private PermissionResolver permissionResolver;
private RolePermissionResolver permissionRoleResolver;
public boolean isPermitted(PrincipalCollection principals, String permission) {
// 问题核心,这里直接得到Permission的实例。
// PermissionReasolver的作用就是创建Permission实例的地方,在resolvePermission方法中创建。
// WildcardPermissionResolver就是一个好例子。
// 重点:所以我们自己自定义一个PermissionResolver,然后覆盖掉AuthorizingRealm的setPermissionResolver就行了。
Permission p = getPermissionResolver().resolvePermission(permission);
return isPermitted(principals, p);
}
public boolean isPermitted(PrincipalCollection principals, Permission permission) {
AuthorizationInfo info = getAuthorizationInfo(principals);
return isPermitted(permission, info);
}
//visibility changed from private to protected per SHIRO-332
protected boolean isPermitted(Permission permission, AuthorizationInfo info) {
Collection<Permission> perms = getPermissions(info);
if (perms != null && !perms.isEmpty()) {
for (Permission perm : perms) {
if (perm.implies(permission)) {
return true;
}
}
}
return false;
}
}
问题二
问题:最后我们可不可以自定义permission?
由问题一的解答,我们就知道Permission是可以自定义,但是要实现自己的PermissionResolver。而且这个PermissionResolver必须要给到AuthorizerRealm对象。
问题三
问题:在哪里处理permission逻辑?
由开头说的实例过程
PermissionAnnotationHandler -> DelegatingSubject -> AuthorizingSecurityManager-> ModularRealmAuthorizer -> AuthorizingRealm -> Permission
我们就知道是AuthorizingRealm把任务委托给Permission处理,这个才是最终的执行过程。shiro默认实现是WildcardPermission
问题四
问题:如果可以那如何去定义?
这个问题是我自己提问的,但是我觉得这是一个比较具有方向性的问题。
- 首先我们已经知道要自定义Permission ,必须要自定义 WildcardPermissionResolver,再把它的对象给到Authorizer。
- 其次我们再决定 Permission是如何实现权限的比对的,可以参考WildcardPermission。会在目录三仔细分析
- 最后我们可以模仿PermissionAnnotationHandler,实现更细颗粒度的权限控制,比如:基于数据做权限控制,基于栏目关系做权限控制。会在目录四仔细分析
2. 具体解读
(1)初始化PermissionResolver
UserRealm
继承了AuthorizingRealm
,所以我们在初始化UserRealm
之后,会初始化AuthorizingRealm
。
里面默认给我们实现了WildcardPermissionResolver
,它的resolvePermission
方法实例化的就是WildcardPermission
ublic abstract class AuthorizingRealm extends AuthenticatingRealm
implements Authorizer, Initializable, PermissionResolverAware, RolePermissionResolverAware {
public AuthorizingRealm() {
this(null, null);
}
public AuthorizingRealm(CacheManager cacheManager, CredentialsMatcher matcher) {
super();
if (cacheManager != null) {setCacheManager(cacheManager);}
if (matcher != null) {setCredentialsMatcher(matcher);}
this.authorizationCachingEnabled = true;
// 默认的PermissionResolver
this.permissionResolver = new WildcardPermissionResolver();
int instanceNumber = INSTANCE_COUNT.getAndIncrement();
this.authorizationCacheName = getClass().getName() + DEFAULT_AUTHORIZATION_CACHE_SUFFIX;
if (instanceNumber > 0) {
this.authorizationCacheName = this.authorizationCacheName + "." + instanceNumber;
}
}
}
(2) 实例化并执行permission
下面三个方法共同构成了单个permission的整个逻辑。
一
它主要获取了PermissionResolver
的具体实现,这里默认的是WildcardPermissionResolver
。它主要的作用是实例化具体permission的实现。
二
它主要是执行了一个抽象方法getAuthorizationInfo
,其实也就是我们UserRealm
实现的getAuthorizationInfo
, 主要是获取AuthorizationInfo
。
三
它就是执行具体的permission了,比对 注解获取到的permission和AuthorizationInfo里面的permission,会返回比较的结果。这里有一个过程 SecurityManager
-> Authorizer
-> Realm
,执行isPermitted是从SecurityManager
开始,一直通过委托,最后在realm执行,realm就是我们可以定义扩展的地方。
class AuthorizingRealm extends AuthenticatingRealm
implements Authorizer, Initializable, PermissionResolverAware, RolePermissionResolverAware{
/**
* 下面截取的只是String的permission,因为其它的跟这个逻辑都差不多。
*/
// 一
public boolean isPermitted(PrincipalCollection principals, String permission) {
Permission p = getPermissionResolver().resolvePermission(permission);
return isPermitted(principals, p);
}
// 二
public boolean isPermitted(PrincipalCollection principals, Permission permission) {
AuthorizationInfo info = getAuthorizationInfo(principals);
return isPermitted(permission, info);
}
// 三
protected boolean isPermitted(Permission permission, AuthorizationInfo info) {
Collection<Permission> perms = getPermissions(info);
if (perms != null && !perms.isEmpty()) {
for (Permission perm : perms) {
if (perm.implies(permission)) {
return true;
}
}
}
return false;
}
}
(3) shiro是如何处理permission返回的结果?
[1] 继承关系SecurityManager
对比上面两个继承图,最后一个是SpringBoot使用的。
// shiro简单的配置
class ShiroConfig{
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Autowired DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
shiroFilterFactoryBean.setLoginUrl("/user/login");
return shiroFilterFactoryBean;
}
@Bean("securityManager")
public DefaultWebSecurityManager defaultWebSecurityManager(@Autowired Realm realm) {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(realm);
return defaultWebSecurityManager;
}
/**
* 开启shiro的注解扫描。
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* 解决注解扫描不生效的问题。
* @return
*/
@Bean
public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setUsePrefix(true);
return defaultAdvisorAutoProxyCreator;
}
}
[2] 逻辑终结
AuthorizingSecurityManager
permission回调逻辑结束点之一,权限不足的时候,会抛出UnauthorizedException,并停止往下执行
class AuthorizingSecurityManager extends AuthenticatingSecurityManager {
// 重要属性
protected Collection<Realm> realms;
public AuthorizingSecurityManager() {
super();
this.authorizer = new ModularRealmAuthorizer();
}
// checkPermission 会调用对应的isPermission
public void checkPermission(PrincipalCollection principals, String permission) throws AuthorizationException {
assertRealmsConfigured();
if (!isPermitted(principals, permission)) {
throw new UnauthorizedException("Subject does not have permission [" + permission + "]");
}
}
public void checkPermission(PrincipalCollection principals, Permission permission) throws AuthorizationException {
assertRealmsConfigured();
if (!isPermitted(principals, permission)) {
throw new UnauthorizedException("Subject does not have permission [" + permission + "]");
}
}
// 下面两个都把授权委托给了AuthorizerRealm
public boolean isPermitted(PrincipalCollection principals, Permission permission) {
assertRealmsConfigured();
for (Realm realm : getRealms()) {
if (!(realm instanceof Authorizer)) continue;
if (((Authorizer) realm).isPermitted(principals, permission)) {
return true;
}
}
return false;
}
public boolean isPermitted(PrincipalCollection principals, String permission) {
assertRealmsConfigured();
for (Realm realm : getRealms()) {
if (!(realm instanceof Authorizer)) continue;
if (((Authorizer) realm).isPermitted(principals, permission)) {
return true;
}
}
return false;
}
}
上面有两个checkPermission方法,其实逻辑都是一样的,只是permission的参数类型不一样。
他们都调用了isPermission,根据返回的结果,再决定抛出异常,终止程序继续往下运行,这就是其中之一的结束点。
### (4) PermissionAnnotationHandler - @RequisePermission
class PermissionAnnotationHandler{
public void assertAuthorized(Annotation a) throws AuthorizationException {
if (!(a instanceof RequiresPermissions)) return;
RequiresPermissions rpAnnotation = (RequiresPermissions) a;
String[] perms = getAnnotationValue(a);
Subject subject = getSubject();
if (perms.length == 1) {
subject.checkPermission(perms[0]);
return;
}
if (Logical.AND.equals(rpAnnotation.logical())) {
getSubject().checkPermissions(perms);
return;
}
if (Logical.OR.equals(rpAnnotation.logical())) {
// Avoid processing exceptions unnecessarily - "delay" throwing the exception by calling hasRole first
boolean hasAtLeastOnePermission = false;
for (String permission : perms) if (getSubject().isPermitted(permission)) hasAtLeastOnePermission = true;
// Cause the exception if none of the role match, note that the exception message will be a bit misleading
if (!hasAtLeastOnePermission) getSubject().checkPermission(perms[0]);
}
}
}
三、ModularRealmAuthorizer
继承图
由继承图看出,ModularRealmAuthorizer具有初始化 PermissionResolver、和初始化RolePermissionResolver的功能。
还具有Authorizer授权的功能。它是permission整个委托过程的一环,AuthorizingSecurityManager
-> ModularRealmAuthorizer
-> UserRealm
。
具体初始化地方是在,AuthorizingSecurityManager
的构造函数里边。
public abstract class AuthorizingSecurityManager extends AuthenticatingSecurityManager {
/**
* The wrapped instance to which all of this <tt>SecurityManager</tt> authorization calls are delegated.
*/
private Authorizer authorizer;
/**
* Default no-arg constructor that initializes an internal default
* {@link org.apache.shiro.authz.ModularRealmAuthorizer ModularRealmAuthorizer}.
*/
public AuthorizingSecurityManager() {
super();
this.authorizer = new ModularRealmAuthorizer();
}
}
ModularRealmAuthorizer
class ModularRealmAuthorizer{
// 给realm初始化RolePermissionResolver
protected void applyRolePermissionResolverToRealms() {
RolePermissionResolver resolver = getRolePermissionResolver();
Collection<Realm> realms = getRealms();
if (resolver != null && realms != null && !realms.isEmpty()) {
for (Realm realm : realms) {
if (realm instanceof RolePermissionResolverAware) {
((RolePermissionResolverAware) realm).setRolePermissionResolver(resolver);
}
}
}
}
// 授权,这里会直接调用我们UserRealm继承的AuthorizingRealm的isPermitted()
public boolean isPermitted(PrincipalCollection principals, Permission permission) {
assertRealmsConfigured();
for (Realm realm : getRealms()) {
if (!(realm instanceof Authorizer)){ continue;}
if (((Authorizer) realm).isPermitted(principals, permission)) {
return true;
}
}
return false;
}
}
四、WildcardPermission对比规则
下面我就粘出部分重要的代码逻辑。
public class WildcardPermission implements Permission, Serializable {
protected static final String WILDCARD_TOKEN = "*";
protected static final String PART_DIVIDER_TOKEN = ":";
protected static final String SUBPART_DIVIDER_TOKEN = ",";
protected static final boolean DEFAULT_CASE_SENSITIVE = false;
// 这个方法是把wildcardString字符串分割成一个List<Set<String>>,先通过:分割,之后基于:分割结果,再通过,分割。
//
protected void setParts(String wildcardString, boolean caseSensitive) {
wildcardString = StringUtils.clean(wildcardString);
if (wildcardString == null || wildcardString.isEmpty()) {
throw new IllegalArgumentException("Wildcard string cannot be null or empty. Make sure permission strings are properly formatted.");
}
if (!caseSensitive) {
wildcardString = wildcardString.toLowerCase();
}
// 通过:分割
List<String> parts = CollectionUtils.asList(wildcardString.split(PART_DIVIDER_TOKEN));
this.parts = new ArrayList<Set<String>>();
for (String part : parts) {
// 基于:分割的结果,再通过,进行分割
Set<String> subparts = CollectionUtils.asSet(part.split(SUBPART_DIVIDER_TOKEN));
if (subparts.isEmpty()) {
throw new IllegalArgumentException("Wildcard string cannot contain parts with only dividers. Make sure permission strings are properly formatted.");
}
this.parts.add(subparts);
}
if (this.parts.isEmpty()) {
throw new IllegalArgumentException("Wildcard string cannot contain only dividers. Make sure permission strings are properly formatted.");
}
}
// 权限逻辑对比。
// 前提:括号这个形参是通过PermissionAnnotationHandler扫描注解得来的。然后本身类的对象是通过AuthorizationInfo实例化出来的。
public boolean implies(Permission p) {
if (!(p instanceof WildcardPermission)) {
return false;
}
WildcardPermission wp = (WildcardPermission) p;
List<Set<String>> otherParts = wp.getParts();
int i = 0;
// 例子:p(形参) [[user], [update, save]] obj(当前类对象)[[user], [update, insert]]
// 1. false的逻辑: obj存在通配符 与 obj包含p的所有子元素,两个都不满足的情况下,就是权限不够。
for (Set<String> otherPart : otherParts) {
if (getParts().size() - 1 < i) {
return true;
} else {
Set<String> part = getParts().get(i);
if (!part.contains(WILDCARD_TOKEN) && !part.containsAll(otherPart)) {
return false;
}
i++;
}
}
// 例子:p(形参) [[user], [update, save]] obj(当前类对象)[[user], [update, insert], [aa]]
// 2. false的逻辑: 如果obj比p元素多的情况下,obj不包含*就代表权限不足。
for (; i < getParts().size(); i++) {
Set<String> part = getParts().get(i);
if (!part.contains(WILDCARD_TOKEN)) {
return false;
}
}
// 以上false逻辑都不满足的情况下,就代表权限满足。
return true;
}
}
五、基于业务自定义permission & 重写AuthorizerRealm的isPermitted(重点)
比如我们要基于数据层面做权限,不是基于接口层面。我们可以使用SpringAOP实现,下面展示代码例子。
思路说明
这里主要通过 aop 拦截具体的方法,执行Subject.checkPermission。
然后流程就是
AOP -> DelegatingSubject -> AuthorizingSecurityManager-> ModularRealmAuthorizer -> AuthorizingRealm -> Permission
shiro自带的流程是
PermissionAnnotationHandler -> DelegatingSubject -> AuthorizingSecurityManager-> ModularRealmAuthorizer -> AuthorizingRealm -> Permission
这里我们自己的流程自带两个扩展点,AOP这里是一个,自定义Permission是一个。
-------------- 下面是重点 --------------
-
原本的PermissionAnnotationHandler是获取方法上的注解拿到 具体的Permission字符串,现在我们改成AOP,获取Permission字符串的地方我们可以改成数据库。
-
最后就是Permission,我们可以自己定义Permision实现,对比的规则也可以根据自己的业务进行扩展。而不仅仅限于WildcardPermission提供的规则。
自定义Permission & AuthorizingRealm
/**
* @author :orange
* @date :Created in 2022/6/1 10:30
* @description:自定义permission处理器
*/
public class OrangePermissionResolver extends WildcardPermissionResolver {
@Override
public Permission resolvePermission(String permissionString) {
return new OrangePermission(permissionString);
}
}
/**
* @author :orange
* @date :Created in 2022/6/1 10:46
* @description:具体处理
*/
public class OrangePermission extends WildcardPermission {
/**
* 业务标识符
*/
private final String DISTINCTION = "data";
public OrangePermission(String wildcardString) {
this(wildcardString, false);
}
public OrangePermission(String wildcardString, boolean caseSensitive) {
super(wildcardString, caseSensitive);
}
/**
* 具体扩展的业务
* @return
*/
public boolean business(Permission permission) {
return true;
}
/**
* 这里可以自定义了。
* @param permission
* @return
*/
@Override
public boolean implies(Permission permission) {
if (!(permission instanceof OrangePermission)) {
return false;
}
return super.implies(permission);
}
}
实现AuthorizerRealm,重写isPermitted(重要)
/**
* @author :orange
* @date :Created in 2022/6/6 12:28
* @description:节点和权限判断。
*/
@Component
public class NodeRolePermission {
/**
* 针对于 父级没有权限,子级有权限,但是要以父级为准的情况。
* 权限是否处于开启状态是available字段
* @return
*/
private boolean level(CMSPermission cmsPermission) {
return true;
}
/**
* 针对于节点和角色,以节点为准,角色为次。
* 如果节点不开启,那么角色就算有权限也不能访问。
* 如果节点开启,角色没有权限也不能访问。
* 要两个同时开启才能有权限。
* @return
*/
private boolean intersection(CMSPermission cmsPermission) {
return false;
}
/**
* 不需要多次处理,处理一次就行了。
* @param wildcard
* @return
*/
@Cacheable(value = "NodeRolePermissionBusiness", key = "#wildcard")
public boolean business(String wildcard) {
SysLanguage sysLanguage = langUtil.getLoginUserLang();
Optional<CMSPermission> optionalPermission = cmsPermissionRepository.findByNameAndDeletedIsFalseAndSysLanguage(wildcard, sysLanguage);
if (optionalPermission.isEmpty()) {
return false;
}
CMSPermission cmsPermission = optionalPermission.get();
boolean intersection = intersection(cmsPermission);
boolean level = level(cmsPermission);
return intersection && level;
}
}
@Slf4j
public class UserRealm extends AuthorizingRealm {
@Resource(type = NodeRolePermission.class)
private NodeRolePermission nodeRolePermission;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addStringPermissions(Arrays.asList(new String[]{"user:show,login"}));
return simpleAuthorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
log.info("authenticationInfo");
String username = (String) authenticationToken.getPrincipal();
String password = new String((char[]) authenticationToken.getCredentials());
String auth = "orange";
if(!auth.equals(username)) {
return null;
}
if (!auth.equals(password)) {
return null;
}
return new SimpleAuthenticationInfo(username, password, getName());
}
// 这里也是业务扩展点,就比如你在对比permission之后,还需要对比栏目的父级是否也开启的权限。
@Override
protected boolean isPermitted(Permission permission, AuthorizationInfo info) {
Collection<Permission> perms = getPermissions(info);
if (perms != null && !perms.isEmpty()) {
for (Permission perm : perms) {
if (perm.implies(permission)) {
return true && nodeRolePermission.business(((KguPermission) permission).getWildcard());
}
}
}
return false;
}
}
shiro配置
/**
* @author :orange
* @date :Created in 2022/5/30 0:20
* @description:shiro配置
*/
@Configuration
@Slf4j
public class ShiroConfig {
/**
* ShiroFilterFactoryBean作用是创建AbstractShiroFilter对象。
* @param defaultWebSecurityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Autowired DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/user/login", "anon");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
shiroFilterFactoryBean.setLoginUrl("/user/login");
return shiroFilterFactoryBean;
}
@Bean("userRealm")
public UserRealm createRealm() {
UserRealm userRealm = new UserRealm();
userRealm.setPermissionResolver(new OrangePermissionResolver());
return userRealm;
}
/**
* SecurityManager的web默认实现
* @return
*/
@Bean("securityManager")
public DefaultWebSecurityManager defaultWebSecurityManager(@Autowired UserRealm userRealm) {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(userRealm);
return defaultWebSecurityManager;
}
/**
* 开启shiro的注解扫描。
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* 解决注解扫描不生效的问题。
* @return
*/
@Bean
public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setUsePrefix(true);
return defaultAdvisorAutoProxyCreator;
}
}
aop切入
这个注解建议可以写在方法上。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface OrangePermission {}
@Aspect
@Component
public class ShiroAdvice {
@Pointcut("@annotation(OrangePermission)")
public void data(){};
@Before("data()")
public void advice() {
Subject subject = SecurityUtils.getSubject();
subject.checkPermission("user:none");
System.out.println("接口之前");
}
}