shiro学习-用户认证器authenticator源码实现解析

1、shiro整体架构

整体架构
shiro中,认证器authenticator、授权器authorizer和用于提供用户认证、授权信息的realm域,统一交由SecurityManager来进行调度。

2、shiro认证用户登录流程

2.1、【UserRealmDemo】创建subject主体,并调用login方法

public static void main(String[] args) {
        IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        Subject subject = SecurityUtils.getSubject();

        UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "1111");
        try {
            subject.login(token);
            System.out.println("登陆成功");
        } catch (UnknownAccountException e) {
            System.out.println("用户名错误");
            e.printStackTrace();
        } catch (ExpiredCredentialsException e){
            System.out.println("凭证过期");
            e.printStackTrace();
        } catch (IncorrectCredentialsException e){
            System.out.println("密码错误");
            e.printStackTrace();
        } catch (Exception e){
            System.out.println("登陆失败");
            e.printStackTrace();
        }

        System.out.println(subject.isPermittedAll("user:add", "user:delete"));
    }
  1. SecurityManagerFactory工厂类加载shiro.ini配置文件,获取securitymanager;
  2. 将其设置到SecurityUtils工具类中,并生成得到主体subject;
  3. 新建UsernamePasswordToken对象,传入用户名密码,模拟前台数据输入;
  4. 调用主体subject的login方法,传入前台token作为参数,并调用try-catch块捕获异常,来确定是否验证成通过。

2.2、【DelegatingSubject】实现subject的login方法

public void login(AuthenticationToken token) throws AuthenticationException {
        clearRunAsIdentitiesInternal();
        Subject subject = securityManager.login(this, token);

        PrincipalCollection principals;

        String host = null;

        。。。。。。
    }

该login方法将控制权交给SecurityManager的login方法来实现,传入自身引用和token对象。
若成功,则返回带有info信息的subject对象。

2.3、【DefaultSecurityManager】实现SecurityManager的login方法

public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
        AuthenticationInfo info;
        try {
            info = authenticate(token);
        } catch (AuthenticationException ae) {
            。。。
        }

        Subject loggedIn = createSubject(token, info, subject);

        onSuccessfulLogin(token, info, loggedIn);

        return loggedIn;
    }

方法中调用由AuthenticatingSecurityManager实现的authenticate方法,传入token参数。

2.4、【AuthenticatingSecurityManager】实现SecurityManager的authenticate方法

public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
        return this.authenticator.authenticate(token);
}
public AuthenticatingSecurityManager() {
        super();
        this.authenticator = new ModularRealmAuthenticator();
}

该类的authenticate方法起到一个中转的作用,调用本身authenticator对象的authenticate方法,而此对象通过new ModularRealmAuthenticator() 新建。

2.5、【AbstractAuthenticator】实现authenticator的authenticate方法

public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {

        if (token == null) {
            throw new IllegalArgumentException("Method argument (authentication token) cannot be null.");
        }

        log.trace("Authentication attempt received for token [{}]", token);

        AuthenticationInfo info;
        try {
            info = doAuthenticate(token);
            if (info == null) {
                String msg = "No account information found for authentication token [" + token + "] by this " +
                        "Authenticator instance.  Please check that it is configured correctly.";
                throw new AuthenticationException(msg);
            }
        } catch (Throwable t) {
           。。。
        }

        log.debug("Authentication successful for token [{}].  Returned account [{}]", token, info);

        notifySuccess(token, info);

        return info;
    }

该类的authenticate方法调用由其子类ModularRealmAuthenticator实现的doAuthenticate方法。

2.6、【ModularRealmAuthenticator】实现authenticator的doAuthenticate方法

protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        assertRealmsConfigured();
        Collection<Realm> realms = getRealms();
        if (realms.size() == 1) {
            return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
        } else {
            return doMultiRealmAuthentication(realms, authenticationToken);
        }
    }
  1. 该类的duAuthenticate()方法首先执行assertRealmsConfigured()方法,来读取并加载shiro.ini中配置的userRealm自定义域:
protected void assertRealmsConfigured() throws IllegalStateException {
        Collection<Realm> realms = getRealms();
        if (CollectionUtils.isEmpty(realms)) {
            String msg = "Configuration error:  No realms have been configured!  One or more realms must be " +
                    "present to execute an authentication attempt.";
            throw new IllegalStateException(msg);
        }
    }
  1. 加载realms域后,判断如果是单个realm执行doSingleRealmAuthentication()方法, 如果是多个realm则执行doMultiRealmAuthentication()方法
    本次是单个realm,执行doSingleRealmAuthentication() 方法, 传入加载的realm域,并在其中调用自定义realm域的getAuthenticationInfo()方法:
protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
        if (!realm.supports(token)) {
            String msg = "Realm [" + realm + "] does not support authentication token [" +
                    token + "].  Please ensure that the appropriate Realm implementation is " +
                    "configured correctly or that the realm accepts AuthenticationTokens of this type.";
            throw new UnsupportedTokenException(msg);
        }
        AuthenticationInfo info = realm.getAuthenticationInfo(token);
        if (info == null) {
            String msg = "Realm [" + realm + "] was unable to find account data for the " +
                    "submitted AuthenticationToken [" + token + "].";
            throw new UnknownAccountException(msg);
        }
        return info;
    }

2.7、【AuthenticatingRealm】实现realm域的getAuthenticationInfo()方法

public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        AuthenticationInfo info = getCachedAuthenticationInfo(token);
        if (info == null) {
            //otherwise not cached, perform the lookup:
            info = doGetAuthenticationInfo(token);
            log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
            if (token != null && info != null) {
                cacheAuthenticationInfoIfPossible(token, info);
            }
        } else {
            log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
        }

        if (info != null) {
            assertCredentialsMatch(token, info);
        } else {
            log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}].  Returning null.", token);
        }

        return info;
    }

该方法调用自定义realm域中写的doGetAuthenticationInfo(token)方法,通过token中的用户名获取数据库中对应的密码信息,并将其封装为AuthenticationInfo对象返回,再执行assertCredentialsMatch(token, info)方法判断密码是否一致,若不一致抛出异常,一致则证明认证通过

2.8、【UserRealm】该类实现自定义doGetAuthenticationInfo()方法

public class UserRealm extends AuthorizingRealm {

    @Override
    public String getName() {
        return "userRealm";
    }

    //完成身份认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        Object principal = token.getPrincipal();
        //此处模拟从数据库根据用户名查询密码和盐值
        String pwd = "47253c7d26d81ca795eabb5314aeffac";
        String salt = "salttest";
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, pwd, ByteSource.Util.bytes(salt), getName());
        return info;
    }

    //完成授权信息
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        。。。
    }
}

UserRealm类即为自定义认证和授权的类,用户可以通过extends关键字继承AuthorizingRealm类,重写doGetAuthenticationInfo()方法来实现自己的业务目标。

3、总结

这一套流程捋清楚之后,其实很简单,只是为了实现根据用户名(Principal)获取用户密码(Credential),然后和用户输入的密码进行比较来验证是否认证通过,中间任何一个环节出现问题都会抛出异常,即认证失败。
可是梳理起来为什么涉及到这么多接口、方法、实现类呢?个人理解应该是为了实现低耦合,同时方便对功能进行扩展。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值