RBAC

RBCA

RBCA zh_CN

Shiro

Apache Shiro is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application, from the smallest mobile applications to the largest web and enterprise applications.

shiro

shiro zh_CN

Shiro provides the application security API to perform the following aspects:

  • Authentication - proving user identity, often called user ‘login’
  • Authorization - access control
  • Cryptography - protecting or hiding data from prying eyes
  • Session Management - per-user time-sensitive state
Subject->SecurityManager: SecurityManager->Realms:

Hello world

  • pom.xml
<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.9</version>
    </dependency>
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.1.3</version>
    </dependency>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.2.2</version>
    </dependency>
</dependencies>
  • shiro.ini

create this file under the classpath.

[users]
ryo=123
wang=123
  • ShiroTest.java
@Test
public void testHelloworld() {
    Factory<SecurityManager> factory =
            new IniSecurityManagerFactory("classpath:shiro.ini");

    org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
    SecurityUtils.setSecurityManager(securityManager);
    Subject subject = SecurityUtils.getSubject();
    UsernamePasswordToken token = new UsernamePasswordToken("ryo", "123");

    try {
        subject.login(token);
    } catch (AuthenticationException e) {
        //login falied
    }

   assertEquals(true, subject.isAuthenticated());   //assert user has logined.

   //logout
   subject.logout();
}

Realms

Realms act as the ‘bridge’ or ‘connector’ between Shiro and your application’s security data. When it comes time to actually interact with security-related data like user accounts to perform authentication (login) and authorization (access control), Shiro looks up many of these things from one or more Realms configured for an application.

  • Realm.java
public interface Realm {
    String getName();   //返回一个唯一的Realm名字

    boolean supports(AuthenticationToken var1); //判断此Realm是否支持此Token

    AuthenticationInfo getAuthenticationInfo(AuthenticationToken var1) throws AuthenticationException;  //根据Token获取认证信息
}
  • MyRealm.java
public class MyRealm implements Realm {
    @Override
    public String getName() {
        return "firstRealm";
    }

    @Override
    public boolean supports(AuthenticationToken authenticationToken) {
        //仅支持UsernamePasswordToken类型的Token
        return authenticationToken instanceof UsernamePasswordToken;
    }

    @Override
    public AuthenticationInfo getAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String username = (String) authenticationToken.getPrincipal();      //get username
        String password = new String((char[]) authenticationToken.getCredentials());    //get password
        if (!"ryo".equals(username)) {
            throw new UnknownAccountException();
        }
        if (!"123".equals(password)) {
            throw new IncorrectCredentialsException();
        }

        //如果身份认证验证成功,返回一个AuthenticationInfo实现;
        return new SimpleAuthenticationInfo(username, password, getName());
    }
}
  • shiro-realm.ini

create this file under the classpath.

#declear realm
firstRealm=com.ryo.shiro.MyRealm
#point the realms impls of securityManager
securityManager.realms=$firstRealm
  • test()
@Test
public void testRealm() {
    Factory<SecurityManager> factory =
            new IniSecurityManagerFactory("classpath:shiro-realm.ini");
    org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
    SecurityUtils.setSecurityManager(securityManager);
    Subject subject = SecurityUtils.getSubject();
    UsernamePasswordToken token = new UsernamePasswordToken("ryo", "123");

    try {
        subject.login(token);
    } catch (AuthenticationException e) {
    }

    assertEquals(true, subject.isAuthenticated());

    subject.logout();
}

multi-realm

  • define another realm SecondRealm.java
public class SecondRealm implements Realm {
    public String getName() {
        return "secondRealm";
    }

    public boolean supports(AuthenticationToken authenticationToken) {
        return authenticationToken instanceof UsernamePasswordToken;
    }

    public AuthenticationInfo getAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String username = (String) authenticationToken.getPrincipal();
        String password = new String((char[]) authenticationToken.getCredentials());
        if (!"wang".equals(username)) {
            throw new UnknownAccountException();
        }
        if (!"123".equals(password)) {
            throw new IncorrectCredentialsException();
        }
        return new SimpleAuthenticationInfo(username, password, getName());
    }
}
  • define shiro-multi-realm.ini
[main]
#define
firstRealm=com.ryo.shiro.FirstRealm
secondRealm=com.ryo.shiro.SecondRealm
#use
securityManager.realms=$firstRealm,$secondRealm
  • test()
@Test
public void testMultiRealm() {
    Factory<SecurityManager> factory =
            new IniSecurityManagerFactory("classpath:shiro-multi-realm.ini");

    org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
    SecurityUtils.setSecurityManager(securityManager);

    Subject subject = SecurityUtils.getSubject();
    UsernamePasswordToken token = new UsernamePasswordToken("wang", "123");

    try {
        subject.login(token);
    } catch (AuthenticationException e) {
        e.printStackTrace();
    }

    Assert.assertEquals(true, subject.isAuthenticated());

    subject.logout();
}

The realm worked only after you used it.

