Realm

  • doGetAuthenticationInfo()和getAuthenticationInfo()一样,都是做用户验证和返回身份凭证的。
  • 从调用链上,getAuthorizationInfo()会调用doGetAuthorizationInfo(),所以大部分时候是重写后者。

AuthenticationToken

  • 收集用户提交的身份信息(如用户名和凭据(如密码))的接口。

    public interface AuthenticationToken extends Serializable {
        Object getPrincipal(); //身份
        Object getCredentials(); //凭据
    };
    
  • 扩展接口RememberMeAuthenticationToken:提供boolean isRememberMe()实现记住我功能。
  • 扩展接口HostAuthenticationToken:提供String getHost()获取用户主机。
  • 内置实现类UsernamePasswordToken:仅保存用户名、密码,并实现了以上两个接口,可以实现记住我和主机验证的支持。

AuthenticationInfo

  • 封装验证通过的身份信息,主要包括Object属性principal(一般存储用户名)和credentials(密码)。
  • MergableAuthenticationInfo子接口:在多Realm时合并AuthenticationInfo,主要合并Principal,如果是其他信息如credentialsSalt,则会后合并进来的AuthenticationInfo覆盖。
  • SaltedAuthenticationInfo子接口:比如HashedCredentialsMatcher,在验证时会判断AuthenticationInfo是否是SaltedAuthenticationInfo的子类,是则获取其盐。
  • Account子接口:相当于我们之前的[users],SimpleAccount是其实现。在IniRealm、PropertiesRealm这种静态创建账号的场景中使用,它们继承了SimpleAccountRealm,其中就有API用于增删查改SimpleAccount。适用于账号不是特别多的情况。
  • SimpleAuthenticationInfo:一般都是返回这个类型。

PincipalCollection

  • Principal前缀:应该是上面AuthenticationInfo的属性principal。
  • PincipalCollection:是一个身份集合,保存登录成功的用户的身份信息。因为我们可以在Shiro中同时配置多个Realm,所以身份信息就有多个。可以传给doGetAuthorizationInfo()方法为登录成功的用户授权。
public interface PrincipalCollection extends Iterable, Serializable {
    Object getPrimaryPrincipal(); //得到主要的身份

    <T> T oneByType(Class<T> type); //根据身份类型获取第一个

    <T> Collection<T> byType(Class<T> type); //根据身份类型获取一组

    List asList(); //转换为List

    Set asSet(); //转换为Set

    Collection fromRealm(String realmName); //根据Realm名字获取

    Set<String> getRealmNames(); //获取所有身份验证通过的Realm名字

    boolean isEmpty(); //判断是否为空
};
  • 其中,getPrimaryPrincipal()时,如果只有一个Principal,那么直接返回即可,但若有多个则返回第一个,由于其底层由Map存储,所以第一个也就是任意一个。
  • oneByType() 、 byType():根据凭据的类型返回对应的Principal。
  • fromRealm():根据Realm名字返回对应的Principal。
  • 因为此例中就一个Realm,所以直接调用getPrimaryPrincipal得到之前传入的用户名即可。
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
        PrincipalCollection principals) {
    //PrincipalCollection是一个身份集合,因为我们现在就一个Realm,
    //所以直接调用getPrimaryPrincipal得到之前传入的用户名即可
    String username=(String)principals.getPrimaryPrincipal();
    ...
}
  • MutablePrincipalCollection:可变的PrincipalCollection接口,提供了如下可变方法
public interface MutablePrincipalCollection extends PrincipalCollection {
    void add(Object principal, String realmName); //添加Realm-Principal的关联

    void addAll(Collection principals, String realmName); //添加一组Realm-Principal的关联

    void addAll(PrincipalCollection principals);//添加PrincipalCollection

    void clear();//清空
}
  • SimplePrincipalCollection:MutablePrincipalCollection唯一实现类。在继承了AbstractAuthenticationStrategy的验证策略中,afterAttemp()会调用SimplePrincipalCollection的merge()将多个Principal合并到一个PrincipalCollection中。

