shiro介绍

什么是shiro


shiro是Apache的一个开源框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。它可以实现如下的功能:


1.验证用户


2.对用户执行访问控制,如:判断用户是否具有角色admin,判断用户是否拥有访问的资源权限。


3.在任何环境下使用SessionAPI。例如C/S程序


4.可以使用多个用户数据源。例如一个是Oracle数据库,另外一个是MySQL数据库。


5.单点登录(SSO)功能


6."Remember Me"服务,类似于购物车的功能,shiro官方建议开启。


为何使用shiro?


因为shiro将安全认证相关的功能抽取出来组成了一个框架,因此使用shiro就可以非常快速的完成认证、授权等功能的开发,降低系统成本。


shiro使用广泛,shiro可以运行在web应用,非web应用,集群分布式应用中越来越多的用户开始使用shiro.


Spring中有Spring security(原名Acegi), 是一个权限框架,它和Spring的依赖过于紧密,没有shiro使用简单。shiro不依赖于Spring,而shiro就相对独立最主要是因为shiro使用简单‘灵活,所以现在越来越多的用户选择shiro。


shiro核心组成部分


shiro token session 多端登录 shiro session管理_apache


shiro的4大组成部分——身份认证,授权,会话管理和加密


Authentication:身份验证(身份认证),简称"登录"。


Authorization:授权,给用户给用户分配角色或者权限资源。


Session Manager:用户Session管理器,可以让C/S程序也使用Session来控制权限。


Cryptography:将JDK中复杂的密码加密方式进行封装。


除了以上功能,shiro还提供很多扩展功能:


Web Support:主要针对web应用提供一些常用功能。


Caching:缓存可以使程序运行更有效率。


Concurrency:多线程相关功能。


Testing:帮助我们进行测试相关的功能。


"Run As":一个允许用户假设为另一个用户身份(如果允许)的功能,有时候在管理脚本时很有用。


"Remember  Me":记住用户身份,提供类似购物车的功能。


shiro运行大致流程:


shiro token session 多端登录 shiro session管理_数据库_02


Subject——主体,是与程序进行交互的对象,可以是人也可以是服务或其他程序,通常理解为用户。所有的Subject实例都必须绑定到一个SecurityManager上。我们与Subject交互,运行时shiro会自动转化为与SecurityManager交互的特定的subject的交互。


SecurityManager——SecurityManager是shiro的核心,初始化时协调各个模块运行。然而,一旦SecurityManager协调完毕,SecurityManager会被单独留下,且我们只需要去操作Subject即可,无需操作SecurityManager。但是需要了解的是当我们与一个Subject进行交互时,实质上是SecurityManager在处理Subject的安全操作。


Realms——Realms在shiro中作为程序和安全数据之间的"桥梁"或"连接器"。它用于获取安全数据来判断subject是否能够登录,subject拥有什么权限。有点类似于DAO。在配置realms时,需要至少一个realm。而且shiro提供了一些常用的Realms来连接数据源,如LDAP数据源的JndiLdapRealm,JDBC数据源的JdbcRealm,ini文件数据源的iniRealm,Properties文件数据源的PropertiesRealm,等等,我们也可以插入自己的Realm实现来代表自定义的数据源。像其他组件一样,Realms也是由SecurityManager控制。


shiro架构


shiro token session 多端登录 shiro session管理_数据库_03


Subject:(org.apache.shiro.subject.Subject)  即主体,简称用户,主体既可以是用户也可以是程序,主体访问系统,系统需要对主体进行认证、授权。 外部应用与subject进行交互,Subject记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。 Subject在shiro中是一个接口,接口中定义了很多认证授权相关的方法,外部程序通过subject进行认证授权,而Subject是通过SecurityManager安全管理器进行认证授权。


SecurityManager:(org.apache.shiro.mgt.SecurityManager)如上所述,SecurityManager是shiro的核心,协调shiro的各个组件。SecurityManager就是安全管理器,负责对全部的subject进行安全管理。通过SecurityManager可以完成Subject的认证、授权等,实质上SecurityManager是通过Authenticator对主体进行认证,通过Authorizer对主体进行授权,通过SessionManager进行会话管理等等。SecurityManager是一个接口,继承了Authenticator,Authorizer,SessionManager这三个接口。


Authenticator:(org.apache.shiro.authc.Authenticator) 即认证器,对用户身份进行认证,Authenticator是一个接口,shiro提供ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足大多数需求,也可以自定义认证器。


Authorizer:(org.apache.shiro.authz.Authorizer)即授权器,用户在通过认证器认证通过后,在访问时需要通过授权器判断用户是否有此功能的操作权限。最终是通过认证器对主体进行授权的。


Realm:(org.apache.shiro.realm.Realm)Realm即领域,相当于DataSource数据源,通过Realm存取认证、授权相关数据。SecurityManager通过认证器对主体进行安全认证需要通过Realm获取用户身份数据,比如:如果用户身份数据在数据库,那么Realm就需要从数据库获取用户的身份信息。授权也是如此,也需要通过Realm取出授权相关信息。注意:不要将Realm理解成只是从数据源获取数据,在Realm中还有认证授权校验的相关代码


SessionManager:(org.apache.shiro.session.SessionManager)会话管理。web应用中一般是web容器对Session进行管理,shiro框架定义了一套会话管理,它不依赖于web容器的Session,所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点进行管理,此特性可使它实现单点登录。


SessionDAO:SessionDAO即会话dao,是对Session会话操作的一套接口,比如要将Session存储到数据库,可以通过JDBC将会话存储到数据库。针对个性化的Session数据存储(存到数据库)需要使用SessionDAO。


CacheManager:(org.apahce.shiro.cache.CacheManager)缓存管理器,主要对Session和授权数据进行缓存,比如将授权数据通过cachemanager进行缓存管理,和ehcache整合对缓存数据进行管理,可以减少不必要的后台访问,提高应用效率,增加用户体验。


Cryptography:(org.apache.shiro.crypto.*)密码管理,提供了一套加密/解密组件,对JDK中的加密解密算法进行了封装,方便开发。比如提供常用的散列、加/解密等功能,比如MD5散列算法。


shiro相关jar


与其他Java开源框架类似,将shiro的jar包加入项目就可以使用shiro提供的功能了。shiro-core时核心包必须选用,还提供了与web整合的shiro-web、与Spring整合的shiro-spring、与任务调度quartz整合的shiro-quartz、与ehcache整合的shiro-ehcache。下边是shiro各个jar包的Maven坐标。


[html] view plain copy


print