JDBC Realm

  • Add jars info your pom.xml, here I user MySQL and druid datasource for test.
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.25</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>0.2.23</version>
</dependency>
  • Here are some sql to init database.
DROP DATABASE IF EXISTS shiro;
CREATE DATABASE shiro;
USE shiro;

CREATE TABLE users (
  id            BIGINT AUTO_INCREMENT,
  username      VARCHAR(100),
  password      VARCHAR(100),
  password_salt VARCHAR(100),
  CONSTRAINT pk_users PRIMARY KEY (id)
)
  CHARSET = utf8
  ENGINE = InnoDB;
CREATE UNIQUE INDEX idx_users_username ON users (username);

CREATE TABLE user_roles (
  id        BIGINT AUTO_INCREMENT,
  username  VARCHAR(100),
  role_name VARCHAR(100),
  CONSTRAINT pk_user_roles PRIMARY KEY (id)
)
  CHARSET = utf8
  ENGINE = InnoDB;
CREATE UNIQUE INDEX idx_user_roles ON user_roles (username, role_name);

CREATE TABLE roles_permissions (
  id         BIGINT AUTO_INCREMENT,
  role_name  VARCHAR(100),
  permission VARCHAR(100),
  CONSTRAINT pk_roles_permissions PRIMARY KEY (id)
)
  CHARSET = utf8
  ENGINE = InnoDB;
CREATE UNIQUE INDEX idx_roles_permissions ON roles_permissions (role_name, permission);

INSERT INTO users (username, password) VALUES ('wang', '123');
INSERT INTO users (username, password) VALUES ('ryo', '123');
  • shiro-jdbc-realm.ini
[main]
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
dataSource=com.alibaba.druid.pool.DruidDataSource
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://localhost:3307/shiro
dataSource.username=root
dataSource.password=${youOwnSQLPassword}
jdbcRealm.dataSource=$dataSource
securityManager.realms=$jdbcRealm


;1、varName=className    auto create an instance of class.
;2、varName.property=val         auto call the set()
;3、$varname             reference an object define before;
  • test()
@Test
public void testJDBCRealm() {
    Factory<org.apache.shiro.mgt.SecurityManager> factory =
            new IniSecurityManagerFactory("classpath:shiro-jdbc-realm.ini");

    org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
    SecurityUtils.setSecurityManager(securityManager);

    Subject subject = SecurityUtils.getSubject();
    UsernamePasswordToken token = new UsernamePasswordToken("ryo", "123");

    try {
        subject.login(token);
    } catch (AuthenticationException e) {
        e.printStackTrace();
    }

    Assert.assertEquals(true, subject.isAuthenticated());

    subject.logout();
}

Authenticator

  • In a single-realm application, the ModularRealmAuthenticator will invoke the single Realm directly.

  • If you wish to configure the SecurityManager with a custom Authenticator implementation, you can do so in shiro.ini for example:

[main]
authenticator = com.foo.bar.CustomAuthenticator

securityManager.authenticator = $authenticator

SecurityManager.java

public interface SecurityManager extends Authenticator, Authorizer, SessionManager {
}

Authenticator.java

public interface Authenticator {
    AuthenticationInfo authenticate(AuthenticationToken var1) throws AuthenticationException;
}

AuthenticationStrategy

When two or more realms are configured for an application, the ModularRealmAuthenticator relies on an internal AuthenticationStrategy component to determine the conditions for which an authentication attempt succeeds or fails.

AuthenticationStrategy class Description
AtLeastOneSuccessfulStrategy If one (or more) Realms authenticate successfully, the overall attempt is considered successful. If none authenticate succesfully, the attempt fails.
FirstSuccessfulStrategy Only the information returned from the first successfully authenticated Realm will be used. All further Realms will be ignored. If none authenticate successfully, the attempt fails.
AllSuccessfulStrategy All configured Realms must authenticate successfully for the overall attempt to be considered successful. If any one does not authenticate successfully, the attempt fails.

1、Here I define three realm for test;

  • FirstRealm ryo/123 success, return ryo/123

  • SecondRealm wang/123 success, return wang/123

  • ThirdRealm ryo/123 success, return ryo@gmail.com/123

2、shiro-authenticator-all-success.ini

The ModularRealmAuthenticator defaults to the AtLeastOneSuccessfulStrategy implementation, as this is the most commonly desired strategy. However, you could configure a different strategy if you wanted.

[main]
#point out securityManager's authenticator  
authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator  
securityManager.authenticator=$authenticator  
  
#Point out securityManager.authenticator's authenticationStrategy  
allSuccessfulStrategy=org.apache.shiro.authc.pam.AllSuccessfulStrategy  
securityManager.authenticator.authenticationStrategy=$allSuccessfulStrategy  

#Define and use realms
firstRealm=com.ryo.shiro.FirstRealm
secondRealm=com.ryo.shiro.SecondRealm
thirdRealm=com.ryo.shiro.ThirdRealm
securityManager.realms=$firstRealm,$thirdRealm

