Shiro源码解析之认证代码走查

Shiro源码解析之认证代码走查

SpringBoot集成Shiro的Demo比较好找,我这篇文章就默认大家已经有相应的基础了,如果还没有集成过,那可能还是先去了解一下比较好,所以,这里就不详细介绍集成的相关内容了。

1.实现AuthorizingRealm

在Shiro集成过程中,有一个重要的步骤就是自定义一个Realm类(如下代码块的类UserRealm),这个类应该继承类AuthorizingRealm,并实现方法doGetAuthenticationInfo、doGetAuthorizationInfo,将认证信息、授权信息分别放入类SimpleAuthenticationInfo、SimpleAuthorizationInfo的实体中。

public class UserRealm extends AuthorizingRealm
{
    private static final Logger log = LoggerFactory.getLogger(UserRealm.class);

    @Autowired
    private IMenuService menuService;

    @Autowired
    private IRoleService roleService;

    @Autowired
    private LoginService loginService;

    /**
     * 授权
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0)
    {
        User user = ShiroUtils.getUser();
        // 角色列表
        Set<String> roles = new HashSet<String>();
        // 功能列表
        Set<String> menus = new HashSet<String>();
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        // 管理员拥有所有权限
        if (user.isAdmin()) {
            info.addRole("admin");
            //*:*:* 资源:操作:对象,如order:create:1
            info.addStringPermission("*:*:*");
        }   else  {
            roles = roleService.selectRoleKeys(user.getUserId());
            menus = menuService.selectPermsByUserId(user.getUserId());
            // 角色加入AuthorizationInfo认证对象
            info.setRoles(roles);
            // 权限加入AuthorizationInfo认证对象
            info.setStringPermissions(menus);
        }
        return info;
    }

    /**
     * 登录认证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException
    {
        //替换为自己封装的token
        CustomToken upToken = (CustomToken) token;
        String username = upToken.getUsername();
        LoginType type = upToken.getType();
        String role = upToken.getRole();
        String password = "";
        if (upToken.getPassword() != null){
            password = new String(upToken.getPassword());
        }

        User user = null;
        try {
            user = loginService.login(username, password,type,role);
        }   catch (CaptchaException e) {
            throw new AuthenticationException(e.getMessage(), e);
        }   catch (UserNotExistsException e) {
            throw new UnknownAccountException(e.getMessage(), e);
        }   catch (UserPasswordNotMatchException e) {
            throw new IncorrectCredentialsException(e.getMessage(), e);
        }   catch (UserPasswordRetryLimitExceedException e) {
            throw new ExcessiveAttemptsException(e.getMessage(), e);
        }   catch (UserBlockedException e) {
            throw new LockedAccountException(e.getMessage(), e);
        }   catch (RoleBlockedException e) {
            throw new LockedAccountException(e.getMessage(), e);
        }   catch (Exception e) {
            log.info("对用户[" + username + "]进行登录验证..验证未通过{}", e.getMessage());
            throw new AuthenticationException(e.getMessage(), e);
        }
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName());
            return info;
    }
}

2. 触发doGetAuthenticationInfo认证

在根据我们自己的业务情况,实现了doGetAuthenticationInfo方法后,我们就可以通过调用SecurityUtils.getSubject().login()方法来触发调用doGetAuthenticationInfo方法进行认证了。

Subject subject = SecurityUtils.getSubject();

subject.login(token);

那么,从执行subject.login方法到调用到我们自定义的doGetAuthenticationInfo方法,到底Shiro框架是怎么实现的呢?以下就是把我的代码追踪过程整理如下:

  • 首先调用Subject的实现类DelegatingSubject里的login方法,这个可以注意一下这个参数值token,其实就是我们在web项目中登录时所输入的用户名、密码等信息,但是需要实现AuthenticationToken接口。
public class DelegatingSubject implements Subject {
    protected transient SecurityManager securityManager;
    public void login(AuthenticationToken token) throws AuthenticationException {
	    //省略...
		Subject subject = securityManager.login(this, token);
	    //省略...
	    if (subject instanceof DelegatingSubject) {
            DelegatingSubject delegating = (DelegatingSubject) subject;
            //we have to do this in case there are assumed identities - we don't want to lose the 'real' principals:
            //下面这句话非常重要,登录后,principals才会有身份信息;若没有执行登录操作,直接调用shiro的权限校验,如注解@RequiresPermissions,会先判断该字段是否有值,若没有则会提示需要先登录
            principals = delegating.principals;
            host = delegating.host;
        } else {
            principals = subject.getPrincipals();
        }
        //省略...
    }
}
  • 然后进入到securityManager.login(this, token),默认跳转到实现类DefaultSecurityManager中的login方法(关于SecurityManager的相关实现类图,我会附在文档后面)。
public class DefaultSecurityManager extends SessionsSecurityManager {
    //省略...
  public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {

    AuthenticationInfo info;
    try {
            info = authenticate(token);
        }
    //省略...
  }
}
  • DefaultSecurityManager的login方法中继续调用父类AuthenticatingSecurityManager的authenticate方法。
public abstract class AuthenticatingSecurityManager extends RealmSecurityManager {
    private Authenticator authenticator;
    public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
        return this.authenticator.authenticate(token);
    }
}
  • 类AuthenticatingSecurityManager的authenticate方法中调用Authenticator类的authenticate方法,找到其实现类AbstractAuthenticator里的authenticate方法(注意,这个方法有final修饰符,不可被重写)。另外,authenticate方法里调用抽象方法doAuthenticate,所以接下来继续找该方法的实现。
public abstract class AbstractAuthenticator implements Authenticator, LogoutAware {
    public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
		//省略...
        AuthenticationInfo info;
        try {
            info = doAuthenticate(token);
        }
        //省略...
    }
	protected abstract AuthenticationInfo doAuthenticate(AuthenticationToken token)
            throws AuthenticationException;
}
  • ModularRealmAuthenticator类对doAuthenticate进行了实现,方法体里的getRealms()里获取到的Realm就是我们之前定义的UserRealm。在只定义了一个Realm的情况下,会调用方法doSingleRealmAuthentication,进而调用到realm的getAuthenticationInfo方法。
public class ModularRealmAuthenticator extends AbstractAuthenticator {
    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        //省略...
        Collection<Realm> realms = getRealms();
        if (realms.size() == 1) {
            return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
        } else {
            return doMultiRealmAuthentication(realms, authenticationToken);
        }
    }
    
    protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
        //省略...
        AuthenticationInfo info = realm.getAuthenticationInfo(token);
        //省略...
        return info;
    }
}
  • AuthenticatingRealm类对getAuthenticationInfo方法进行实现,并调用doGetAuthenticationInfo方法,这是一个抽象方法,所以会调用它子类的实现。
public abstract class AuthenticatingRealm extends CachingRealm implements Initializable {
	public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

	    AuthenticationInfo info = getCachedAuthenticationInfo(token);
		//省略...
	    info = doGetAuthenticationInfo(token);
		//省略...
	    return info;
    }
    protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;
}

代码走查到这一步,再往下查找,就会调用到我们的实现类UserRealm里对doGetAuthenticationInfo方法的具体实现了(这个还有印象吧,就是第一个代码块的内容)。

走完一次源码的过程,就会觉得比较通畅,消除神秘感了,不喜欢那种黑盒子的感觉。接下来,就是继续把授权的调用链过程发出来。至于这里面所使用到的设计思想,可以有空再仔细研究。

附图

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值