Shiro【自定义Realm实战】

一、前言

虽然 Shiro 给我们提供了很多内置的 Realm,但在企业开发中,这往往不适用于项目中。

所以,都需要自定义 Realm 来使用。

二、步骤

  1. 创建一个类 ,继承 AuthorizingRealm

​ (继承关系:自定义Realm->AuthorizingRealm->AuthenticatingRealm->CachingRealm->Realm)

  1. 重写认证方法 doGetAuthenticationInfo

​ 重写授权方法 doGetAuthorizationInfo

  1. 在方法中分别返回对应的对象

​ SimpleAuthenticationInfo :代表该用户的认证信息
​ SimpleAuthorizationInfo:代表用户角色权限信息

三、代码实现

注:

本文的案例中没有从数据库中查询真实的数据,所以使用集合模拟的数据。

在实际开发中,只需要 service 层写好的方法从数据库查询数据即可。

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * 自定义的 Redlm 步骤:
 * 1.继承AuthorizingRealm
 * 2.重写授权方法 doGetAuthorizationInfo
 *   重写认证方法 doGetAuthenticationInfo
 * 3.在方法中分别返回对应的对象
 *   SimpleAuthorizationInfo:代表用户角色权限信息
     SimpleAuthenticationInfo :代表该用户的认证信息
 */
public class CustomRealm extends AuthorizingRealm {

    private final Map<String,String> userInfoMap = new HashMap<>();
    {
        userInfoMap.put("jack","123");
        userInfoMap.put("xdclass","456");
    }

    //user -> role
    private final Map<String,Set<String>> roleMap = new HashMap<>();
    {
        Set<String> set1 = new HashSet<>();
        Set<String> set2 = new HashSet<>();

        set1.add("role1");
        set1.add("role2");

        set2.add("root");

        roleMap.put("jack",set1);
        roleMap.put("xdclass",set2);
    }

    //role -> permission
    private final Map<String,Set<String>> permissionMap = new HashMap<>();
    {
        Set<String> set1 = new HashSet<>();
        Set<String> set2 = new HashSet<>();

        set1.add("video:find");
        set1.add("video:buy");

        set2.add("video:add");
        set2.add("video:delete");

        permissionMap.put("jack",set1);
        permissionMap.put("xdclass",set2);
    }

    /**
     * 执行认证操作时,SecurityManager 会执行此方法
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // UsernamePasswordToken -> HostAuthenticationToken -> AuthenticationToken
        // 在实际开发的时候,authenticationToken是需要前端传递过来的用户输入的账号和密码

        // 获取用户唯一标识(俗称“账号”)
        String name = (String)authenticationToken.getPrincipal();
        // 根据账号从数据库中获取密码
        String pwd = getPwdByUserNameFromDB(name);
        if( pwd == null || "".equals(pwd)){
            return null;
        }
        // 将账号和密码封装到 SimpleAuthenticationInfo 对象中进行返回
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, pwd, this.getName());

        return simpleAuthenticationInfo;
    }

    /**
     * 执行授权操作时,SecurityManager 会执行此方法
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // 获取“账号”
        String name = (String)principals.getPrimaryPrincipal();

        // 模拟从数据库中获取角色
        Set<String> roles = getRolesByNameFromDB(name);

        // 模拟从数据库中获取权限
        //(另外,此处本来是应该的根据角色获取权限的,但因为本来也只是模拟,所以为了简便,则直接根据账号获取的权限)
        Set<String> permissions = getPermissionsByNameFromDB(name);

        // 将角色和权限等信息封装到 SimpleAuthorizationInfo 进行返回
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.setRoles(roles);
        simpleAuthorizationInfo.setStringPermissions(permissions);

        return simpleAuthorizationInfo;
    }

    /**
     * 模拟从数据库中获取密码
     * @param name
     * @return
     */
    private String getPwdByUserNameFromDB(String name) {
        return userInfoMap.get(name);
    }

    /**
     * 模拟从数据库获取用户角色集合
     * @param name
     * @return
     */
    private Set<String> getRolesByNameFromDB(String name) {
        return roleMap.get(name);

    }

    /**
     *  模拟从数据库获取权限集合
     * @param name
     * @return
     */
    private Set<String> getPermissionsByNameFromDB(String name) {
        return permissionMap.get(name);
    }

}

测试代码如下:

/**
 * 测试自定义的Realm
 */
public class CustomRealmTest {

    private CustomRealm customRealm = new CustomRealm();
    private DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();

    @Before
    public void init(){
        //构建环境
        defaultSecurityManager.setRealm(customRealm);
        SecurityUtils.setSecurityManager(defaultSecurityManager);
    }

    @Test
    public void testAuthentication() {
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("jack", "123");

        Subject subject = SecurityUtils.getSubject();
        subject.login(usernamePasswordToken);

        System.out.println(" 认证结果:"+subject.isAuthenticated());
        System.out.println(" getPrincipal=" + subject.getPrincipal());
        System.out.println("是否有对应的角色:"+subject.hasRole("role1"));
        System.out.println("是否有对应的权限:"+subject.isPermitted("video:add"));
    }
}

四、代码解析

(一)方法

当用户登陆的时候会调用 doGetAuthenticationInfo

进行权限校验的时候会调用: doGetAuthorizationInfo

(二)代码中的类(对象)
  1. UsernamePasswordToken :用来封装用户(Subject)传递过来的账号和密码

​ (继承体系:UsernamePasswordToken -> HostAuthenticationToken -> AuthenticationToken)

  1. SimpleAuthenticationInfo :用来封装用户的认证信息

  2. SimpleAuthorizationInfo:用来封装用户的角色权限信息

五、认证和授权流程源码分析

在编辑器中通过 Debug 方式则能分析出认证和授权的大致流程如下:

(一)认证
认证流程解读:
    subject.login(usernamePasswordToken);
    DelegatingSubject->login()
    DefaultSecurityManager->login()
    AuthenticatingSecurityManager->authenticate()
    AbstractAuthenticator->authenticate()
    ModularRealmAuthenticator->doAuthenticate()
    ModularRealmAuthenticator->doSingleRealmAuthentication()
    AuthenticatingRealm->getAuthenticationInfo()
(二)授权
授权流程解读(查询角色):
    subject.checkRole("admin")
    DelegatingSubject->checkRole()
    AuthorizingSecurityManager->checkRole()
    ModularRealmAuthorizer->checkRole()
    AuthorizingRealm->hasRole()
    AuthorizingRealm->doGetAuthorizationInfo()
授权流程解读(查询权限):
subject.isPermitted("具体权限")
	DelegatingSubject->isPermitted()
	AuthorizingSecurityManager->isPermitted()
	ModularRealmAuthorizer->isPermitted()
	AuthorizingRealm->isPermitted()
	AuthorizingRealm->getAuthorizationInfo()
	CustomRealm->doGetAuthorizationInfo

六、总结

Realm 就是用来从数据库中获取用户的角色、权限等数据的,不要被这个名字给吓唬到。

Java新手,若有错误,欢迎指正!

原文地址:https://www.cnblogs.com/Java-biao/p/14480684.html