shiro-入门Realm
为什么要用Realm
在上篇文章中我们将身份信息(用户名/密码/角色/权限)写在配置文件中,但是实际开发中,这些身份信息应该保存在数据中,因此我们需要自定义Realm来从数据中获取身份信息,进行验证。
自定义Realm
- 定义一个MyRealm,继承
AuthorizingRealm
package com.shiro.realm; import java.util.HashSet; import java.util.Set; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UnknownAccountException; 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.realm.Realm; import org.apache.shiro.subject.PrincipalCollection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class MyRealm1 extends AuthorizingRealm{ private static final transient Logger log = LoggerFactory.getLogger(Main.class); /** * 获取身份信息,我们可以在这个方法中,从数据库获取该用户的权限和角色信息 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { log.info("----------doGetAuthorizationInfo方法被调用----------"); String username = (String) getAvailablePrincipal(principals); //我们可以通过用户名从数据库获取权限/角色信息 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //权限 Set<String> s = new HashSet<String>(); s.add("printer:print"); s.add("printer:query"); info.setStringPermissions(s); //角色 Set<String> r = new HashSet<String>(); r.add("role1"); info.setRoles(r); return info; } /** * 在这个方法中,进行身份验证 */ @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { //用户名 String username = (String) token.getPrincipal(); log.info("username:"+username); //密码 String password = new String((char[])token.getCredentials()); log.info("password:"+password); //从数据库获取用户名密码进行匹配,这里为了方面,省略数据库操作 if(!"admin".equals(username)){ throw new UnknownAccountException(); } if(!"123".equals(password)){ throw new IncorrectCredentialsException(); } //身份验证通过,返回一个身份信息 AuthenticationInfo aInfo = new SimpleAuthenticationInfo(username,password,getName()); return aInfo; } }
- 让我们定义的Realm起作用,就要在配置文件中配置(shiro-realm.ini)
#声明一个realm
MyRealm1=com.shiro.realm.MyRealm1
#指定securityManager的realms实现
securityManager.realms=$MyRealm1
- 测试
package com.shiro.realm; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.LockedAccountException; import org.apache.shiro.authc.UnknownAccountException; 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.slf4j.Logger; import org.slf4j.LoggerFactory; public class Main { private static final transient Logger log = LoggerFactory.getLogger(Main.class); public static void main(String[] args) { //获取SecurityManager的实例 Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-realm.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); Subject currenUser = SecurityUtils.getSubject(); //如果还未认证 if(!currenUser.isAuthenticated()){ UsernamePasswordToken token = new UsernamePasswordToken("admin","123"); token.setRememberMe(true); try { currenUser.login(token); } catch (UnknownAccountException uae) { log.info("没有该用户: " + token.getPrincipal()); } catch (IncorrectCredentialsException ice) { log.info( token.getPrincipal() + " 的密码不正确!"); } catch (LockedAccountException lae) { log.info( token.getPrincipal() + " 被锁定 ,请联系管理员"); }catch (AuthenticationException ae) { //其他未知的异常 } } if(currenUser.isAuthenticated()) log.info("用户 "+currenUser.getPrincipal() +" 登录成功"); //是否有role1这个角色 if(currenUser.hasRole("role1")){ log.info("有角色role1"); }else{ log.info("没有角色role1"); } //是否有对打印机进行打印操作的权限 if(currenUser.isPermitted("printer:print")){ log.info("可以对打印机进行打印操作"); }else { log.info("不可以对打印机进行打印操作"); } } }
散列算法支持
一般我们存入数据库的密码都是通过加密的,比如将“原密码+盐”进行一次或多次MD5计算,shiro提供了对散列算法的支持
package com.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.authz.AuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; public class UserRealm extends AuthorizingRealm { private String salt = "hehe";//盐 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) { // TODO Auto-generated method stub return null; } @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { //用户输入的用户名 String username = (String) token.getPrincipal(); //如果数据库中没有这个用户,则返回null,登录失败 if(!username.equals("xiaozhou")) return null; //从数据库中查询密码 String password = "42029a889cc26562c986346114c02367"; SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password, ByteSource.Util.bytes(salt), getName()); return info; } }
使用MD5的realm和一般的realm没有太多区别,唯一的区别在于:不使用散列算法(即对密码加密)的话,从数据库查询出来的密码是明文,否则查询出来的是密文,我们没法使用密文来直接比对判断密码是否正确,为了让shiro自动帮我们先加密再比对,我们要在配置文件ini中告诉shiro使用什么算法
[main] #密码匹配器 credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher #匹配器使用md5 credentialsMatcher.hashAlgorithmName=md5 #进行几次散列(用md5算法做几次运算) credentialsMatcher.hashIterations=1 #realm userRealm=com.shiro.realm.UserRealm #该realm使用的匹配器是哪个 userRealm.credentialsMatcher=$credentialsMatcher #使用哪个realm securityManager.realms=$userRealm
多个Realm
有时候,我们需要进行多次身份验证,我们可以定义多个Realm,如同流水线一样,shiro会依次调用Realm
MyRealm1
package com.shiro.mutilrealm; import java.util.HashSet; import java.util.Set; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UnknownAccountException; 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.realm.Realm; import org.apache.shiro.subject.PrincipalCollection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.shiro.realm.Main; public class MyRealm1 extends AuthorizingRealm{ private static final transient Logger log = LoggerFactory.getLogger(Main.class); @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String) getAvailablePrincipal(principals); //通过用户名从数据库获取权限字符串 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //权限 Set<String> s = new HashSet<String>(); s.add("printer:print"); s.add("printer:query"); info.setStringPermissions(s); //角色 Set<String> r = new HashSet<String>(); r.add("role1"); info.setRoles(r); return info; } @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { log.info("MyRealm1开始认证。。。。。。"); //用户名 String username = (String) token.getPrincipal(); log.info("username:"+username); //密码 String password = new String((char[])token.getCredentials()); log.info("password:"+password); //从数据库获取用户名密码进行匹配,这里为了方面,省略数据库操作 if(!"admin".equals(username)){ throw new UnknownAccountException(); } if(!"123".equals(password)){ throw new IncorrectCredentialsException(); } //身份验证通过 AuthenticationInfo aInfo = new SimpleAuthenticationInfo(username,password,getName()); return aInfo; } }
- MyRealm2和MyRealm1 代码其实基本上是一样的,直接复制一份即可。当然,如果有需求,我们可以自由地定义修改Realm。这里只做个示例而已。
配置Authenticator和AuthenticationStrategy
这两个东东是啥玩意?
上面我们配置了多个Realm进行身份验证,假设一下:MyRealm1 验证通过了,MyRealm2验证不通过怎么办,这就需要定义一个验证策略来处理这种情况。Strategy的意思就是策略。Authenticator就是验证器
配置文件(shiro-mutil-realm.ini)
#声明一个realm MyRealm1=com.shiro.mutilrealm.MyRealm1 MyRealm2=com.shiro.mutilrealm.MyRealm2 #配置验证器 authenticator = org.apache.shiro.authc.pam.ModularRealmAuthenticator #配置策略 # AllSuccessfulStrategy 表示 MyRealm1和MyRealm2 认证都通过才算通过 authcStrategy = org.apache.shiro.authc.pam.AllSuccessfulStrategy #将验证器和策略关联起来 authenticator.authenticationStrategy = $authcStrategy #配置验证器所使用的Realm authenticator.realms=$MyRealm2,$MyRealm1 #把Authenticator设置给securityManager securityManager.authenticator = $authenticator ########################################################################## # 1. AtLeastOneSuccessfulStrategy :如果一个(或更多)Realm 验证成功,则整体的尝试被认 # 为是成功的。如果没有一个验证成功,则整体尝试失败。 # 2. FirstSuccessfulStrategy 只有第一个成功地验证的Realm 返回的信息将被使用。所有进一步 # 的Realm 将被忽略。如果没有一个验证成功,则整体尝试失败 # 3. AllSucessfulStrategy 为了整体的尝试成功,所有配置的Realm 必须验证成功。如果没有一 # 个验证成功,则整体尝试失败。 # ModularRealmAuthenticator 默认的是AtLeastOneSuccessfulStrategy ###########################################################################
- 测试
package com.shiro.mutilrealm; import java.util.List; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.LockedAccountException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Main { private static final transient Logger log = LoggerFactory.getLogger(Main.class); public static void main(String[] args) { //获取SecurityManager的实例 Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-mutil-realm.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); Subject currenUser = SecurityUtils.getSubject(); //如果还未认证 if(!currenUser.isAuthenticated()){ UsernamePasswordToken token = new UsernamePasswordToken("admin","123"); token.setRememberMe(true); try { currenUser.login(token); } catch (UnknownAccountException uae) { log.info("没有该用户: " + token.getPrincipal()); } catch (IncorrectCredentialsException ice) { log.info( token.getPrincipal() + " 的密码不正确!"); } catch (LockedAccountException lae) { log.info( token.getPrincipal() + " 被锁定 ,请联系管理员"); }catch (AuthenticationException ae) { //其他未知的异常 } } if(currenUser.isAuthenticated()) log.info("用户 "+currenUser.getPrincipal() +" 登录成功"); //得到一个身份集合 PrincipalCollection principalCollection = currenUser.getPrincipals(); } }
结果很明显,MyRealm1和MyRealm2依次执行
多个Realm验证顺序
隐式排列
- 当你配置多个realm的时候,处理的顺序默认就是你配置的顺序。
- 这种情况通常就是只定义了realm,而没有配置securityManager的realms
显式排列
- 也就是显示的配置securityManager.realms,那么执行的顺序就是你配置该值的realm的顺序。
- 通常更推荐显示排列。
我们可以简单的理解为,多个Realm验证的顺序,就是我们配置的顺序