Shiro学习笔记(2)——身份验证之Realm

怼烎@ 2022-08-05 00:56 491阅读 0赞
  • 环境准备
  • 什么是Realm
  • 为什么要用Realm
  • 自定义Realm
  • 散列算法支持
  • 多个Realm
  • 配置Authenticator和AuthenticationStrategy
  • 自定义AuthenticationStrategy验证策略
  • 多个Realm验证顺序

环境准备

  • 创建java工程
  • 需要的jar包

这里写图片描述

  • 大家也可以使用maven,参考官网

什么是Realm

  • 在我所看的学习资料中,关于Realm的定义,写了整整一长串,但是对于初学者来说,看定义实在是太头疼了。
  • 对于什么是Realm,我使用过之后,个人总结一下:shiro要进行身份验证,就要从realm中获取相应的身份信息来进行验证,简单来说,我们可以自行定义realm,在realm中,从数据库获取身份信息,然后和 用户输入的身份信息进行匹配。这一切都由我们自己来定义。

为什么要用Realm

在Shiro学习笔记(1)——shiro入门中,我们将身份信息(用户名/密码/角色/权限)写在配置文件中,但是实际开发中,这些身份信息应该保存在数据中,因此我们需要自定义Realm来从数据中获取身份信息,进行验证。

自定义Realm

  • 定义一个MyRealm,继承AuthorizingRealm

    package com.shiro.realm;

  1. import java.util.HashSet;
  2. import java.util.Set;
  3. import org.apache.shiro.authc.AuthenticationException;
  4. import org.apache.shiro.authc.AuthenticationInfo;
  5. import org.apache.shiro.authc.AuthenticationToken;
  6. import org.apache.shiro.authc.IncorrectCredentialsException;
  7. import org.apache.shiro.authc.SimpleAuthenticationInfo;
  8. import org.apache.shiro.authc.UnknownAccountException;
  9. import org.apache.shiro.authc.UsernamePasswordToken;
  10. import org.apache.shiro.authz.AuthorizationInfo;
  11. import org.apache.shiro.authz.SimpleAuthorizationInfo;
  12. import org.apache.shiro.realm.AuthorizingRealm;
  13. import org.apache.shiro.realm.Realm;
  14. import org.apache.shiro.subject.PrincipalCollection;
  15. import org.slf4j.Logger;
  16. import org.slf4j.LoggerFactory;
  17. public class MyRealm1 extends AuthorizingRealm{
  18. private static final transient Logger log = LoggerFactory.getLogger(Main.class);
  19. /** * 获取身份信息,我们可以在这个方法中,从数据库获取该用户的权限和角色信息 */
  20. @Override
  21. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
  22. log.info("----------doGetAuthorizationInfo方法被调用----------");
  23. String username = (String) getAvailablePrincipal(principals);
  24. //我们可以通过用户名从数据库获取权限/角色信息
  25. SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
  26. //权限
  27. Set<String> s = new HashSet<String>();
  28. s.add("printer:print");
  29. s.add("printer:query");
  30. info.setStringPermissions(s);
  31. //角色
  32. Set<String> r = new HashSet<String>();
  33. r.add("role1");
  34. info.setRoles(r);
  35. return info;
  36. }
  37. /** * 在这个方法中,进行身份验证 */
  38. @Override
  39. protected AuthenticationInfo doGetAuthenticationInfo(
  40. AuthenticationToken token) throws AuthenticationException {
  41. //用户名
  42. String username = (String) token.getPrincipal();
  43. log.info("username:"+username);
  44. //密码
  45. String password = new String((char[])token.getCredentials());
  46. log.info("password:"+password);
  47. //从数据库获取用户名密码进行匹配,这里为了方面,省略数据库操作
  48. if(!"admin".equals(username)){
  49. throw new UnknownAccountException();
  50. }
  51. if(!"123".equals(password)){
  52. throw new IncorrectCredentialsException();
  53. }
  54. //身份验证通过,返回一个身份信息
  55. AuthenticationInfo aInfo = new SimpleAuthenticationInfo(username,password,getName());
  56. return aInfo;
  57. }
  58. }
  • 让我们定义的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 {

    1. private static final transient Logger log = LoggerFactory.getLogger(Main.class);
    2. public static void main(String[] args) {
    3. //获取SecurityManager的实例
    4. Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-realm.ini");
    5. SecurityManager securityManager = factory.getInstance();
    6. SecurityUtils.setSecurityManager(securityManager);
    7. Subject currenUser = SecurityUtils.getSubject();
    8. //如果还未认证
    9. if(!currenUser.isAuthenticated()){
    10. UsernamePasswordToken token = new UsernamePasswordToken("admin","123");
    11. token.setRememberMe(true);
    12. try {
    13. currenUser.login(token);
    14. } catch (UnknownAccountException uae) {
    15. log.info("没有该用户: " + token.getPrincipal());
    16. } catch (IncorrectCredentialsException ice) {
    17. log.info( token.getPrincipal() + " 的密码不正确!");
    18. } catch (LockedAccountException lae) {
    19. log.info( token.getPrincipal() + " 被锁定 ,请联系管理员");
    20. }catch (AuthenticationException ae) {
    21. //其他未知的异常
    22. }
    23. }
    24. if(currenUser.isAuthenticated())
    25. log.info("用户 "+currenUser.getPrincipal() +" 登录成功");
    26. //是否有role1这个角色
    27. if(currenUser.hasRole("role1")){
    28. log.info("有角色role1");
    29. }else{
    30. log.info("没有角色role1");
    31. }
    32. //是否有对打印机进行打印操作的权限
    33. if(currenUser.isPermitted("printer:print")){
    34. log.info("可以对打印机进行打印操作");
    35. }else {
    36. log.info("不可以对打印机进行打印操作");
    37. }
    38. }

    }

  • 测试结果
    这里写图片描述