?



    1. <dependency>
    2. <groupId>org.apache.shiro</groupId>
    3. <artifactId>shiro-core</artifactId>
    4. <version>1.2.3</version>
    5. </dependency>
    6. <dependency>
    7. <groupId>org.apache.shiro</groupId>
    8. <artifactId>shiro-web</artifactId>
    9. <version>1.2.3</version>
    10. </dependency>
    11. <dependency>
    12. <groupId>org.apache.shiro</groupId>
    13. <artifactId>shiro-spring</artifactId>
    14. <version>1.2.3</version>
    15. </dependency>
    16. <dependency>
    17. <groupId>org.apache.shiro</groupId>
    18. <artifactId>shiro-ehcache</artifactId>
    19. <version>1.2.3</version>
    20. </dependency>
    21. <dependency>
    22. <groupId>org.apache.shiro</groupId>
    23. <artifactId>shiro-quartz</artifactId>
    24. <version>1.2.3</version>
    25. </dependency>


    <dependency>
    	<groupId>org.apache.shiro</groupId>
    	<artifactId>shiro-core</artifactId>
    	<version>1.2.3</version>
    </dependency>
    <dependency>
    	<groupId>org.apache.shiro</groupId>
    	<artifactId>shiro-web</artifactId>
    	<version>1.2.3</version>
    </dependency>
    <dependency>
    	<groupId>org.apache.shiro</groupId>
    	<artifactId>shiro-spring</artifactId>
    	<version>1.2.3</version>
    </dependency>
    <dependency>
    	<groupId>org.apache.shiro</groupId>
    	<artifactId>shiro-ehcache</artifactId>
    	<version>1.2.3</version>
    </dependency>
    <dependency>
    	<groupId>org.apache.shiro</groupId>
    	<artifactId>shiro-quartz</artifactId>
    	<version>1.2.3</version>
    </dependency>

    也可以通过引入shiro-all包括shiro所有的包


    [html] view plain copy


    print

    ?


    1. <dependency>
    2. <groupId>org.apache.shiro</groupId>
    3. <artifactId>shiro-all</artifactId>
    4. <version>1.2.3</version>
    5. </dependency>


    <dependency>
    	<groupId>org.apache.shiro</groupId>
    	<artifactId>shiro-all</artifactId>
    	<version>1.2.3</version>
    </dependency>

    相关jar包如下所示:


    shiro token session 多端登录 shiro session管理_数据源_04




    shiro认证


    shiro认证过程


    shiro token session 多端登录 shiro session管理_数据库_05


    1.应用程序构建了一个终端用户认证信息AuthenticationToken实例后,调用Subject.login方法。


    2.Subject的实例通常是DelegatingSubject类(或子类)的实例对象,在认证开始时,会委托应用程序设置的SecurityManager实例调用securityManager.login(token)方法。


    3.SecurityManager接受到token(令牌)信息后会委托内置的Authenticator的实例(通常是ModularRealmAuthenticator类的实例)调用authenticator.authenticate(token)。ModularRealmAuthenticator在认证过程中会对设置的一个或多个Realm实例进行适配,它实际上为shiro提供了一个可插拔的认证机制。


    4.如果在应用程序中配置类多个Realm,ModularRealmAuthenticator会根据配置的AuthenticationStrategy(认证策略)来进行多Realm的认证过程。在Realm被调用后,AuthenticationStrategy将对每一个Realm的结果做出响应。注意:如果应用程序中仅配置了一个Realm,Realm将被直接调用而无需再配置认证策略。


    5.判断每一个Realm是否支持提交的token,如果支持,Realm将调用getAuthenticationInfo(token);getAuthenticationInfo方法就是实际认证处理,我们通过覆盖Realm的doGetAuthenticationInfo方法来编写我们自定义的认证处理。


    shiro的认证流程图如下:


    shiro token session 多端登录 shiro session管理_数据库_06


    shiro入门程序(用户登录和退出)


    创建Java工程


    加入shiro-core的jar包以及依赖包



    shiro token session 多端登录 shiro session管理_数据库_07



    工程结构如下:


    shiro token session 多端登录 shiro session管理_数据源_08


    log4j.properties日志配置文件


    [plain] view plain copy

    print ?

      1. log4j.rootLogger=debug, stdout  
      2.   
      3. log4j.appender.stdout=org.apache.log4j.ConsoleAppender  
      4. log4j.appender.stdout.layout=org.apache.log4j.PatternLayout  
      5. log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n


      log4j.rootLogger=debug, stdout
      
      log4j.appender.stdout=org.apache.log4j.ConsoleAppender
      log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
      log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n

      shiro-first.ini


      通过shiro-first.ini配置文件初始化SecurityManager环境,创建SecurityManager工厂。


      配置MyEclipse支持ini,添加中文支持插件Properties Editor 地址是 http://propedit.sourceforge.jp/eclipse/updates/



      shiro token session 多端登录 shiro session管理_apache_09



      在MyEclipse配置后,在classpath创建shiro.ini配置文件,为了方便测试将用户名和密码配置在shiro-first.ini文件中



      [plain] view plain copy

      print ?

      1. #对用户信息进行配置  
      2. [users]  
      3. #用户账号和密码  
      4. zhangsan=123456  
      5. lisi=654321  


      #对用户信息进行配置
      [users]
      #用户账号和密码
      zhangsan=123456
      lisi=654321

      入门程序认证代码


      [java] view plain copy

      print ?

      1. // 用户登录和退出
      2. @Test
      3. public void
      4.   
      5. // 创建SecurityManager工厂。通过ini配置文件创建securityManager
      6. new IniSecurityManagerFactory("classpath:shiro-first.ini");  
      7.   
      8. // 创建SecurityManager
      9.         SecurityManager securityManager = factory.getInstance();  
      10.   
      11. // 将securityManager设置到当前的运行环境中
      12.         SecurityUtils.setSecurityManager(securityManager);  
      13.   
      14. // 从SecurityUtils中创建一个subject
      15.         Subject subject = SecurityUtils.getSubject();  
      16.   
      17. // 在认证提交前准备token(令牌)
      18. // 这里的账号和密码 将来是由用户输入进去的
      19. new UsernamePasswordToken("zhangsan", "123456");  
      20.          
      21. //执行认证提交
      22. try
      23. //执行认证提交
      24.             subject.login(token);  
      25. catch
      26.             e.printStackTrace();  
      27.         }  
      28.           
      29. // 是否认证通过
      30. boolean
      31. "是否认证通过:"+isAuthenticated);  
      32.           
      33. //退出操作
      34.         subject.logout();  
      35.           
      36. // 是否认证通过
      37.         isAuthenticated = subject.isAuthenticated();  
      38. "是否认证通过:"+isAuthenticated);  
      39.     }


      // 用户登录和退出
      	@Test
      	public void testLoginAndLogout() {
      
      		// 创建SecurityManager工厂。通过ini配置文件创建securityManager
      		Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-first.ini");
      
      		// 创建SecurityManager
      		SecurityManager securityManager = factory.getInstance();
      
      		// 将securityManager设置到当前的运行环境中
      		SecurityUtils.setSecurityManager(securityManager);
      
      		// 从SecurityUtils中创建一个subject
      		Subject subject = SecurityUtils.getSubject();
      
      		// 在认证提交前准备token(令牌)
      		// 这里的账号和密码 将来是由用户输入进去的
      		UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "123456");
             
      		//执行认证提交
      		try {
      			//执行认证提交
      			subject.login(token);
      		} catch (AuthenticationException e) {
      			e.printStackTrace();
      		}
      		
      		// 是否认证通过
      		boolean isAuthenticated = subject.isAuthenticated();
      		System.out.println("是否认证通过:"+isAuthenticated);
      		
      		//退出操作
      		subject.logout();
      		
      		// 是否认证通过
      		isAuthenticated = subject.isAuthenticated();
      		System.out.println("是否认证通过:"+isAuthenticated);
      	}


      shiro token session 多端登录 shiro session管理_数据源_10


      认证执行流程


      1.通过ini配置文件创建SecurityManager


      2.创建token令牌,token中有用户提交的认证信息即用户名和密码


      3.执行subject.login(token)方法提交认证,最终由securityManager通过Authenticator进行认证


      4.Authenticator的实现ModularRealmAuthenticator调用realm从ini配置文件取出用户真实的账号和密码,这里使用的是iniRealm(shiro自带)


      5.initRealm先根据先根据token中的账号去ini中查找账号,如果查找不到则给ModularRealmAuthenticator返回null,如果查到用户信息,就给ModularRealmAuthenticator返回用户信息(账号和密码)


      6.ModularRealmAuthenticator接收IniRealm返回Authentication认证信息,如果返回的认证信息是null,ModularRealmAuthenticator抛出异常(org.apache.shiro.authc.UnknownAccountException)如果返回的认证信息不是null(说明inirealm找到了用户),对IniRealm返回用户密码 (在ini文件中存在)和 token中的密码 进行对比,如果不一致抛出异常(org.apache.shiro.authc.IncorrectCredentialsException)


      常见的认证异常


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


      小结


      ModularRealmAuthenticator作用进行认证,需要调用realm查询用户信息(在数据库中存在用户信息)
      ModularRealmAuthenticator进行密码对比(认证过程)。
      realm:需要根据token中的身份信息去查询数据库(入门程序使用ini配置文件),如果查到用户返回认证信息,如果查询不到返回null。


      自定义Realm


      上边的程序中使用的是Shiro自带的iniRealm。iniRealm从配置文件中读取用户的信息,大部分情况下需要从系统的数据库中读取用户信息,所以需要自定义Realm。


      shiro中提供的Realm接口的实现


      shiro token session 多端登录 shiro session管理_数据源_11


      实现自定义Realm


      [java] view plain copy

      pri

      nt     ?       
      1. public class CustomRealm extends
      2. // 设置Realm的名称
      3. @Override
      4. public
      5. return super.getName();  
      6.     }  
      7.   
      8. // 支持UsernamePasswordToken
      9. @Override
      10. public boolean
      11. return token instanceof
      12.     }  
      13.   
      14. // 用于认证
      15. @Override
      16. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws
      17.   
      18. // token是用户输入的
      19. // 第一步从token中取出身份信息
      20.         String usercode = (String) token.getPrincipal();  
      21.   
      22. // 第二步:根据用户输入的usercode从数据库查询
      23. // ......
      24.   
      25. // 如果查询不到返回null
      26. // 数据库中用户账号是zhangsan
      27. if (!usercode.equals("zhangsan")) {  
      28. return null;  
      29.         }  
      30.   
      31. // 模拟从数据库中查询到密码
      32. "123456";  
      33.   
      34. // 如果查询到返回认证信息AuthenticationInfo
      35. new
      36. this.getName());  
      37.   
      38. return
      39.     }  
      40.   
      41. // 用于授权
      42. @Override
      43. protected
      44.   
      45. return null;  
      46.     }  
      47. }


      public class CustomRealm extends AuthorizingRealm {
      	// 设置Realm的名称
      	@Override
      	public String getName() {
      		return super.getName();
      	}
      
      	// 支持UsernamePasswordToken
      	@Override
      	public boolean supports(AuthenticationToken token) {
      		return token instanceof UsernamePasswordToken;
      	}
      
      	// 用于认证
      	@Override
      	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
      
      		// token是用户输入的
      		// 第一步从token中取出身份信息
      		String usercode = (String) token.getPrincipal();
      
      		// 第二步:根据用户输入的usercode从数据库查询
      		// ......
      
      		// 如果查询不到返回null
      		// 数据库中用户账号是zhangsan
      		if (!usercode.equals("zhangsan")) {
      			return null;
      		}
      
      		// 模拟从数据库中查询到密码
      		String password = "123456";
      
      		// 如果查询到返回认证信息AuthenticationInfo
      		SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(usercode, password,
      				this.getName());
      
      		return simpleAuthenticationInfo;
      	}
      
      	// 用于授权
      	@Override
      	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
      
      		return null;
      	}
      }

      shiro-realm.ini


      需要在shiro-realm.ini配置realm注入到securityManager



      [plain] view plain copy

      print ?

      1. [main]  
      2. #自定义realm  
      3. customRealm=liuxun.test.shiro.realm.CustomRealm  
      4. #将realm设置到SecurityManager,相当于Spring中的注入  
      5. securityManager.realms=$customRealm


      [main]
      #自定义realm
      customRealm=liuxun.test.shiro.realm.CustomRealm
      #将realm设置到SecurityManager,相当于Spring中的注入
      securityManager.realms=$customRealm

      测试代码


      测试代码同入门程序,将ini的地址修改为shiro-realm.ini


      分别模拟账号不存在、密码错误、账号和密码正确进行测试


      散列算法


      散列算法一般用于生成一段文本的摘要信息,散列算法不可逆,将内容可以生成摘要,无法将摘要转成原始内容。散列算法常用于对密码进行散列,常用的散列算法有MD5、SHA。
      一般散列算法需要提供一个salt(盐)与原始内容生成摘要信息,这样做的目的是为了安全性,比如:111111的md5值是:96e79218965eb72c92a549dd5a330112,拿着“96e79218965eb72c92a549dd5a330112”去md5破解网站很容易进行破解,如果要是对111111和salt(盐,一个随机数)进行散列,这样虽然密码都是111111加不同的盐会生成不同的散列值。


      md5散列测试程序


      [java] view plain copy

      print ?

      1. package
      2.   
      3. import
      4. import
      5.   
      6. public class
      7. public static void
      8. //原始密码
      9. "123456";  
      10. //盐
      11. "qwerty";  
      12. //散列次数
      13. int hashIterations = 2;  
      14. //上边散列1次:48474f975022f960bc2afbe49be581e8
      15. //上边散列2次:13f79dafcbbedc313273e2b891ac84d3
      16.           
      17. //构造方法中:
      18. //第一个参数:明文,原始密码
      19. //第二个参数:盐,通过使用随机字符串
      20. //第三个参数:散列的次数,比如散列两次,相当于md5(md5(''))
      21. new
      22.           
      23.         String password_md5 = md5Hash.toString();  
      24.         System.out.println(password_md5);  
      25.           
      26. //使用后SimpleHash
      27. //第一个参数:散列算法
      28. new SimpleHash("md5", source, salt, hashIterations);  
      29.         System.out.println(simpleHash.toString());  
      30.     }  
      31. }


      package liuxun.test.shiro.authentication;
      
      import org.apache.shiro.crypto.hash.Md5Hash;
      import org.apache.shiro.crypto.hash.SimpleHash;
      
      public class MD5Test {
      	public static void main(String[] args) {
      		//原始密码
      		String source = "123456";
      		//盐
      		String salt = "qwerty";
      		//散列次数
      		int hashIterations = 2;
      		//上边散列1次:48474f975022f960bc2afbe49be581e8
      		//上边散列2次:13f79dafcbbedc313273e2b891ac84d3
      		
      		//构造方法中:
      		//第一个参数:明文,原始密码
      		//第二个参数:盐,通过使用随机字符串
      		//第三个参数:散列的次数,比如散列两次,相当于md5(md5(''))
      		Md5Hash md5Hash = new Md5Hash(source, salt, hashIterations);
      	    
      		String password_md5 = md5Hash.toString();
      		System.out.println(password_md5);
      		
      		//使用后SimpleHash
      		//第一个参数:散列算法
      		SimpleHash simpleHash = new SimpleHash("md5", source, salt, hashIterations);
      		System.out.println(simpleHash.toString());
      	}
      }

      建议对MD5进行散列时加salt(盐),相当于对原始密码+盐进行散列


      正常使用时散列算法:


      在程序中对"原始密码+盐"进行散列,将散列值存储到数据库中,并且还要将盐存储在数据库中,如果进行密码比对时,使用相同方法,将原始密码+盐进行散列,进行比对。


      自定义Realm支持散列算法


      实际应用中是将盐和散列后的值存在数据库中,自动Realm从数据库中取出盐和加密后的值,由shiro完成密码校验。


      新建Realm(CustomRealmMd5)


      [java] view plain copy

      print ?

        1. // 用于认证
        2. @Override
        3. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws
        4.   
        5. // token保存了用户输入的身份信息userName和password
        6. // 第一步:从token中取出身份信息
        7.         String userCode = (String) token.getPrincipal();  
        8.   
        9. // 第二步:根据用户输入的userCode从数据库查询
        10. // ....
        11. // 如果查询不到返回null 假设用户输入的账号是zhansgan
        12. // 模拟从数据库中查询账号是zhangsan的用户
        13. if (!userCode.equals("zhangsan")) {  
        14. return null;  
        15.         }  
        16.   
        17. // 模拟从数据库中查询到密码(散列值)
        18. // 按照固定规则加密的结果,此密码是在数据库中存储的,原始密码是123456 盐是qwerty
        19. "48474f975022f960bc2afbe49be581e8";  
        20. // 盐,随机字符串,此随机字符串也是在数据库中存储的,模拟从数据库中获取
        21. "qwerty";  
        22.   
        23. // 如果查询到则返回认证信息AuthenticationInfo
        24. new
        25. this.getName());  
        26.           
        27. return
        28.     }  
          
        // 用于认证
        	@Override
        	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        
        		// token保存了用户输入的身份信息userName和password
        		// 第一步:从token中取出身份信息
        		String userCode = (String) token.getPrincipal();
        
        		// 第二步:根据用户输入的userCode从数据库查询
        		// ....
        		// 如果查询不到返回null 假设用户输入的账号是zhansgan
        		// 模拟从数据库中查询账号是zhangsan的用户
        		if (!userCode.equals("zhangsan")) {
        			return null;
        		}
        
        		// 模拟从数据库中查询到密码(散列值)
        		// 按照固定规则加密的结果,此密码是在数据库中存储的,原始密码是123456 盐是qwerty
        		String password = "48474f975022f960bc2afbe49be581e8";
        		// 盐,随机字符串,此随机字符串也是在数据库中存储的,模拟从数据库中获取
        		String salt = "qwerty";
        
        		// 如果查询到则返回认证信息AuthenticationInfo
        		SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(userCode, password,
        				ByteSource.Util.bytes(salt), this.getName());
        		
        		return simpleAuthenticationInfo;
        	}配置散列Realm
          在classpath下配置shiro-realm-md5.ini 
          
          
             [plain]      view plain       copy     
             
             print     ?       
        1. [main]  
        2. #定义凭证匹配器  
        3. credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher  
        4. #散列算法  
        5. credentialsMatcher.hashAlgorithmName=md5  
        6. #散列次数  
        7. credentialsMatcher.hashIterations=1  
        8.   
        9. #将凭证匹配器设置到Realm  
        10. customRealm=liuxun.test.shiro.realm.CustomRealmMd5  
        11. customRealm.credentialsMatcher=$credentialsMatcher  
        12. #将Realm设置到securityManager  
        13. securityManager.realms=$customRealm


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


        shiro授权


        授权有三个核心元素:权限、角色和用户。


        shiro权限声明通常是使用冒号分隔的表达式,权限表达式示例如下:


        user:view         可查询用户数据


        user:view,edit  可查询或编辑用户数据


        user:*或user    可对用户数据进行所有操作


        user:edit:123   可编辑id为123的用户数据。


        Shiro支持两种角色模式:


          1、传统角色:一个角色代表着一系列的操作,当需要对某一操作进行授权验证时,只需判断是否是该角色即可。这种角色权限相对简单、模糊,不利于扩展。 


        2、权限角色:一个角色拥有一个权限的集合。授权验证时,需要判断当前角色是否拥有该权限。这种角色权限可以对该角色进行详细的权限描述,适合更复杂的权限设计。


        shiro内部授权处理机制


        shiro token session 多端登录 shiro session管理_数据源_12




        1.在应用程序中调用授权验证方法(Subject的isPermitted*或hasRole*等)


        2.Subject的实例通常是DelegatingSubject(或子类)的实例对象,在认证开始时,会委托应用程序设置的securityManager实例调用相应的isPermitted*或hasRole*方法。


        3.接下来SecurityManager会委托内置的Authorizer的实例(默认是ModularRealmAuthorizer类的实例,类似于认证实例,它同样支持一个或多个Realm实例认证)调用相应的授权方法。


        4.每一个Realm将检查是否实现了相同的Authorizer接口。然后,将调用Realm自己相应的授权验证方法。


        注意:


        当使用多个Realm时,不同于认证策略处理方式,在授权处理过程中:


        (1) 当调用Realm出现异常时,将立即抛出异常,结束授权验证。


        (2) 只要有一个Realm验证成功,那么将认为授权成功,立即返回,结束认证。


        Shiro有3中认证策略的具体实现:
        (1) AtLeastOneSuccessfulStrategy  只要有一个(或更多)的Realm验证成功,那么认证将被视为成功                       (2) FirstSuccessfulStrategy  第一个Realm验证成功,整体认证将被视为成功,且后续Realm将被忽略                         (3) AllSuccessfulStrategy  所有Realm成功,认证才视为成功


        shiro授权流程


        shiro token session 多端登录 shiro session管理_数据源_13


        shiro授权方式


        shiro支持三种方式的授权:编程式、注解式、标签式


        方式一:编程式,通过写if/else授权代码块完成


        Subject subject = SecurityUtils.getSubject(); 
        
         
        
          if(subject.hasRole("admin")){ 
        
         
        
             //有权限 
        
         
        
          }else{ 
        
         
        
             // 无权限 
        
         
        
          } 
        
         
        方式二:注解式,通过在执行的Java方法上放置相应的注解完成。
         
        
          @RequiresRoles("admin") 
        
         
        
          public void hello(){ 
        
         
        
             //有权限 
        
         
        
          } 
        
         
        方式三:JSP/GSP标签,页面通过相应的标签完成
         
        
          <shiro:hasRole  name="admin"> 
        
         
        
            <!--  有权限   --> 
        
         
        
          </shiro:hasole>


        授权测试

        shiro-permission.ini


        创建存放权限的配置文件shiro-permission.ini 如下:


        [plain] view plain copy

        print ?

          1. #用户  
          2. [users]  
          3. #用户zhang的密码是123,此用户具有role1和role2两个角色  
          4. zhang=123,role1,role2  
          5. wang=123,role2  
          6.   
          7. #权限  
          8. [roles]  
          9. #角色role1对资源user拥有create、update权限  
          10. role1=user:create,user:update  
          11. #角色role2对资源user拥有create、delete权限  
          12. role2=user:create,user:delete  
          13. #role3对资源user拥有create权限  
          14. role3=user:create


          #用户
          [users]
          #用户zhang的密码是123,此用户具有role1和role2两个角色
          zhang=123,role1,role2
          wang=123,role2
          
          #权限
          [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

          在ini文件中用户、角色、权限的配置规则是:


          " 用户名=密码,角色1,角色2 .... "


          “ 角色=权限1,权限2...... ”


          首先根据用户名查找角色,再根据角色查找权限,角色是权限的集合


          权限字符串规则


          权限字符串的规则是:“资源标识符:操作:资源实例标识符”,意思是对哪个资源的哪个实例具有什么操作,“:”是资源/操作/实例的分割符,权限字符串也可以使用*通配符。
          例子:
          用户创建权限:user:create,或user:create:*
          用户修改实例001的权限:user:update:001
          用户实例001的所有权限:user:*:001  


          权限测试代码


          测试代码同认证代码,注意ini地址改为shiro-permission.ini,主要掌握其授权方法


          注意:在用户认证通过后才能执行下边的授权


          [java] view plain copy

          pr

          int     ?       
          1. // 角色授权、资源授权测试
          2. @Test
          3. public void
          4.   
          5. // 创建SecurityManager工厂
          6. new IniSecurityManagerFactory("classpath:shiro-permission.ini");  
          7.   
          8. // 创建SecurityManager
          9.         SecurityManager securityManager = factory.getInstance();  
          10.   
          11. // 将SecurityManager设置到系统运行环境,和Spring整合后将SecurityManager配置在Spring容器中,一般单例管理
          12.         SecurityUtils.setSecurityManager(securityManager);  
          13.   
          14. // 创建subject
          15.         Subject subject = SecurityUtils.getSubject();  
          16.   
          17. // 创建token令牌
          18. new UsernamePasswordToken("zhangsan", "123");  
          19.   
          20. // 执行认证
          21. try
          22.             subject.login(token);  
          23. catch
          24.             e.printStackTrace();  
          25.         }  
          26.   
          27. "认证状态:"
          28.   
          29. // 认证通过后执行授权
          30.   
          31. // 基于角色的授权
          32. // hasRole传入角色标识
          33. boolean ishasRole = subject.hasRole("role1");  
          34. "单个角色判断 "
          35. // hasAllRoles 是否拥有多个角色
          36. boolean hasAllRoles = subject.hasAllRoles(Arrays.asList("role1", "role2", "role3"));  
          37. "多个角色判断 "
          38.           
          39. //使用check方法进行授权,如果授权不通过会抛出异常,用于断言
          40. "role2");  
          41.           
          42. //基于资源的权限
          43. //isPermitted传入权限标识符
          44. boolean isPermitted = subject.isPermitted("user:create:1");  
          45. "单个权限判断 "+isPermitted);  
          46.           
          47. boolean isPermittedAll = subject.isPermittedAll("user:create:1","user:delete");  
          48. "多个权限判断 "+isPermittedAll);  
          49.           
          50. //使用check方法进行授权测试,如果授权不通过会抛出异常
          51. "item:delete");  
          52.     }

          // 角色授权、资源授权测试
          	@Test
          	public void testAuthorization() {
          
          		// 创建SecurityManager工厂
          		Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-permission.ini");
          
          		// 创建SecurityManager
          		SecurityManager securityManager = factory.getInstance();
          
          		// 将SecurityManager设置到系统运行环境,和Spring整合后将SecurityManager配置在Spring容器中,一般单例管理
          		SecurityUtils.setSecurityManager(securityManager);
          
          		// 创建subject
          		Subject subject = SecurityUtils.getSubject();
          
          		// 创建token令牌
          		UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "123");
          
          		// 执行认证
          		try {
          			subject.login(token);
          		} catch (AuthenticationException e) {
          			e.printStackTrace();
          		}
          
          		System.out.println("认证状态:" + subject.isAuthenticated());
          
          		// 认证通过后执行授权
          
          		// 基于角色的授权
          		// hasRole传入角色标识
          		boolean ishasRole = subject.hasRole("role1");
          		System.out.println("单个角色判断 " + ishasRole);
          		// hasAllRoles 是否拥有多个角色
          		boolean hasAllRoles = subject.hasAllRoles(Arrays.asList("role1", "role2", "role3"));
          		System.out.println("多个角色判断 " + hasAllRoles);
          		
          		//使用check方法进行授权,如果授权不通过会抛出异常,用于断言
          		subject.checkRole("role2");
          		
          		//基于资源的权限
          		//isPermitted传入权限标识符
          		boolean isPermitted = subject.isPermitted("user:create:1");
          		System.out.println("单个权限判断 "+isPermitted);
          		
          		boolean isPermittedAll = subject.isPermittedAll("user:create:1","user:delete");
          		System.out.println("多个权限判断 "+isPermittedAll);
          		
          		//使用check方法进行授权测试,如果授权不通过会抛出异常
          		subject.checkPermission("item:delete");
          	}

          使用check方法测试授权失败会抛出异常:org.apache.shiro.authz.UnauthorizedException


          自定义Realm授权


          上边的程序通过shiro-permission.ini对权限信息进行静态配置,实际开发中从数据库中获取权限数据。就需要自定义Realm,由Realm从数据库查询权限数据。Realm根据用户身份信息查询权限数据,将权限数据返回给authorizer(授权器)


          自定义Realm授权代码


          在原来自定义的Realm类中完善doGetAuthorizationInfo方法,此方法需要完成以下功能:根据用户身份信息从数据库中查询权限字符串,由shiro进行授权。


          [java] view plain copy

          print ?

          1. // 用于授权
          2. @Override
          3. protected
          4.   
          5. //从principals获取身份信息
          6. //将getPrimaryPrincipal方法返回值转为真实类型
          7. //(在上边的doGetAuthenticationInfo认证通过后填充到SimpleAuthenticationInfo中身份类型)
          8.         String userCode = (String) principals.getPrimaryPrincipal();  
          9.           
          10. //根据身份信息从数据库中获取权限信息
          11. //模拟从数据库中取到的数据
          12. new
          13. "user:create");//用户创建
          14. "items:add");//商品添加权限
          15. //....
          16.           
          17. //查询到权限数据,返回授权信息(要包括上边的permissions)
          18. new
          19. //将上边查询到的授权信息填充到simpleAuthorizationInfo对象中
          20.         simpleAuthorizationInfo.addStringPermissions(permissions);  
          21.           
          22. return
          23.     }


          // 用于授权
          	@Override
          	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
          
          		//从principals获取身份信息
          		//将getPrimaryPrincipal方法返回值转为真实类型
          		//(在上边的doGetAuthenticationInfo认证通过后填充到SimpleAuthenticationInfo中身份类型)
          		String userCode = (String) principals.getPrimaryPrincipal();
          		
          		//根据身份信息从数据库中获取权限信息
          		//模拟从数据库中取到的数据
          		List<String>  permissions = new ArrayList<String>();
          		permissions.add("user:create");//用户创建
          		permissions.add("items:add");//商品添加权限
          		//....
          		
          		//查询到权限数据,返回授权信息(要包括上边的permissions)
          		SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
          		//将上边查询到的授权信息填充到simpleAuthorizationInfo对象中
          		simpleAuthorizationInfo.addStringPermissions(permissions);
          		
          		return simpleAuthorizationInfo;
          	}

          shiro-realm.ini


          [plain] view plain copy

          print ?

            1. [main]  
            2. #自定义realm  
            3. customRealm=liuxun.test.shiro.realm.CustomRealm  
            4. #将realm设置到SecurityManager,相当于Spring中的注入  
            5. securityManager.realms=$customRealm


            [main]
            #自定义realm
            customRealm=liuxun.test.shiro.realm.CustomRealm
            #将realm设置到SecurityManager,相当于Spring中的注入
            securityManager.realms=$customRealm

            测试代码


            同上边的授权测试代码,注意ini地址为shiro-realm.ini


            授权执行流程总结


            2.SecurityManager执行授权,通过ModularRealmAuthorizer执行授权


            3.ModularRealmAuthorizer执行realm(自定义的CustomRealm)从数据库查询权限数据(调用realm的授权方法doGetAuthorizationInfo)


            4.realm从数据库查询权限数据,返回给ModularRealmAuthorizer


            5.ModularRealmAuthorizer调用PermissionResolver进行权限串比对


            6.如果比对后,isPermitted中"permission串" 在realm查询到的权限数据中,说明用户访问permission串有权限,否则没有权限,抛出异常。


            shiro认证和授权入门Demo


            项目结构如下:


            shiro token session 多端登录 shiro session管理_apache_14




            此Demo已经上传GitHub(https://github.com/LX1993728/permission_java_shiro)


            其代码具体如下:


            AuthenticationTest.java 测试认证


            [java] view plain copy

            print     ?       
            1. package
            2.   
            3. import
            4. import
            5. import
            6. import
            7. import
            8. import
            9. import
            10. import
            11.   
            12. /**
            13.  * 认证测试
            14.  * 
            15.  * @author liuxun
            16.  *
            17.  */
            18. public class
            19.   
            20. // 用户登录和退出
            21. @Test
            22. public void
            23.   
            24. // 创建SecurityManager工厂。通过ini配置文件创建securityManager
            25. new IniSecurityManagerFactory("classpath:shiro-first.ini");  
            26.   
            27. // 创建SecurityManager
            28.         SecurityManager securityManager = factory.getInstance();  
            29.   
            30. // 将securityManager设置到当前的运行环境中
            31.         SecurityUtils.setSecurityManager(securityManager);  
            32.   
            33. // 从SecurityUtils中创建一个subject
            34.         Subject subject = SecurityUtils.getSubject();  
            35.   
            36. // 在认证提交前准备token(令牌)
            37. // 这里的账号和密码 将来是由用户输入进去的
            38. new UsernamePasswordToken("zhangsan", "123456");  
            39.          
            40. //执行认证提交
            41. try
            42. //执行认证提交
            43.             subject.login(token);  
            44. catch
            45.             e.printStackTrace();  
            46.         }  
            47.           
            48. // 是否认证通过
            49. boolean
            50. "是否认证通过:"+isAuthenticated);  
            51.           
            52. //退出操作
            53.         subject.logout();  
            54.           
            55. // 是否认证通过
            56.         isAuthenticated = subject.isAuthenticated();  
            57. "是否认证通过:"+isAuthenticated);  
            58.     }  
            59.       
            60. //自定义Realm
            61. @Test
            62. public void
            63.           
            64. // 创建SecurityManager工厂。通过ini配置文件创建securityManager
            65. new IniSecurityManagerFactory("classpath:shiro-realm.ini");  
            66.           
            67. // 创建SecurityManager
            68.         SecurityManager securityManager = factory.getInstance();  
            69.           
            70. // 将securityManager设置到当前的运行环境中
            71.         SecurityUtils.setSecurityManager(securityManager);  
            72.           
            73. // 从SecurityUtils中创建一个subject
            74.         Subject subject = SecurityUtils.getSubject();  
            75.           
            76. // 在认证提交前准备token(令牌)
            77. // 这里的账号和密码 将来是由用户输入进去的
            78. new UsernamePasswordToken("zhangsan", "123456");  
            79.           
            80. //执行认证提交
            81. try
            82. //执行认证提交
            83.             subject.login(token);  
            84. catch
            85.             e.printStackTrace();  
            86.         }  
            87.           
            88. // 是否认证通过
            89. boolean
            90. "是否认证通过:"+isAuthenticated);  
            91.           
            92.     }  
            93. //自定义Realm实现散列值匹配
            94. @Test
            95. public void
            96.           
            97. // 创建SecurityManager工厂。通过ini配置文件创建securityManager
            98. new IniSecurityManagerFactory("classpath:shiro-realm-md5.ini");  
            99.           
            100. // 创建SecurityManager
            101.         SecurityManager securityManager = factory.getInstance();  
            102.           
            103. // 将securityManager设置到当前的运行环境中
            104.         SecurityUtils.setSecurityManager(securityManager);  
            105.           
            106. // 从SecurityUtils中创建一个subject
            107.         Subject subject = SecurityUtils.getSubject();  
            108.           
            109. // 在认证提交前准备token(令牌)
            110. // 这里的账号和密码 将来是由用户输入进去的
            111. new UsernamePasswordToken("zhangsan", "123456");  
            112.           
            113. //执行认证提交
            114. try
            115. //执行认证提交
            116.             subject.login(token);  
            117. catch
            118.             e.printStackTrace();  
            119.         }  
            120.           
            121. // 是否认证通过
            122. boolean
            123. "是否认证通过:"+isAuthenticated);  
            124.           
            125.     }  
            126. }

            package liuxun.test.shiro.authentication;
            
            import org.apache.shiro.SecurityUtils;
            import org.apache.shiro.authc.AuthenticationException;
            import org.apache.shiro.authc.UsernamePasswordToken;
            import org.apache.shiro.config.IniSecurityManagerFactory;
            import org.apache.shiro.mgt.SecurityManager;
            import org.apache.shiro.subject.Subject;
            import org.apache.shiro.util.Factory;
            import org.junit.Test;
            
            /**
             * 认证测试
             * 
             * @author liuxun
             *
             */
            public class AuthenticationTest {
            
            	// 用户登录和退出
            	@Test
            	public void testLoginAndLogout() {
            
            		// 创建SecurityManager工厂。通过ini配置文件创建securityManager
            		Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-first.ini");
            
            		// 创建SecurityManager
            		SecurityManager securityManager = factory.getInstance();
            
            		// 将securityManager设置到当前的运行环境中
            		SecurityUtils.setSecurityManager(securityManager);
            
            		// 从SecurityUtils中创建一个subject
            		Subject subject = SecurityUtils.getSubject();
            
            		// 在认证提交前准备token(令牌)
            		// 这里的账号和密码 将来是由用户输入进去的
            		UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "123456");
                   
            		//执行认证提交
            		try {
            			//执行认证提交
            			subject.login(token);
            		} catch (AuthenticationException e) {
            			e.printStackTrace();
            		}
            		
            		// 是否认证通过
            		boolean isAuthenticated = subject.isAuthenticated();
            		System.out.println("是否认证通过:"+isAuthenticated);
            		
            		//退出操作
            		subject.logout();
            		
            		// 是否认证通过
            		isAuthenticated = subject.isAuthenticated();
            		System.out.println("是否认证通过:"+isAuthenticated);
            	}
            	
            	//自定义Realm
            	@Test
            	public void testCustomRealm() {
            		
            		// 创建SecurityManager工厂。通过ini配置文件创建securityManager
            		Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-realm.ini");
            		
            		// 创建SecurityManager
            		SecurityManager securityManager = factory.getInstance();
            		
            		// 将securityManager设置到当前的运行环境中
            		SecurityUtils.setSecurityManager(securityManager);
            		
            		// 从SecurityUtils中创建一个subject
            		Subject subject = SecurityUtils.getSubject();
            		
            		// 在认证提交前准备token(令牌)
            		// 这里的账号和密码 将来是由用户输入进去的
            		UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "123456");
            		
            		//执行认证提交
            		try {
            			//执行认证提交
            			subject.login(token);
            		} catch (AuthenticationException e) {
            			e.printStackTrace();
            		}
            		
            		// 是否认证通过
            		boolean isAuthenticated = subject.isAuthenticated();
            		System.out.println("是否认证通过:"+isAuthenticated);
            		
            	}
            	//自定义Realm实现散列值匹配
            	@Test
            	public void testCustomRealmMd5() {
            		
            		// 创建SecurityManager工厂。通过ini配置文件创建securityManager
            		Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-realm-md5.ini");
            		
            		// 创建SecurityManager
            		SecurityManager securityManager = factory.getInstance();
            		
            		// 将securityManager设置到当前的运行环境中
            		SecurityUtils.setSecurityManager(securityManager);
            		
            		// 从SecurityUtils中创建一个subject
            		Subject subject = SecurityUtils.getSubject();
            		
            		// 在认证提交前准备token(令牌)
            		// 这里的账号和密码 将来是由用户输入进去的
            		UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "123456");
            		
            		//执行认证提交
            		try {
            			//执行认证提交
            			subject.login(token);
            		} catch (AuthenticationException e) {
            			e.printStackTrace();
            		}
            		
            		// 是否认证通过
            		boolean isAuthenticated = subject.isAuthenticated();
            		System.out.println("是否认证通过:"+isAuthenticated);
            		
            	}
            }

            MD5Test.java  测试shiro散列算法


            [java] view plain copy

            print ?

              1. package
              2.   
              3. import
              4. import
              5.   
              6. public class
              7. public static void
              8. //原始密码
              9. "123456";  
              10. //盐
              11. "qwerty";  
              12. //散列次数
              13. int hashIterations = 2;  
              14. //上边散列1次:48474f975022f960bc2afbe49be581e8
              15. //上边散列2次:13f79dafcbbedc313273e2b891ac84d3
              16.           
              17. //构造方法中:
              18. //第一个参数:明文,原始密码
              19. //第二个参数:盐,通过使用随机字符串
              20. //第三个参数:散列的次数,比如散列两次,相当于md5(md5(''))
              21. new
              22.           
              23.         String password_md5 = md5Hash.toString();  
              24.         System.out.println(password_md5);  
              25.           
              26. //使用后SimpleHash
              27. //第一个参数:散列算法
              28. new SimpleHash("md5", source, salt, hashIterations);  
              29.         System.out.println(simpleHash.toString());  
              30.     }  
              31. }


              package liuxun.test.shiro.authentication;
              
              import org.apache.shiro.crypto.hash.Md5Hash;
              import org.apache.shiro.crypto.hash.SimpleHash;
              
              public class MD5Test {
              	public static void main(String[] args) {
              		//原始密码
              		String source = "123456";
              		//盐
              		String salt = "qwerty";
              		//散列次数
              		int hashIterations = 2;
              		//上边散列1次:48474f975022f960bc2afbe49be581e8
              		//上边散列2次:13f79dafcbbedc313273e2b891ac84d3
              		
              		//构造方法中:
              		//第一个参数:明文,原始密码
              		//第二个参数:盐,通过使用随机字符串
              		//第三个参数:散列的次数,比如散列两次,相当于md5(md5(''))
              		Md5Hash md5Hash = new Md5Hash(source, salt, hashIterations);
              	    
              		String password_md5 = md5Hash.toString();
              		System.out.println(password_md5);
              		
              		//使用后SimpleHash
              		//第一个参数:散列算法
              		SimpleHash simpleHash = new SimpleHash("md5", source, salt, hashIterations);
              		System.out.println(simpleHash.toString());
              	}
              }

              AuthorizationTest  测试授权


              [java] view plain copy

              print ?

              1. package
              2.   
              3. import
              4.   
              5. import
              6. import
              7. import
              8. import
              9. import
              10. import
              11. import
              12. import
              13.   
              14. /**
              15.  * 授权测试
              16.  * 
              17.  * @author liuxun
              18.  *
              19.  */
              20. public class
              21.   
              22. // 角色授权、资源授权测试
              23. @Test
              24. public void
              25.   
              26. // 创建SecurityManager工厂
              27. new IniSecurityManagerFactory("classpath:shiro-permission.ini");  
              28.   
              29. // 创建SecurityManager
              30.         SecurityManager securityManager = factory.getInstance();  
              31.   
              32. // 将SecurityManager设置到系统运行环境,和Spring整合后将SecurityManager配置在Spring容器中,一般单例管理
              33.         SecurityUtils.setSecurityManager(securityManager);  
              34.   
              35. // 创建subject
              36.         Subject subject = SecurityUtils.getSubject();  
              37.   
              38. // 创建token令牌
              39. new UsernamePasswordToken("zhangsan", "123");  
              40.   
              41. // 执行认证
              42. try
              43.             subject.login(token);  
              44. catch
              45.             e.printStackTrace();  
              46.         }  
              47.   
              48. "认证状态:"
              49.   
              50. // 认证通过后执行授权
              51.   
              52. // 基于角色的授权
              53. // hasRole传入角色标识
              54. boolean ishasRole = subject.hasRole("role1");  
              55. "单个角色判断 "
              56. // hasAllRoles 是否拥有多个角色
              57. boolean hasAllRoles = subject.hasAllRoles(Arrays.asList("role1", "role2", "role3"));  
              58. "多个角色判断 "
              59.   
              60. // 使用check方法进行授权,如果授权不通过会抛出异常,用于断言
              61. "role2");  
              62.   
              63. // 基于资源的权限
              64. // isPermitted传入权限标识符
              65. boolean isPermitted = subject.isPermitted("user:create:1");  
              66. "单个权限判断 "
              67.   
              68. boolean isPermittedAll = subject.isPermittedAll("user:create:1", "user:delete");  
              69. "多个权限判断 "
              70.   
              71. // 使用check方法进行授权测试,如果授权不通过会抛出异常
              72. "item:delete");  
              73.     }  
              74.   
              75. // 自定义Realm进行资源授权测试
              76. @Test
              77. public void
              78.   
              79. // 创建SecurityManager工厂
              80. new IniSecurityManagerFactory("classpath:shiro-realm.ini");  
              81.   
              82. // 创建SecurityManager
              83.         SecurityManager securityManager = factory.getInstance();  
              84.   
              85. // 将SecurityManager设置到系统运行环境,和Spring整合后将SecurityManager配置在Spring容器中,一般单例管理
              86.         SecurityUtils.setSecurityManager(securityManager);  
              87.   
              88. // 创建subject
              89.         Subject subject = SecurityUtils.getSubject();  
              90.   
              91. // 创建token令牌
              92. new UsernamePasswordToken("zhangsan", "123");  
              93.   
              94. // 执行认证
              95. try
              96.             subject.login(token);  
              97. catch
              98.             e.printStackTrace();  
              99.         }  
              100.   
              101. "认证状态:"
              102.   
              103. // 认证通过后执行授权
              104.   
              105. // 基于资源的授权,调用isPermitted方法会调用CustomRealm从数据库中查询正确权限数据
              106. // isPermitted传入权限标识符,判断user:create:1是否在CustomRealm查询到的权限数据之内
              107. boolean isPermitted = subject.isPermitted("user:create:1");  
              108. "单个权限判断 "
              109.   
              110. boolean isPermittedAll = subject.isPermittedAll("user:create:1", "user:delete");  
              111. "多个权限判断 "
              112.   
              113. // 使用check方法进行授权测试,如果授权不通过会抛出异常
              114. "item:add:1");  
              115.     }  
              116.   
              117. }


              package liuxun.test.shiro.authorization;
              
              import java.util.Arrays;
              
              import org.apache.shiro.SecurityUtils;
              import org.apache.shiro.authc.AuthenticationException;
              import org.apache.shiro.authc.UsernamePasswordToken;
              import org.apache.shiro.config.IniSecurityManagerFactory;
              import org.apache.shiro.mgt.SecurityManager;
              import org.apache.shiro.subject.Subject;
              import org.apache.shiro.util.Factory;
              import org.junit.Test;
              
              /**
               * 授权测试
               * 
               * @author liuxun
               *
               */
              public class AuthorizationTest {
              
              	// 角色授权、资源授权测试
              	@Test
              	public void testAuthorization() {
              
              		// 创建SecurityManager工厂
              		Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-permission.ini");
              
              		// 创建SecurityManager
              		SecurityManager securityManager = factory.getInstance();
              
              		// 将SecurityManager设置到系统运行环境,和Spring整合后将SecurityManager配置在Spring容器中,一般单例管理
              		SecurityUtils.setSecurityManager(securityManager);
              
              		// 创建subject
              		Subject subject = SecurityUtils.getSubject();
              
              		// 创建token令牌
              		UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "123");
              
              		// 执行认证
              		try {
              			subject.login(token);
              		} catch (AuthenticationException e) {
              			e.printStackTrace();
              		}
              
              		System.out.println("认证状态:" + subject.isAuthenticated());
              
              		// 认证通过后执行授权
              
              		// 基于角色的授权
              		// hasRole传入角色标识
              		boolean ishasRole = subject.hasRole("role1");
              		System.out.println("单个角色判断 " + ishasRole);
              		// hasAllRoles 是否拥有多个角色
              		boolean hasAllRoles = subject.hasAllRoles(Arrays.asList("role1", "role2", "role3"));
              		System.out.println("多个角色判断 " + hasAllRoles);
              
              		// 使用check方法进行授权,如果授权不通过会抛出异常,用于断言
              		subject.checkRole("role2");
              
              		// 基于资源的权限
              		// isPermitted传入权限标识符
              		boolean isPermitted = subject.isPermitted("user:create:1");
              		System.out.println("单个权限判断 " + isPermitted);
              
              		boolean isPermittedAll = subject.isPermittedAll("user:create:1", "user:delete");
              		System.out.println("多个权限判断 " + isPermittedAll);
              
              		// 使用check方法进行授权测试,如果授权不通过会抛出异常
              		subject.checkPermission("item:delete");
              	}
              
              	// 自定义Realm进行资源授权测试
              	@Test
              	public void testAuthorizationCustomRealm() {
              
              		// 创建SecurityManager工厂
              		Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-realm.ini");
              
              		// 创建SecurityManager
              		SecurityManager securityManager = factory.getInstance();
              
              		// 将SecurityManager设置到系统运行环境,和Spring整合后将SecurityManager配置在Spring容器中,一般单例管理
              		SecurityUtils.setSecurityManager(securityManager);
              
              		// 创建subject
              		Subject subject = SecurityUtils.getSubject();
              
              		// 创建token令牌
              		UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "123");
              
              		// 执行认证
              		try {
              			subject.login(token);
              		} catch (AuthenticationException e) {
              			e.printStackTrace();
              		}
              
              		System.out.println("认证状态:" + subject.isAuthenticated());
              
              		// 认证通过后执行授权
              
              		// 基于资源的授权,调用isPermitted方法会调用CustomRealm从数据库中查询正确权限数据
              		// isPermitted传入权限标识符,判断user:create:1是否在CustomRealm查询到的权限数据之内
              		boolean isPermitted = subject.isPermitted("user:create:1");
              		System.out.println("单个权限判断 " + isPermitted);
              
              		boolean isPermittedAll = subject.isPermittedAll("user:create:1", "user:delete");
              		System.out.println("多个权限判断 " + isPermittedAll);
              
              		// 使用check方法进行授权测试,如果授权不通过会抛出异常
              		subject.checkPermission("item:add:1");
              	}
              
              }

              CustomRealm.java  自定义Realm


              [ja

              va]      view plain       copy     
                   
                   print     ?       
              1. package
              2.   
              3. import
              4. import
              5.   
              6. import
              7. import
              8. import
              9. import
              10. import
              11. import
              12. import
              13. import
              14. import
              15.   
              16. public class CustomRealm extends
              17. // 设置Realm的名称
              18. @Override
              19. public
              20. return "CustomRealm";  
              21.     }  
              22.   
              23. // 支持UsernamePasswordToken
              24. @Override
              25. public boolean
              26. return token instanceof
              27.     }  
              28.   
              29. // 用于认证
              30. @Override
              31. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws
              32.   
              33. // token是用户输入的
              34. // 第一步从token中取出身份信息
              35.         String usercode = (String) token.getPrincipal();  
              36.   
              37. // 第二步:根据用户输入的usercode从数据库查询
              38. // ......
              39.   
              40. // 如果查询不到返回null
              41. // 数据库中用户账号是zhangsan
              42. if (!usercode.equals("zhangsan")) {  
              43. return null;  
              44.         }  
              45.   
              46. // 模拟从数据库中查询到密码
              47. "123456";  
              48.   
              49. // 如果查询到返回认证信息AuthenticationInfo
              50. new
              51. this.getName());  
              52.   
              53. return
              54.     }  
              55.   
              56. // 用于授权
              57. @Override
              58. protected
              59.   
              60. //从principals获取身份信息
              61. //将getPrimaryPrincipal方法返回值转为真实类型
              62. //(在上边的doGetAuthenticationInfo认证通过后填充到SimpleAuthenticationInfo中身份类型)
              63.         String userCode = (String) principals.getPrimaryPrincipal();  
              64.           
              65. //根据身份信息从数据库中获取权限信息
              66. //模拟从数据库中取到的数据
              67. new
              68. "user:create");//用户创建
              69. "items:add");//商品添加权限
              70. //....
              71.           
              72. //查询到权限数据,返回授权信息(要包括上边的permissions)
              73. new
              74. //将上边查询到的授权信息填充到simpleAuthorizationInfo对象中
              75.         simpleAuthorizationInfo.addStringPermissions(permissions);  
              76.           
              77. return
              78.     }  
              79. }

              package liuxun.test.shiro.realm;
              
              import java.util.ArrayList;
              import java.util.List;
              
              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.authc.UsernamePasswordToken;
              import org.apache.shiro.authz.AuthorizationInfo;
              import org.apache.shiro.authz.SimpleAuthorizationInfo;
              import org.apache.shiro.realm.AuthorizingRealm;
              import org.apache.shiro.subject.PrincipalCollection;
              
              public class CustomRealm extends AuthorizingRealm {
              	// 设置Realm的名称
              	@Override
              	public String getName() {
              		return "CustomRealm";
              	}
              
              	// 支持UsernamePasswordToken
              	@Override
              	public boolean supports(AuthenticationToken token) {
              		return token instanceof UsernamePasswordToken;
              	}
              
              	// 用于认证
              	@Override
              	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
              
              		// token是用户输入的
              		// 第一步从token中取出身份信息
              		String usercode = (String) token.getPrincipal();
              
              		// 第二步:根据用户输入的usercode从数据库查询
              		// ......
              
              		// 如果查询不到返回null
              		// 数据库中用户账号是zhangsan
              		if (!usercode.equals("zhangsan")) {
              			return null;
              		}
              
              		// 模拟从数据库中查询到密码
              		String password = "123456";
              
              		// 如果查询到返回认证信息AuthenticationInfo
              		SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(usercode, password,
              				this.getName());
              
              		return simpleAuthenticationInfo;
              	}
              
              	// 用于授权
              	@Override
              	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
              
              		//从principals获取身份信息
              		//将getPrimaryPrincipal方法返回值转为真实类型
              		//(在上边的doGetAuthenticationInfo认证通过后填充到SimpleAuthenticationInfo中身份类型)
              		String userCode = (String) principals.getPrimaryPrincipal();
              		
              		//根据身份信息从数据库中获取权限信息
              		//模拟从数据库中取到的数据
              		List<String>  permissions = new ArrayList<String>();
              		permissions.add("user:create");//用户创建
              		permissions.add("items:add");//商品添加权限
              		//....
              		
              		//查询到权限数据,返回授权信息(要包括上边的permissions)
              		SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
              		//将上边查询到的授权信息填充到simpleAuthorizationInfo对象中
              		simpleAuthorizationInfo.addStringPermissions(permissions);
              		
              		return simpleAuthorizationInfo;
              	}
              }

              CustomRealmMd5.java  自定义Realm处理散列算法


              [java] view plain copy

              print ?

              1. package
              2.   
              3. import
              4. import
              5. import
              6. import
              7. import
              8. import
              9. import
              10. import
              11. import
              12.   
              13. public class CustomRealmMd5 extends
              14. // 设置Realm名称
              15. @Override
              16. public void
              17. super.setName("CustomRealmMd5");  
              18.     }  
              19.   
              20. // 支持UsernamePasswordToken
              21. @Override
              22. public boolean
              23. return token instanceof
              24.     }  
              25.   
              26. // 用于认证
              27. @Override
              28. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws
              29.   
              30. // token保存了用户输入的身份信息userName和password
              31. // 第一步:从token中取出身份信息
              32.         String userCode = (String) token.getPrincipal();  
              33.   
              34. // 第二步:根据用户输入的userCode从数据库查询
              35. // ....
              36. // 如果查询不到返回null 假设用户输入的账号是zhansgan
              37. // 模拟从数据库中查询账号是zhangsan的用户
              38. if (!userCode.equals("zhangsan")) {  
              39. return null;  
              40.         }  
              41.   
              42. // 模拟从数据库中查询到密码(散列值)
              43. // 按照固定规则加密的结果,此密码是在数据库中存储的,原始密码是123456 盐是qwerty
              44. "48474f975022f960bc2afbe49be581e8";  
              45. // 盐,随机字符串,此随机字符串也是在数据库中存储的,模拟从数据库中获取
              46. "qwerty";  
              47.   
              48. // 如果查询到则返回认证信息AuthenticationInfo
              49. new
              50. this.getName());  
              51.           
              52. return
              53.     }  
              54.   
              55. // 用于授权
              56. @Override
              57. protected
              58. return null;  
              59.     }  
              60.   
              61. }


              package liuxun.test.shiro.realm;
              
              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.authc.UsernamePasswordToken;
              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 CustomRealmMd5 extends AuthorizingRealm {
              	// 设置Realm名称
              	@Override
              	public void setName(String name) {
              		super.setName("CustomRealmMd5");
              	}
              
              	// 支持UsernamePasswordToken
              	@Override
              	public boolean supports(AuthenticationToken token) {
              		return token instanceof UsernamePasswordToken;
              	}
              
              	// 用于认证
              	@Override
              	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
              
              		// token保存了用户输入的身份信息userName和password
              		// 第一步:从token中取出身份信息
              		String userCode = (String) token.getPrincipal();
              
              		// 第二步:根据用户输入的userCode从数据库查询
              		// ....
              		// 如果查询不到返回null 假设用户输入的账号是zhansgan
              		// 模拟从数据库中查询账号是zhangsan的用户
              		if (!userCode.equals("zhangsan")) {
              			return null;
              		}
              
              		// 模拟从数据库中查询到密码(散列值)
              		// 按照固定规则加密的结果,此密码是在数据库中存储的,原始密码是123456 盐是qwerty
              		String password = "48474f975022f960bc2afbe49be581e8";
              		// 盐,随机字符串,此随机字符串也是在数据库中存储的,模拟从数据库中获取
              		String salt = "qwerty";
              
              		// 如果查询到则返回认证信息AuthenticationInfo
              		SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(userCode, password,
              				ByteSource.Util.bytes(salt), this.getName());
              		
              		return simpleAuthenticationInfo;
              	}
              
              	// 用于授权
              	@Override
              	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
              		return null;
              	}
              
              }

              log4j.properties


              [plain] view plain copy

              print ?

                1. log4j.rootLogger=debug, stdout  
                2.   
                3. log4j.appender.stdout=org.apache.log4j.ConsoleAppender  
                4. log4j.appender.stdout.layout=org.apache.log4j.PatternLayout  
                5. log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n


                log4j.rootLogger=debug, stdout
                
                log4j.appender.stdout=org.apache.log4j.ConsoleAppender
                log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
                log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n

                shiro-first.ini  用于用户身份信息从配置文件中取


                [plain] view plain copy

                print ?

                1. #对用户信息进行配置  
                2. [users]  
                3. #用户账号和密码  
                4. zhangsan=123456  
                5. lisi=654321


                #对用户信息进行配置
                [users]
                #用户账号和密码
                zhangsan=123456
                lisi=654321

                shiro-permission.ini  用于用户权限和身份信息从配置文件中取


                [plain] view plain copy

                print ?

                1. #用户  
                2. [users]  
                3. #用户zhang的密码是123,此用户具有role1和role2两个角色  
                4. zhangsan=123,role1,role2  
                5. wang=123,role2  
                6.   
                7. #权限  
                8. [roles]  
                9. #角色role1对资源user拥有create、update权限  
                10. role1=user:create,user:update  
                11. #角色role2对资源user拥有create、delete权限  
                12. role2=user:create,user:delete  
                13. #role3对资源user拥有create权限  
                14. role3=user:create
                1.  


                #用户
                [users]
                #用户zhang的密码是123,此用户具有role1和role2两个角色
                zhangsan=123,role1,role2
                wang=123,role2
                
                #权限
                [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

                shiro-realm.ini  用于配置自定义Realm 从数据库中获取身份和权限以及角色信息

                [plain] view plain copy

                print     ?       
                1. [main]  
                2. #自定义realm  
                3. customRealm=liuxun.test.shiro.realm.CustomRealm  
                4. #将realm设置到SecurityManager,相当于Spring中的注入  
                5. securityManager.realms=$customRealm

                [main]
                #自定义realm
                customRealm=liuxun.test.shiro.realm.CustomRealm
                #将realm设置到SecurityManager,相当于Spring中的注入
                securityManager.realms=$customRealm

                shiro-realm-md5.ini 用于配置自定义Realm 从数据库中获取身份和权限以及角色信息 以及散列配置


                [plain] view plain copy

                print     ?       
                1. [main]  
                2. #定义凭证匹配器  
                3. credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher  
                4. #散列算法  
                5. credentialsMatcher.hashAlgorithmName=md5  
                6. #散列次数  
                7. credentialsMatcher.hashIterations=1  
                8.   
                9. #将凭证匹配器设置到Realm  
                10. customRealm=liuxun.test.shiro.realm.CustomRealmMd5  
                11. customRealm.credentialsMatcher=$credentialsMatcher  
                12. #将Realm设置到securityManager  
                13. securityManager.realms=$customRealm