3、AuthenticatorTest.java

@Test
public void testAllSuccessfulStrategyWithSuccess() {
    Subject subject = getSubjectByPath("classpath:shiro-authenticator-all-success.ini");

    UsernamePasswordToken token = new UsernamePasswordToken("ryo", "123");
    subject.login(token);

    PrincipalCollection principalCollection = subject.getPrincipals();
    assertEquals("ryo,ryo@gmail.com", principalCollection.toString());
}

private Subject getSubjectByPath(String configFilePath) {
    Factory<SecurityManager> factory = new IniSecurityManagerFactory(configFilePath);

    SecurityManager securityManager = factory.getInstance();
    SecurityUtils.setSecurityManager(securityManager);
    return SecurityUtils.getSubject();
}

If you wanted to create your own AuthenticationStrategy implementation yourself, you could use the org.apache.shiro.authc.pam.AbstractAuthenticationStrategy as a starting point.

  • OnlyOneAuthenticatorStrategy.java
public class OnlyOneAuthenticatorStrategy extends AbstractAuthenticationStrategy {
    //Simply returns the aggregate argument without modification.
    @Override
    public AuthenticationInfo beforeAllAttempts(Collection<? extends Realm> realms, AuthenticationToken token) throws AuthenticationException {
        return super.beforeAllAttempts(realms, token);
    }

    //Simply returns the aggregate method argument, without modification.
    @Override
    public AuthenticationInfo beforeAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo aggregate) throws AuthenticationException {
        return super.beforeAttempt(realm, token, aggregate);
    }

    /**
     * Base implementation that will aggregate the specified singleRealmInfo into the aggregateInfo and then returns the aggregate.
     * @param realm the realm that was just consulted for AuthenticationInfo for the given token.
     * @param token the AuthenticationToken submitted for the subject attempting system log-in.
     * @param singleRealmInfo the info returned from a single realm.    单个realm信息
     * @param aggregateInfo the aggregate info representing all realms in a multi-realm environment.    总信息
     * @param t the Throwable thrown by the Realm during the attempt, or null if the method returned normally.
     * @return
     * @throws AuthenticationException
     */
    @Override
    public AuthenticationInfo afterAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo singleRealmInfo, AuthenticationInfo aggregateInfo, Throwable t) throws AuthenticationException {
        AuthenticationInfo info;

        if(singleRealmInfo == null) {
            info = aggregateInfo;
        } else if(aggregateInfo == null) {
            info = singleRealmInfo;
        } else {
            info = merge(singleRealmInfo, aggregateInfo);

            if(info.getPrincipals().getRealmNames().size() > 1) {
                throw new AuthenticationException("Authentication token of type [" + token.getClass() + "] " +
                        "could not be authenticated by any configured realms.  Please ensure that only one realm can " +
                        "authenticate these tokens.");
            }
        }

        return info;
    }

    //Merges the specified info argument into the aggregate argument and then returns an aggregate for continued use throughout the login process.
    @Override
    protected AuthenticationInfo merge(AuthenticationInfo info, AuthenticationInfo aggregate) {
        return super.merge(info, aggregate);
    }

    //Base implementation that will aggregate the specified singleRealmInfo into the aggregateInfo and then returns the aggregate.
    @Override
    public AuthenticationInfo afterAllAttempts(AuthenticationToken token, AuthenticationInfo aggregate) throws AuthenticationException {
        return super.afterAllAttempts(token, aggregate);
    }
}
  • shiro-authenticator-onlyone-success.ini
[main]
authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator
securityManager.authenticator=$authenticator

onlyOneAuthenticatorStrategy=com.ryo.shiro.authenticator.strategy.OnlyOneAuthenticatorStrategy
securityManager.authenticator.authenticationStrategy=$onlyOneAuthenticatorStrategy

#define and uer realms.
firstRealm=com.ryo.shiro.FirstRealm
secondRealm=com.ryo.shiro.SecondRealm
securityManager.realms=$firstRealm,$secondRealm
  • test()
@Test
    public void testOnlyOneAuthenticatorStrategy() {
        Subject subject = getSubjectByPath("classpath:shiro-authenticator-onlyone-success.ini");

        UsernamePasswordToken token = new UsernamePasswordToken("ryo", "123");
        subject.login(token);

        PrincipalCollection principalCollection = subject.getPrincipals();
        assertEquals("ryo", principalCollection.toString());
    }
  • if you change the shiro-authenticator-onlyone-success.ini into
#define and uer realms.
firstRealm=com.ryo.shiro.FirstRealm
thirdRealm=com.ryo.shiro.ThirdRealm
securityManager.realms=$firstRealm,$thirdRealm

You will get an error as following.

org.apache.shiro.authc.AuthenticationException: Authentication token of type [class org.apache.shiro.authc.UsernamePasswordToken] 
could not be authenticated by any configured realms.  Please ensure that only one realm can authenticate these tokens.