Shiro 基础
    2021-04-18

    Apache Shiro(日语“堡垒(Castle)”的意思)是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理功能,可为任何应用提供安全保障 - 从命令行应用、移动应用到大型网络及企业应用。


    一、简介

    1.1 什么是Shiro

    Apache Shiro(日语“堡垒(Castle)”的意思)是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理功能,可为任何应用提供安全保障 - 从命令行应用、移动应用到大型网络及企业应用。

    Apache Shiro 是一个强大而灵活的开源安全框架,它干净利落地处理身份认证,授权,企业会话管理和加密。 Apache Shiro 的首要目标是易于使用和理解。安全有时候是很复杂的,甚至是痛苦的,但它没有必要这样。框架应该尽可能掩盖复杂的地方,露出一个干净而直观的 API,来简化开发人员在使他们的应用程序安全上的努力。以下是你可以用 Apache Shiro 所做的事情:

    1. 验证用户来核实他们的身份
    2. 对用户执行访问控制,如:
      • 判断用户是否被分配了一个确定的安全角色
      • 判断用户是否被允许做某事
    3. 在任何环境下使用 Session API,即使没有 Web 或 EJB 容器
    4. 在身份验证,访问控制期间或在会话的生命周期,对事件作出反应
    5. 聚集一个或多个用户安全数据的数据源,并作为一个单一的复合用户“视图”
    6. 启用单点登录(SSO)功能
    7. 为没有关联到登录的用户启用"Remember Me"服务
    8. 以及更多——全部集成到紧密结合的易于使用的 API 中

    Shiro 视图在所有应用程序环境下实现这些目标——从最简单的命令行应用程序到最大的企业应用,不强制依赖其他第三方框架,容器,或应用服务器。当然,该项目的目标是尽可能地融入到这些环境,但它能够在任何环境下立即可用。

    1.2 Shiro的整体架构

    9825bc315c6034a8f93c7d0cce13495408237665.jpg

    1. Subject:主体,可以看到主体可以是任何与应用交互的“用户”。
    2. SecurityManager:相当于 SpringMVC 中的 DispatcherServlet 或者 Struts2 中的FilterDispatcher。它是 Shiro 的核心,所有具体的交互都通过 SecurityManager 进行控制。它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理。
    3. Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得 Shiro 默认的不好,我们可以自定义实现。其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了。
    4. Authrizer:授权器,或者访问控制器。它用来决定主体是否有权限进行相应的操作,即控制着用户能访问应用中的哪些功能。
    5. Realm:可以有1个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的。它可以是 JDBC 实现,也可以是 LDAP 实现,或者内存实现等。
    6. SessionManager:如果写过 Servlet 就应该知道 Session 的概念,Session 需要有人去管理它的生命周期,这个组件就是 SessionManager。而 Shiro 并不仅仅可以用在 Web 环境,也可以用在如普通的 JavaSE 环境。
    7. SessionDAO:DAO 大家都用过,数据访问对象,用于会话的 CRUD。我们可以自定义 SessionDAO 的实现,控制 session 存储的位置。如通过 JDBC 写到数据库或通过 jedis 写入 redis 中。另外 SessionDAO 中可以使用 Cache 进行缓存,以提高性能。
    8. CacheManager:缓存管理器。它来管理如用户、角色、权限等的缓存的。因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能。
    9. Cryptography:密码模块,Shiro 提供了一些常见的加密组件用于如密码的加密/解密。

    二、环境依赖

    <dependencies>
      	<dependency>
      		<groupId>junit</groupId>
      		<artifactId>junit</artifactId>
      		<version>4.12</version>
      	</dependency>
      	<dependency>
      		<groupId>org.apache.shiro</groupId>
      		<artifactId>shiro-core</artifactId>
      		<version>1.3.2</version>
      		<exclusions>
      			<exclusion>
      				<groupId>org.slf4j</groupId>
      				<artifactId>slf4j-api</artifactId>
      			</exclusion>
      		</exclusions>
      	</dependency>
      	
      	<dependency>
      		<groupId>org.slf4j</groupId>
      		<artifactId>slf4j-log4j12</artifactId>
      		<version>1.7.25</version>		
      	</dependency>
      	<dependency>
      		<groupId>commons-logging</groupId>
      		<artifactId>commons-logging</artifactId>
      		<version>1.2</version>
      	</dependency>
      	<dependency>
      		<groupId>commons-httpclient</groupId>
      		<artifactId>commons-httpclient</artifactId>
      		<version>3.1</version>
      	</dependency>
    </dependencies>
    

    三、简单案例

    在应用程序中启用 Shiro 最先要明白的事情是几乎在 Shiro 中的每个东西都与一个名为 SecurityManager 的主要的/核心的组件有关。对于那些熟悉 Java安全的人来说,这是 Shiro 的 SecurityManager 概念——它不等同于java.lang.SecurityManager。

    现在了解了Shiro 的 SecurityManager 是应用程序的Shiro环境的核心。因此,在我们的教程应用程序中第一件要做的事情就是配置 SecurityManager 实例。

    虽然我们能够直接实例化一个 SecurityManager 类,但 Shiro 的 SecurityManager 实现有足够的配置选项及内置组件使得在 Java 源代码做这件事情变得较为痛苦——如果使用一个灵活的基于文本的配置格式来配置 SecurityManager,那么这将是一件很容易的事情。 为此, Shiro 通过基于文本的 INI 配置文件提供了一个默认的"共性(common denominator) "解决方案。近来人们已经相当厌倦了使用笨重的 XML 文件,且 INI 文件易于阅读,使用简单,依赖性低。 INI 文件能够有效地被用来配置简单的对象图,如 SecurityManager。

    Shiro 的 SecurityManager 实现及所有支持组件都是兼容 JavaBean 的。这允许 Shiro能够与几乎任何配置格式如 XML(Spring, JBoss, Guice 等等), YAML, JSON, GroovyBuilder markup,以及更多配置被一起配置。 INI 文件只是 Shiro 的“共性”格式,他它允许任何环境下的配置,除非其他选项不可用。

    因此,我们将为这个简单的应用程序使用 INI 文件来配置 Shiro SecurityManager。首先,在 pom.xml 所在的同一目录下创建 src/main/resources 目录。然后在新目录下创建包含以下内容的 shiro.ini 文件:

    3.1 数据初始化

    shiro-first.ini

    [users]
    alex=password
    haley=snoopy
    luke=luke
    manny=manny
    

    3.2 测试代码:

    //根据ini初始化文件创建SecurityManager工厂
    IniSecurityManagerFactory factory = new IniSecurityManagerFactory(
        "classpath:shiro-first.ini");
    
    //从工厂获取securityManager实例
    SecurityManager securityManager = factory.getInstance();
    
    //使用工具类设置securityManager运行环境
    SecurityUtils.setSecurityManager(securityManager);
    
    //获取Subject实例
    Subject subject = SecurityUtils.getSubject();
    
    //封装一个用户信息的令牌(用户名,密码),相当于在controller获得用户的登陆信息并封装
    AuthenticationToken token = new UsernamePasswordToken("haley", "snoopy");
    
    try {
        //用户登录
        subject.login(token);
    }catch(UnknownAccountException ue){
        System.out.println("用户名不存在");
    }catch(IncorrectCredentialsException ice){
        System.out.println("密码错误");
    }catch (AuthenticationException e) {
        e.printStackTrace();
    }
    
    //查看用户是否通过认证
    if(subject.isAuthenticated()){
        System.out.println("认证通过");
    }else{
        System.out.println("认证未通过");
    }
    

    3.2.1 认证执行流程

    1. 创建token令牌,token中有用户提交的认证信息即账号和密码
    2. 执行subject.login(token),最终由securityManager通过Authenticator进行认证
    3. Authenticator的实现ModularRealmAuthenticator调用realm从ini配置文件取用户真实的账号和密码,这里使用的是IniRealm(shiro自带)
    4. IniRealm先根据token中的账号去ini中找该账号,如果找不到则给ModularRealmAuthenticator返回null,如果找到则匹配密码,匹配密码成功则认证通过。

    3.2.2 常见的异常

    1. UnknownAccountException 账号不存在异常如下: org.apache.shiro.authc.UnknownAccountException: No account found for user。。。。
    2. IncorrectCredentialsException 当输入密码错误会抛此异常,如下: org.apache.shiro.authc.IncorrectCredentialsException: Submitted credentials for token [org.apache.shiro.authc.UsernamePasswordToken - zhangsan, rememberMe=false] did not match the expected credentials.
    3. 更多如下: DisabledAccountException(帐号被禁用) LockedAccountException(帐号被锁定) ExcessiveAttemptsException(登录失败次数过多) ExpiredCredentialsException(凭证过期)等

    四、拥有角色权限的shiro应用案例

    4.1 数据初始化

    shiro-permission.ini

    [users]
    #用户haley的密码是snoopy,此用户具有role1和role2两个角色
    haley=snoopy,role1,role2
    alex=password,role3
    luke=luke
    manny=manny
    [roles]
    #角色role1对资源user拥有create、update权限
    role1=user:create,user:update
    #角色role2对资源user拥有create、delete权限
    role2=user:create,user:delete
    #角色role3对资源user拥有create权限
    role3=user:create
    

    4.2 测试代码

    IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro-permission.ini");
    SecurityManager securityManager = factory.getInstance();
    SecurityUtils.setSecurityManager(securityManager);
    Subject subject = SecurityUtils.getSubject();
    String username="alex";
    String password="password";
    AuthenticationToken token = new UsernamePasswordToken(username, password);
    
    try {
        //用户登录
        subject.login(token);
    } catch (AuthenticationException e) {
        e.printStackTrace();
    }
    
    boolean bl = subject.hasRole("role1");
    System.out.println(username+"含有role1角色吗?:"+bl);
    
    boolean[] hasRoles = subject.hasRoles(Arrays.asList("role1","role2"));
    System.out.println(username+"含有role1,role2角色吗?:"+Arrays.toString(hasRoles));
    
    boolean permitted1 = subject.isPermitted("user:create");
    System.out.println(username+"是否含有user:create权限:"+permitted1);
    
    boolean permitted2 = subject.isPermittedAll("user:create","user:query");
    System.out.println(username+"是否含有user:create和user:update权限:"+permitted2);
    

    五、自定义安全策略的shiro应用案例

    5.1 数据初始化

    shiro-realm.ini

    [main]
    #自定义realm
    myRealm=top.top234.shiro.MyRealm
    #在securityManager中设置自定义的realm
    securityManager.realms=$myRealm
    

    5.2 自定义安全策略

    Myrealm.java

    public class MyRealm extends AuthorizingRealm{
        //负责授权的方法
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals){
           //用户登录时提交的用户名
            String  username = (String) principals.getPrimaryPrincipal();
            System.out.println(username);
    
            //根据用户名查询数据库获取该用户权限信息
            List<String> permissions = new ArrayList<>();
            //构造String的权限集合
            permissions.add("user:create");
            permissions.add("user:update");
    
            //创建AuthorizationInfo对象
            SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
    
            //将构造的权限集合赋予AuthorizationInfo对象
            authorizationInfo.addStringPermissions(permissions);
            
            authorizationInfo.addRole("role1");
            return authorizationInfo;
        }
        
        //负责用户认证的方法
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            //用户登录时提交的用户名
            String  username = (String) token.getPrincipal();
            //根据用户名从数据库查询对应密码
            String pwd= "Snoopy";
            if (pwd == null) {
                throw new UnknownAccountException("用户名不存在");
            }
            
    		//将用户提交的用户名及根据用户名查出来的密码(凭证)封装成一个AuthenticationInfo实例并返回
            SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, pwd, getName());
            return authenticationInfo;
        }
    }
    

    5.3 测试代码1

    IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro-realm.ini");
    
    SecurityManager securityManager = factory.getInstance();
    SecurityUtils.setSecurityManager(securityManager);
    Subject subject = SecurityUtils.getSubject();
    String username="haley";
    String pwd = "Snoopy";
    
    //将用户提交的用户名和密码封装为Token对象
    UsernamePasswordToken token = new UsernamePasswordToken("haley", "snoopy");
    try {
        subject.login(token);
        System.out.println("success");
    } catch (UnknownAccountException e) {
        System.out.println("fail");
    }
    
    if(subject.isAuthenticated()){
        System.out.println(username+"登录成功");
    }else{
        System.out.println(username+"登录失败");
    }
    

    5.4 测试代码2

    IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro-realm.ini");
    SecurityManager securityManager = factory.getInstance();
    SecurityUtils.setSecurityManager(securityManager);
    Subject subject = SecurityUtils.getSubject();
    
    String username="luke";
    String password="luke";
    AuthenticationToken token = new UsernamePasswordToken(username, password);
    
    try {
        //用户登陆认证
        subject.login(token);
    } catch (AuthenticationException e) {
        e.printStackTrace();
    }
    
    if(subject.isPermitted("user:create1")){
        System.out.println(username+"含有user:create1权限!");
    }else{
        System.out.println(username+"不含有user:create1权限!");
    }
    
    if(subject.hasRole("role1")){
        System.out.println(username+"含有role1角色!");
    }
    

    六、自定义安全策略并凭证MD5加密的shiro应用案例

    6.1 数据初始化

    shiro-realm-md5.ini

    [main]
    #定义凭证匹配器
    credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
    #设置散列算法
    credentialsMatcher.hashAlgorithmName=md5
    #设置散列次数
    credentialsMatcher.hashIterations=1
    
    #将凭证匹配器设置到realm
    myRealm=top.top234.shiro.MyRealmMd5
    myRealm.credentialsMatcher=$credentialsMatcher
    securityManager.realms=$myRealm
    

    6.2 自定义安全策略-MD5加密

    MyRealmMd5.java

    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.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    import org.apache.shiro.util.ByteSource;
    
    public class MyRealmMd5 extends AuthorizingRealm {
    	//负责授权的方法
    	@Override
    	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
    		return null;
    	}
    	//负责用户认证的方法
    	@Override
    	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    		String  username = (String) token.getPrincipal();
    		//根据用户名查到的密码(加密后的密码)
    		String password= "d23409dd4c65abc5c49a26a6115a9a54";
            
            //盐值
            String salt="zhangsan";
    		//封装AuthenticationInfo接口实现类实例并返回
    		SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, password, ByteSource.Util.bytes(salt), getName());
    		return authenticationInfo;
    	}
    }
    

    6.3 测试代码

    IniSecurityManagerFactory factory= new IniSecurityManagerFactory("classpath:shiro-realm-md5.ini");
    SecurityManager securityManager = factory.getInstance();
    SecurityUtils.setSecurityManager(securityManager);
    Subject subject = SecurityUtils.getSubject();
    
    AuthenticationToken token = new UsernamePasswordToken("alex", "password");
    
    try {
        subject.login(token);
    } catch (AuthenticationException e) {
        e.printStackTrace();
    }
    
    if(subject.isAuthenticated()){
        System.out.println(username+"登录成功!");
    }
    

    © 2019-2021 Top234 版权所有

    陕ICP备19020319号-1