从结果截图中,我们可以看到,自定义的Realm中的doGetAuthorizationInfo 方法被调用了两次,并且分别在currenUser.hasRole()currenUser.isPermitted 方法调用时调用

散列算法支持

一般我们存入数据库的密码都是通过加密的,比如将“原密码+盐”进行一次或多次MD5计算,shiro提供了对散列算法的支持

  1. package com.shiro.realm;
  2. import org.apache.shiro.authc.AuthenticationException;
  3. import org.apache.shiro.authc.AuthenticationInfo;
  4. import org.apache.shiro.authc.AuthenticationToken;
  5. import org.apache.shiro.authc.SimpleAuthenticationInfo;
  6. import org.apache.shiro.authz.AuthorizationInfo;
  7. import org.apache.shiro.realm.AuthorizingRealm;
  8. import org.apache.shiro.subject.PrincipalCollection;
  9. import org.apache.shiro.util.ByteSource;
  10. public class UserRealm extends AuthorizingRealm {
  11. private String salt = "hehe";//盐
  12. @Override
  13. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
  14. // TODO Auto-generated method stub
  15. return null;
  16. }
  17. @Override
  18. protected AuthenticationInfo doGetAuthenticationInfo(
  19. AuthenticationToken token) throws AuthenticationException {
  20. //用户输入的用户名
  21. String username = (String) token.getPrincipal();
  22. //如果数据库中没有这个用户,则返回null,登录失败
  23. if(!username.equals("xiaozhou"))
  24. return null;
  25. //从数据库中查询密码
  26. String password = "42029a889cc26562c986346114c02367";
  27. SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username,
  28. password, ByteSource.Util.bytes(salt), getName());
  29. return info;
  30. }
  31. }

使用MD5的realm和一般的realm没有太多区别,唯一的区别在于:不使用散列算法(即对密码加密)的话,从数据库查询出来的密码是明文,否则查询出来的是密文,我们没法使用密文来直接比对判断密码是否正确,为了让shiro自动帮我们先加密再比对,我们要在配置文件ini中告诉shiro使用什么算法

  1. [main]
  2. #密码匹配器
  3. credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
  4. #匹配器使用md5
  5. credentialsMatcher.hashAlgorithmName=md5
  6. #进行几次散列(用md5算法做几次运算)
  7. credentialsMatcher.hashIterations=1
  8. #realm
  9. userRealm=com.shiro.realm.UserRealm
  10. #该realm使用的匹配器是哪个
  11. userRealm.credentialsMatcher=$credentialsMatcher
  12. #使用哪个realm
  13. securityManager.realms=$userRealm

