30分钟了解Shiro与Springboot的多Realm基础配置

写在前面的话:

我之前写过两篇与shiro安全框架有关的博文,居然能够广受欢迎实在令人意外。说明大家在互联网时代大伙对于安全和登录都非常重视,无论是大型项目还是中小型业务,普遍都至少需要登录与认证的逻辑封装。相较于SpringSecurity而言,Shrio更轻量无过多依赖和便于独立部署的特点更收到开发者的欢迎。本篇博客只作为前两篇对于shiro使用的基础补充,我只谈谈如何在springboot项目中配置多角色验证。

一、场景介绍

假设在我们的项目中需要有前台用户登录和后台系统管理员登录两种验证方式。当然在传统的业务逻辑中用户和管理员可以使用不同的角色加以区分,假设现在的逻辑是用户与系统管理员分别保存在不同的表中并且也分别对应了不同的角色(role)与权限(permission)。换句话说,从业务上看相同的用户名和密码如果是在前台页面登录可能对应的是普通用户,从后台登录则对应某板块的系统管理。面对这样的需求我们可以在shiro框架中配置多个realm,再配合上认证策略来执行。

二、代码讲解

与单一realm相同,首先根据不同的登录认证要求创建不同的realm。这里只提供作为后台系统管理员的realm代码实例:

// 系统管理员专用
public class AdministratorRealm extends AuthorizingRealm {
    @Autowired
    private AdministratorRegisterService administratorRegisterService;
    @Autowired
    private PasswordSupport passwordSupport;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        String username = (String) principals.getPrimaryPrincipal();
        Administrator administrator = administratorRegisterService.getAdministratorByName(username);
        for (Role r : administrator.getRoles()) {
            authorizationInfo.addRole(r.getRoleName());
        }
        return authorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String username = (String) token.getPrincipal();
        Administrator administrator = administratorRegisterService.getAdministratorByName(username);
        if (administrator == null) {
            throw new UnknownAccountException();
        }
        if (administrator.isLocked()) {
            throw new LockedAccountException();
        }
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(administrator.getUsername(),
                administrator.getPassword(), ByteSource.Util.bytes(passwordSupport.credentialsSalt(administrator)),
                getName());

        return authenticationInfo;
    }

}

doGetAuthenticationInfo方法负责用户名和密码验证,doGetAuthorizationInfo方法负责角色和权限的分配, passwordSupport是一个自定义的类负责password加密和生成salt功能。

/**
 * 密码辅助方法
 * 
 * @author learnhow
 *
 */
@Component
public class PasswordSupport {
    public static final String ALGORITHM_NAME = "md5";
    public static final int HASH_ITERATIONS = 2;/**
     * 针对系统管理生成salt和加密密码
     * 
     * @param administrator
     */
    public void encryptPassword(Administrator administrator) {
        administrator.setSalt(new SecureRandomNumberGenerator().nextBytes().toHex());
        String newPassword = new SimpleHash(ALGORITHM_NAME, administrator.getPassword(),
                ByteSource.Util.bytes(credentialsSalt(administrator)), HASH_ITERATIONS).toHex();
        administrator.setPassword(newPassword);
    }/**
     * 对Administrator的salt生成规则
     * 
     * @param administrator
     * @return
     */
    public String credentialsSalt(Administrator administrator) {
        return administrator.getSalt() + administrator.getEmail();
    }
}

AdministratorRegisterService是服务组件负责通过name从数据库中查询。

在Shiro中无论是单一realm还是多个realm都需要对SecurityManager进行配置。

@Configuration
public class ShiroConfig {
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName(PasswordSupport.ALGORITHM_NAME);
        hashedCredentialsMatcher.setHashIterations(PasswordSupport.HASH_ITERATIONS);
        return hashedCredentialsMatcher;
    }

    @Bean
    public AdministratorRealm getAdministatorRealm() {
        AdministratorRealm realm = new AdministratorRealm();
        realm.setName("AdministratorRealm");
        realm.setCredentialsMatcher(hashedCredentialsMatcher());
        return realm;
    }

    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator();
        modularRealmAuthenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy());
        securityManager.setAuthenticator(modularRealmAuthenticator);

        List<Realm> realms = new ArrayList<>();
        // TODO-多个realms进配置在这里 
        realms.add(getAdministatorRealm());
        securityManager.setRealms(realms);
        return securityManager;
    }
}

ModularRealmAuthenticator的setAuthenticationStrategy方法中配置认证策略。Shiro提供了三种策略:AllSuccessFulStrategy, AtLeastOneSuccessFulAtrategy, FirstSuccessFulStrategy,默认使用AtLeastOneSuccessFulAtrategy,通常不需要特别配置。

三、注意事项

1.多realm认证只会抛出AuthenticationException,因此如果要想在外部判断到底是在认证的哪一步发生的错误需要自己定义一些异常类型。

2.shiro没有提供根据条件指定realm的功能,如果需要实现这样的功能只能通过继承与重写来实现,这里没有涉及需要深入探讨的同学最好根据自己的实际情况专门研究。

 

写在后面的话:

最近有不少朋友在看了我的博客以后加我的QQ或者发邮件要求提供演示源码,为了方便交流我索性建了一个技术交流群,今后有些源码我可能就放群资料里面了。当然之前的一些东西还在补充中,有些问题也希望大伙能共同交流。QQ群号:960652410

posted @ 2018-12-09 12:20  冷豪  阅读(4425)  评论(0编辑  收藏  举报