鲁春利的工作笔记,好记性不如烂笔头



官网地址:http://shiro.apache.org/


Apache Shiro学习笔记(一)Shiro简介_第1张图片


主要功能包括

Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情;常见的如:验证某个用户是否拥有某个角色。

Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;
Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
Web Integration:Web 集成,可以非常容易的集成到Web 环境;


基本概念

Subject:主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject 都绑定到SecurityManager。

Apache Shiro学习笔记(一)Shiro简介_第2张图片

SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager 交互;且它管理着所有Subject;可以看出它是Shiro 的核心,它负责与后边介绍的其他组件进行交互。

Apache Shiro学习笔记(一)Shiro简介_第3张图片

Realm:域,Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;可以把Realm看成DataSource,即安全数据源。

Apache Shiro学习笔记(一)Shiro简介_第4张图片


Shiro中的Hello World!

配置文件(src/test/resources/shiro/first-shiro.ini):

[users]
lucl=123
wang=123

代码实现类:

/**
 * 
 * @author lucl
 * 
 * Authentication with shiro
 *
 */
public class TestLoginWithShiro {
    private static final Logger logger = Logger.getLogger(TestLoginWithShiro.class);

    /**
     * test login
     */
    @Test
    public void testLoginSuccess () {
        // 1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager
        Factory factory = new IniSecurityManagerFactory("classpath:shiro/first-shiro.ini");
        
        // 2、得到SecurityManager实例并绑定给SecurityUtils
        org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        
        // 3、得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken("lucl", "123");
        
        try{
            // 4、登录,即身份验证
            subject.login(token);
        } catch (AuthenticationException e) {
            // 5、身份验证失败
            logger.info("用户身份验证失败");
            e.printStackTrace();
        }
        
        Assert.assertEquals(true, subject.isAuthenticated()); //断言用户已经登录
        
        if (subject.isAuthenticated()) {
            logger.info("用户登录成功。");
        } else {
            logger.info("用户登录失败。");
        }
        
        // 6、退出
        subject.logout();
    }
}


身份认证流程

1、通过new IniSecurityManagerFactory 并指定一个ini 配置文件来创建一个SecurityManager工厂;
2、获取SecurityManager并绑定到SecurityUtils,这是一个全局设置,设置一次即可;
3、通过SecurityUtils得到Subject,其会自动绑定到当前线程;
4、调用subject.login 方法进行登录,其会自动委托给SecurityManager.login方法进行登录;
DelegatingSubject类的login方法:

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

    PrincipalCollection principals;

    String host = null;

    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 = delegating.principals;
        host = delegating.host;
    } else {
        principals = subject.getPrincipals();
    }

    if (principals == null || principals.isEmpty()) {
        String msg = "Principals returned from securityManager.login( token ) returned a null or " +
                "empty value.  This value must be non null and populated with one or more elements.";
        throw new IllegalStateException(msg);
    }
    this.principals = principals;
    this.authenticated = true;
    if (token instanceof HostAuthenticationToken) {
        host = ((HostAuthenticationToken) token).getHost();
    }
    if (host != null) {
        this.host = host;
    }
    Session session = subject.getSession(false);
    if (session != null) {
        this.session = decorate(session);
    } else {
        this.session = null;
    }
}

DefaultSecurityManager执行真正的登录操作:

public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
    AuthenticationInfo info;
    try {
        info = authenticate(token);
    } catch (AuthenticationException ae) {
        try {
            onFailedLogin(token, ae, subject);
        } catch (Exception e) {
            if (log.isInfoEnabled()) {
                log.info("onFailedLogin method threw an " +
                        "exception.  Logging and propagating original AuthenticationException.", e);
            }
        }
        throw ae; //propagate
    }

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

    onSuccessfulLogin(token, info, loggedIn);

    return loggedIn;
}

IniRealm获取身份验证信息(AuthenticatingRealm类定义):

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;
}

IniRealm获取身份验证信息(SimpleAccountRealm类定义):

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    UsernamePasswordToken upToken = (UsernamePasswordToken) token;
    SimpleAccount account = getUser(upToken.getUsername());

    if (account != null) {

        if (account.isLocked()) {
            throw new LockedAccountException("Account [" + account + "] is locked.");
        }
        if (account.isCredentialsExpired()) {
            String msg = "The credentials for account [" + account + "] are expired";
            throw new ExpiredCredentialsException(msg);
        }

    }

    return account;
}

AuthenticatingRealm执行身份认证:

protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
    CredentialsMatcher cm = getCredentialsMatcher();
    if (cm != null) {
        if (!cm.doCredentialsMatch(token, info)) {
            //not successful - throw an exception to indicate this:
            String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
            throw new IncorrectCredentialsException(msg);
        }
    } else {
        throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify " +
                "credentials during authentication.  If you do not wish for credentials to be examined, you " +
                "can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
    }
}

5、验证通过则身份认证成功;否则抛出异常。

6、调用subject.logout退出,其会自动委托给SecurityManager.logout方法退出。