多个Realm

  • 有时候,我们需要进行多次身份验证,我们可以定义多个Realm,如同流水线一样,shiro会依次调用Realm
  • MyRealm1

    package com.shiro.mutilrealm;

  1. import java.util.HashSet;
  2. import java.util.Set;
  3. import org.apache.shiro.authc.AuthenticationException;
  4. import org.apache.shiro.authc.AuthenticationInfo;
  5. import org.apache.shiro.authc.AuthenticationToken;
  6. import org.apache.shiro.authc.IncorrectCredentialsException;
  7. import org.apache.shiro.authc.SimpleAuthenticationInfo;
  8. import org.apache.shiro.authc.UnknownAccountException;
  9. import org.apache.shiro.authc.UsernamePasswordToken;
  10. import org.apache.shiro.authz.AuthorizationInfo;
  11. import org.apache.shiro.authz.SimpleAuthorizationInfo;
  12. import org.apache.shiro.realm.AuthorizingRealm;
  13. import org.apache.shiro.realm.Realm;
  14. import org.apache.shiro.subject.PrincipalCollection;
  15. import org.slf4j.Logger;
  16. import org.slf4j.LoggerFactory;
  17. import com.shiro.realm.Main;
  18. public class MyRealm1 extends AuthorizingRealm{
  19. private static final transient Logger log = LoggerFactory.getLogger(Main.class);
  20. @Override
  21. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
  22. String username = (String) getAvailablePrincipal(principals);
  23. //通过用户名从数据库获取权限字符串
  24. SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
  25. //权限
  26. Set<String> s = new HashSet<String>();
  27. s.add("printer:print");
  28. s.add("printer:query");
  29. info.setStringPermissions(s);
  30. //角色
  31. Set<String> r = new HashSet<String>();
  32. r.add("role1");
  33. info.setRoles(r);
  34. return info;
  35. }
  36. @Override
  37. protected AuthenticationInfo doGetAuthenticationInfo(
  38. AuthenticationToken token) throws AuthenticationException {
  39. log.info("MyRealm1开始认证。。。。。。");
  40. //用户名
  41. String username = (String) token.getPrincipal();
  42. log.info("username:"+username);
  43. //密码
  44. String password = new String((char[])token.getCredentials());
  45. log.info("password:"+password);
  46. //从数据库获取用户名密码进行匹配,这里为了方面,省略数据库操作
  47. if(!"admin".equals(username)){
  48. throw new UnknownAccountException();
  49. }
  50. if(!"123".equals(password)){
  51. throw new IncorrectCredentialsException();
  52. }
  53. //身份验证通过
  54. AuthenticationInfo aInfo = new SimpleAuthenticationInfo(username,password,getName());
  55. return aInfo;
  56. }
  57. }
  • 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 {

    1. private static final transient Logger log = LoggerFactory.getLogger(Main.class);
    2. public static void main(String[] args) {
    3. //获取SecurityManager的实例
    4. Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-mutil-realm.ini");
    5. SecurityManager securityManager = factory.getInstance();
    6. SecurityUtils.setSecurityManager(securityManager);
    7. Subject currenUser = SecurityUtils.getSubject();
    8. //如果还未认证
    9. if(!currenUser.isAuthenticated()){
    10. UsernamePasswordToken token = new UsernamePasswordToken("admin","123");
    11. token.setRememberMe(true);
    12. try {
    13. currenUser.login(token);
    14. } catch (UnknownAccountException uae) {
    15. log.info("没有该用户: " + token.getPrincipal());
    16. } catch (IncorrectCredentialsException ice) {
    17. log.info( token.getPrincipal() + " 的密码不正确!");
    18. } catch (LockedAccountException lae) {
    19. log.info( token.getPrincipal() + " 被锁定 ,请联系管理员");
    20. }catch (AuthenticationException ae) {
    21. //其他未知的异常
    22. }
    23. }
    24. if(currenUser.isAuthenticated())
    25. log.info("用户 "+currenUser.getPrincipal() +" 登录成功");
    26. //得到一个身份集合
    27. PrincipalCollection principalCollection = currenUser.getPrincipals();
    28. }

    }

  • 运行结果
    这里写图片描述

结果很明显,MyRealm1和MyRealm2依次执行

