目录

Apache-Shiro基本使用指南

Apache Shiro基本使用指南

前言

在Java Web开发中,安全框架的选择一直是开发者关注的重点。Spring Security功能强大但配置复杂,而Apache Shiro则以其简单易用、功能完整而受到广大开发者的喜爱。今天我们就来详细了解Shiro的基本使用,看看如何快速上手这个优秀的安全框架。

Shiro简介

Apache Shiro是一个功能强大且易于使用的Java安全框架,它可以帮助我们完成认证、授权、加密、缓存和会话管理等安全相关的功能。

Shiro的核心概念

  • Subject(主体):当前用户,不一定是具体的人,也可能是第三方服务
  • SecurityManager(安全管理器):Shiro的核心,协调各个组件工作
  • Realm(域):数据源,负责获取安全数据(用户、角色、权限)

环境搭建

首先,我们需要在项目中引入Shiro的相关依赖。以Maven项目为例:

<dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
    <!-- Shiro核心依赖 -->
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-core</artifactId>
      <version>1.3.2</version>
    </dependency>
    <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.2</version>
    </dependency>
</dependencies>

快速入门示例

让我们从一个最简单的示例开始,了解Shiro的基本工作流程。

1. 创建shiro.ini配置文件

在resources目录下创建shiro.ini文件:

[users]
# 用户名=密码,角色1,角色2
zhang=123,admin
wang=456,user
li=789,user,manager

[roles]
# 角色=权限1,权限2
admin=*
user=user:read,user:create
manager=user:*,order:read

2. 编写测试代码

public class ShiroQuickStart {
    
    public static void main(String[] args) {
        // 1. 获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        
        // 2. 得到SecurityManager实例并绑定给SecurityUtils
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        
        // 3. 得到Subject及创建用户名/密码身份验证Token
        Subject currentUser = SecurityUtils.getSubject();
        
        // 4. 登录
        if (!currentUser.isAuthenticated()) {
            UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");
            token.setRememberMe(true); 
            
            try {
                currentUser.login(token);
                logger.info("用户登录成功");
            } catch (UnknownAccountException uae) {
                logger.error("用户不存在: " + token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {
                logger.error("密码错误: " + token.getPrincipal());
            } catch (LockedAccountException lae) {
                logger.error("账户被锁定: " + token.getPrincipal());
            } catch (AuthenticationException ae) {
                logger.error("认证失败");
            }
        }
        
        // 5. 权限检查
        if (currentUser.hasRole("admin")) {
            logger.info("用户拥有admin角色");
        } else {
            logger.info("用户没有admin角色");
        }
        
        // 检查具体权限
        if (currentUser.isPermitted("user:create")) {
            logger.info("用户有创建用户的权限");
        } else {
            logger.info("用户没有创建用户的权限");
        }
        
        // 6. 退出登录
        currentUser.logout();
    }
}

自定义Realm

在实际项目中,我们通常需要从数据库获取用户信息,这时就需要自定义Realm。

创建shiro.ini配置文件

[main]
#声明realm
myClass = com.coldscholor.shiro.MyRealm
#注册realm到securityManager中
securityManager.realm = $myClass

创建自定义Realm类

/**
 * Realm域:Shiro从Realm获取安全数据(如用户、角色、权限),
 * 就是说SecurityManager要验证用户身份,那么它需要从Realm
 * 获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm
 * 得到用户相应的角色/权限进行验证用户是否能进行操作;可以把
 * Realm看成DataSource,即安全数据源
 * <p>
 * 自定义realms对象
 * 继承AuthorizingRealm
 * 重写方法
 * doGetAuthorizationInfo:授权
 * 获取到用户的授权数据(用户的权限数据)
 * doGetAuthenticationInfo:认证
 * 根据用户名密码登录,将用户数据保存(安全数据)
 */
public class MyRealm extends AuthorizingRealm {
    public MyRealm() {
        // 设置密码匹配器
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(DigestsUtil.SHA1);
        // 设置迭代次数
        matcher.setHashIterations(DigestsUtil.COUNTS);
        // 设置加密方式
        setCredentialsMatcher(matcher);
    }

    /**
     * 授权
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("授权");
        // 获取用户名
        String username = (String) principalCollection.getPrimaryPrincipal();
        System.out.println("用户名:" + username);
        // 用户的权限数据
        List<String> perms = new ArrayList<>();
        perms.add("save");
        perms.add("update");
        perms.add("delete");
        perms.add("find");

        // 用户的角色数据
        List<String> roles = new ArrayList<>();
        roles.add("admin");
        roles.add("user");

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermissions(perms);
        info.addRoles(roles);
        return info;
    }


    /**
     * 认证
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    /*@Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("认证");
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        String username = token.getUsername();
        String password = new String(token.getPassword());
        if ("admin".equals(username) && "123456".equals(password)) {
            // 参数:用户名,密码,当前realm对象的名称
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password, getName());
            return info;
        } else {
            throw new RuntimeException("用户名或密码错误,认证失败");
        }
    }*/


    /**
     * 对密文密码进行解密并认证
     * salt:是一个随机字符串,用于对密码进行加密
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("认证");
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        String username = token.getUsername();
        SecurityService securityService = new SecurityServiceImpl();
        Map<String,String> map = securityService.findPasswordByLoginName(username);
        if(map == null){
            throw new RuntimeException("用户不存在");
        }
        String salt = map.get("salt");
        String password =  map.get("password");
        //参数1:安全数据   参数2:密码   参数3:混淆字符串  参数4:realm名称
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password, ByteSource.Util.bytes(salt), getName());
        return info;
    }
}

测试结果

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
认证
登录结果:true

总结

Apache Shiro作为一个轻量级的安全框架,具有简单易用、功能完整的特点。在实际项目中,建议根据具体的业务需求选择合适的配置方式,并结合缓存、异常处理等机制,构建一个安全、高效的权限管理系统。Shiro虽然简单,但功能强大。掌握了这些基本用法,相信你已经能够在项目中熟练使用Shiro来处理安全相关的需求了。