PrincipalCollection示例

  • 准备三个Realm,命名分别为a,b,c,身份凭证只有细微差别。
public class MyRealm1 implements Realm {
    @Override
    public String getName() {
        return "a"; //realm name 为 “a”
    }

    @Override
    public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {
        return new SimpleAuthenticationInfo(
                "zhang", //身份 字符串类型
                "123",   //凭据
                getName() //Realm Name
        );
    }
}

//和1完全一样,只是命名为b
public class MyRealm2 implements Realm {
    @Override
    public String getName() {
        return "b"; //realm name 为 “b”
    }

    @Override
    public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {
        return new SimpleAuthenticationInfo(
                "zhang", //身份 字符串类型
                "123",   //凭据
                getName() //Realm Name
        );
    }
}

//除了命名不同,只是Principal类型为User,而不是简单的String
public class MyRealm3 implements Realm {
    @Override
    public String getName() {
        return "c"; //realm name 为 “c”
    }

    @Override
    public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {
        User user=new User("zhang","123");
        return new SimpleAuthenticationInfo(
                user, //身份 User类型
                "123",   //凭据
                getName() //Realm Name
        );
    }
}
  • shiro-multirealm.ini
[main]
realm1=com.haien.shiroHelloWorld.principalTest.realm.MyRealm1
realm2=com.haien.shiroHelloWorld.principalTest.realm.MyRealm2
realm3=com.haien.shiroHelloWorld.principalTest.realm.MyRealm3
securityManager.realms=$realm1,$realm2,$realm3
  • PrincipalCollectionTest:测试方法
public class PrincipalCollectionTest extends BaseTest {
    @Test
    public void testPrincipalCollection(){
        login("classpath:config/shiro-multirealm.ini",
                "zhang","123");

        Subject subject=subject();

        //获取Map中第一个Principal,即PrimaryPrincipal
        Object primaryPrincipal1=subject.getPrincipal();
        //获取PrincipalCollection
        PrincipalCollection principalCollection=subject.getPrincipals();
        //也是获取PrimaryPrincipal
        Object primaryPrincipal2=principalCollection.getPrimaryPrincipal();

        //获取所有身份验证成功的Realm名字
        Set<String> realmNames=principalCollection.getRealmNames();
        for(String realmName:realmNames)
            System.out.println(realmName);

        //将身份信息转换为Set/List(实际转换为List也是先转为Set)
        List<Object> principals=principalCollection.asList();
        /*返回集合包含两个String类、一个User类,但由于两个String类都是"zhang",
        所以只只剩下一个,转为List结果也是一样*/
        for(Object principal:principals)
            System.out.println("set:"+principal);

        //根据realm名字获取身份,因为realm名字可以重复,
        //所以可能有多个身份,建议尽量不要重复
        Collection<User> users=principalCollection.fromRealm("c");
        for(User user:users)
            System.out.println("c:user="+user.getUsername()+user.getPassword());
        Collection<String> usernames=principalCollection.fromRealm("b");
        for(String username:usernames)
            System.out.println("b:username="+username);
    }
}
  • 代码实例:ideaProjects/shiroHelloWorld/PrincipalTest

AuthorizationInfo

  • 封装权限信息,主要是doGetAuthorizationInfo()时封装授权信息然后返回的。
public interface AuthorizationInfo extends Serializable {
    Collection<String> getRoles(); //获取角色字符串信息

    Collection<String> getStringPermissions(); //获取权限字符串信息

    Collection<Permission> getObjectPermissions(); //获取Permission对象信息
}
  • SimpleAuthorizationInfo:实现类,大多数时候使用这个。主要增加了以下方法:
authorizationInfo.addRole("role1"); //添加角色到内部维护的role集合;
    添加角色后调用MyRolePermissionResolver解析出权限
authorizationInfo.setRoles(Set<String> roles); //将内部维护的role集合设置为入参