自定义AuthenticationStrategy(验证策略)

  • 上面我们使用了shiro自带的AuthenticationStrategy,其实我们也可以自己定义。

    package com.shiro.authenticationstrategy;

    import java.util.Collection;

    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authc.pam.AbstractAuthenticationStrategy;
    import org.apache.shiro.realm.Realm;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    import com.shiro.realm.Main;

    public class MyAuthenticationStrategy extends AbstractAuthenticationStrategy{

    1. private static final transient Logger log = LoggerFactory.getLogger(MyAuthenticationStrategy.class);
    2. /** * 所有Realm验证之前调用 */
    3. @Override
    4. public AuthenticationInfo beforeAllAttempts(
    5. Collection<? extends Realm> realms, AuthenticationToken token)
    6. throws AuthenticationException {
    7. log.info("===============beforeAllAttempts方法被调用==================");
    8. return super.beforeAllAttempts(realms, token);
    9. }
    10. /** * 每一个Realm验证之前调用 */
    11. @Override
    12. public AuthenticationInfo beforeAttempt(Realm realm,
    13. AuthenticationToken token, AuthenticationInfo aggregate)
    14. throws AuthenticationException {
    15. log.info("===============beforeAttempt方法被调用==================");
    16. return super.beforeAttempt(realm, token, aggregate);
    17. }
    18. /** * 每一个Realm验证之后调用 */
    19. @Override
    20. public AuthenticationInfo afterAttempt(Realm realm,
    21. AuthenticationToken token, AuthenticationInfo singleRealmInfo,
    22. AuthenticationInfo aggregateInfo, Throwable t)
    23. throws AuthenticationException {
    24. log.info("===============afterAttempt方法被调用==================");
    25. return super.afterAttempt(realm, token, singleRealmInfo, aggregateInfo, t);
    26. }
    27. /** * 所有Realm验证之后调用 */
    28. @Override
    29. public AuthenticationInfo afterAllAttempts(AuthenticationToken token,
    30. AuthenticationInfo aggregate) throws AuthenticationException {
    31. log.info("===============afterAllAttempts方法被调用==================");
    32. return super.afterAllAttempts(token, aggregate);
    33. }
  1. }

我们所继承的 AbstractAuthenticationStrategy 中,各个方法并不是抽象的,也就是说并一定要重写,我们可以根据需求重写需要的方法即可

  • 配置文件

    要让我们自定义的AuthenticationStrategy起作用,只要将上面配置文件(shiro-mutil-realm.ini)中
    authcStrategy = org.apache.shiro.authc.pam.AllSuccessfulStrategy
    改为authcStrategy = com.shiro.authenticationstrategy.MyAuthenticationStrategy 即可

  • 测试代码不变
  • 结果
    这里写图片描述

从截图中也可以清除的看到自定义的策略中,各个方法被调用的顺序。有了这些,我们就可以随心所欲的根据需求进行操作了

多个Realm验证顺序

  • 隐式排列

    • 当你配置多个realm的时候,处理的顺序默认就是你配置的顺序。
    • 这种情况通常就是只定义了realm,而没有配置securityManager的realms
  • 显式排列

    • 也就是显示的配置securityManager.realms,那么执行的顺序就是你配置该值的realm的顺序。
    • 通常更推荐显示排列。

我们可以简单的理解为,多个Realm验证的顺序,就是我们配置的顺序

发表评论

表情:
评论列表 (有 0 条评论,491人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Shiro学习2身份验证

    身份验证:即在应用中谁能证明他就是他本人。一般提供如他们的身份ID一些标识信息来表明他就是他本人,如提供身份证,用户名/密码来证明。 在shiro中,用户需要提供princ

    相关 shiro身份验证

    1.场景还原     Shiro 是 Java 的一个安全框架。目前,使用 Apache Shiro 的人越来越多,因为它非常实用又简单易懂,今天笔者就shiro的身份验

    相关 Shiro(二)身份验证

    一、身份验证 即在应用中谁能证明他就是他本人。一般提供如他们的身份 ID 一些标识信息来表明他就是他本人,如提供身份证,用户名/密码来证明。 在 shiro 中,用户需

    相关 Shiro--身份验证

    身份验证 身份验证: 即在应用中谁能证明他就是他本人。一般提供如他们的身份ID一些标识信息来表明他就是他本人,如提供身份证,用户名/密码来证明。