一、说明
二、三、四都是推导过程,结论在五,可以直接应用。
主要有两点:
- 自定义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 {
private PermissionResolver permissionResolver;
private RolePermissionResolver permissionRoleResolver;
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;
}
}
问题二
问题:最后我们可不可以自定义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;
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{
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使用的。
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;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@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();
}
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 + "]");
}
}
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())) {
boolean hasAtLeastOnePermission = false;
for (String permission : perms) if (getSubject().isPermitted(permission)) hasAtLeastOnePermission = true;
if (!hasAtLeastOnePermission) getSubject().checkPermission(perms[0]);
}
}
}
三、ModularRealmAuthorizer
继承图
由继承图看出,ModularRealmAuthorizer具有初始化 PermissionResolver、和初始化RolePermissionResolver的功能。
还具有Authorizer授权的功能。它是permission整个委托过程的一环,AuthorizingSecurityManager -> ModularRealmAuthorizer -> UserRealm 。
具体初始化地方是在,AuthorizingSecurityManager 的构造函数里边。
public abstract class AuthorizingSecurityManager extends AuthenticatingSecurityManager {
private Authorizer authorizer;
public AuthorizingSecurityManager() {
super();
this.authorizer = new ModularRealmAuthorizer();
}
}
ModularRealmAuthorizer
class ModularRealmAuthorizer{
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);
}
}
}
}
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;
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.");
}
}
public boolean implies(Permission p) {
if (!(p instanceof WildcardPermission)) {
return false;
}
WildcardPermission wp = (WildcardPermission) p;
List<Set<String>> otherParts = wp.getParts();
int i = 0;
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++;
}
}
for (; i < getParts().size(); i++) {
Set<String> part = getParts().get(i);
if (!part.contains(WILDCARD_TOKEN)) {
return false;
}
}
return true;
}
}
五、基于业务自定义permission(重点)
比如我们要基于数据层面做权限,不是基于接口层面。我们可以使用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
public class OrangePermissionResolver extends WildcardPermissionResolver {
@Override
public Permission resolvePermission(String permissionString) {
return new OrangePermission(permissionString);
}
}
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);
}
public boolean business(Permission permission) {
return true;
}
@Override
public boolean implies(Permission permission) {
if (!(permission instanceof OrangePermission)) {
return false;
}
OrangePermission wp = (OrangePermission) permission;
List<Set<String>> otherParts = wp.getParts();
if (otherParts.size() > 0) {
Set<String> one = otherParts.get(0);
if (one.size() == 1 && one.remove(DISTINCTION)){
return business(permission);
}else {
return super.implies(permission);
}
}
return false;
}
}
实现AuthorizerRealm
@Slf4j
public class UserRealm extends AuthorizingRealm {
@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());
}
}
shiro配置
@Configuration
@Slf4j
public class ShiroConfig {
@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;
}
@Bean("securityManager")
public DefaultWebSecurityManager defaultWebSecurityManager(@Autowired UserRealm userRealm) {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(userRealm);
return defaultWebSecurityManager;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@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("接口之前");
}
}
六、衍生问题
|