authorizationInfo.addObjectPermission(new BitPermission("+user1+10")); //添加对象型权限
authorizationInfo.addObjectPermission(new WildcardPermission("user1:*"));
authorizationInfo.addStringPermission("+user2+10"); //字符串型权限
authorizationInfo.addStringPermission("user2:*");
authorizationInfo.setStringPermissions(Set<String> permissions);

Subject

  • Shiro核心对象,基本所有身份验证、授权都是通过Subject完成的。
//获取身份信息
Object getPrincipal(); //Primary Principal
PrincipalCollection getPrincipals(); // PrincipalCollection

//身份验证
void login(AuthenticationToken token) throws AuthenticationException; //调用各种方法;
    登录失败抛AuthenticationException,成功则调用isAuthenticated()返回true
boolean isAuthenticated(); //与isRemembered()一个为true一个为false
boolean isRemembered(); //返回true表示是通过记住我登录到额而不是调用login方法

//角色验证
boolean hasRole(String roleIdentifier); //返回true或false表示成功与否
boolean[] hasRoles(List<String> roleIdentifiers);
boolean hasAllRoles(Collection<String> roleIdentifiers);
void checkRole(String roleIdentifier) throws AuthorizationException; //失败抛异常
void checkRoles(Collection<String> roleIdentifiers) throws AuthorizationException;
void checkRoles(String... roleIdentifiers) throws AuthorizationException;

//权限验证
boolean isPermitted(String permission);
boolean isPermitted(Permission permission);
boolean[] isPermitted(String... permissions);
boolean[] isPermitted(List<Permission> permissions);
boolean isPermittedAll(String... permissions);
boolean isPermittedAll(Collection<Permission> permissions);
void checkPermission(String permission) throws AuthorizationException;
void checkPermission(Permission permission) throws AuthorizationException;
void checkPermissions(String... permissions) throws AuthorizationException;
void checkPermissions(Collection<Permission> permissions) throws AuthorizationException;

//会话(登录成功相当于建立了会话,然后调用getSession获取
Session getSession(); //相当于getSession(true)
Session getSession(boolean create); //当create=false,如果没有会话将返回null,
    当create=true,没有也会强制创建一个

//退出
void logout();

//RunAs
void runAs(PrincipalCollection principals) 
    throws NullPointerException, IllegalStateException; //实现允许A作为B进行访问,
    调用runAs(b)即可
boolean isRunAs(); //此时此方法返回true
PrincipalCollection getPreviousPrincipals(); //得到a的身份信息,
    而getPrincipals()得到b的身份信息
PrincipalCollection releaseRunAs(); //不需要了RunAs则调用这个

//多线程
<V> V execute(Callable<V> callable) throws ExecutionException;
void execute(Runnable runnable);
<V> Callable<V> associateWith(Callable<V> callable);
Runnable associateWith(Runnable runnable);
  • Subject的获取:一般不需要我们创建,直接通过SecurityUtils获取即可
public static Subject getSubject() {
    Subject subject = ThreadContext.getSubject();
    if (subject == null) {
        subject = (new Subject.Builder()).buildSubject();
        ThreadContext.bind(subject);
    }
    return subject;
}
  • 首先查看当前线程是否绑定了Subject,没有则通过Subject.BUilder构建一个并绑定到线程返回。
  • 如果想自定义Subject实例的创建,代码如下:
new Subject.Builder().principals(身份).authenticated(true/false).buildSubject()
  • 然后自己绑定到线程即可。在new Subject.Builder()时如果没有传入SecurityManager,则自动调用SecurityUtils.getsecurityManager()获取一个默认实现类的对象。
  • Subject一般用法
  1. 身份验证login()
  2. 授权hasRole*()/isPermitted*/checkRole*()/checkPermission*()
  3. 将相应的数据存储到会话Session
  4. 切换身份RunAs/多线程身份传播
  5. 退出
  • 完整用户登录认证与授权示例:ideaProjects/shiroHelloWorld/chapter6、src/test/UserRealmTest为主测试