zoukankan      html  css  js  c++  java
  • shiro实现不同身份使用不同Realm进行验证

    转载:https://blog.csdn.net/xiangwanpeng/article/details/54802509

    假设现在有这样一种需求:存在两张表user和admin,分别记录普通用户和管理员的信息。并且现在要实现普通用户和管理员的分开登录,即需要两个Realm——UserRealm和AdminRealm,分别处理普通用户和管理员的验证功能。 
      但是正常情况下,当定义了两个Realm,无论是普通用户登录,还是管理员登录,都会由这两个Realm共同处理。这是因为,当配置了多个Realm时,我们通常使用的认证器是shiro自带的org.apache.shiro.authc.pam.ModularRealmAuthenticator,其中决定使用的Realm的是doAuthenticate()方法,源代码如下:

     protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
             assertRealmsConfigured();
             Collection<Realm> realms = getRealms();
             if (realms.size() == 1) {
                 return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
             } else {
                 return doMultiRealmAuthentication(realms, authenticationToken);
             }
         }

      这段代码的意思是:当只有一个Realm时,就使用这个Realm,当配置了多个Realm时,会使用所有配置的Realm。 
      现在,为了实现需求,我会创建一个org.apache.shiro.authc.pam.ModularRealmAuthenticator的子类,并重写doAuthenticate()方法,让特定的Realm完成特定的功能。如何区分呢?我会同时创建一个org.apache.shiro.authc.UsernamePasswordToken的子类,在其中添加一个字段loginType,用来标识登录的类型,即是普通用户登录,还是管理员登录。具体步骤如下: 
       
      第一步:创建枚举类LoginType用以记录登录的类型:

    //登录类型
    //普通用户登录,管理员登录
    public enum LoginType {
        USER("User"),  ADMIN("Admin");
    
        private String type;
    
        private LoginType(String type) {
            this.type = type;
        }
    
        @Override
        public String toString() {
            return this.type.toString();
        }
    }

      第二步:新建org.apache.shiro.authc.UsernamePasswordToken的子类CustomizedToken:

    import org.apache.shiro.authc.UsernamePasswordToken;
    
    public class CustomizedToken extends UsernamePasswordToken {
    
        //登录类型,判断是普通用户登录,教师登录还是管理员登录
        private String loginType;
    
        public CustomizedToken(final String username, final String password,String loginType) {
            super(username,password);
            this.loginType = loginType;
        }
    
        public String getLoginType() {
            return loginType;
        }
    
        public void setLoginType(String loginType) {
            this.loginType = loginType;
        }
    }

      第三步:新建org.apache.shiro.authc.pam.ModularRealmAuthenticator的子类CustomizedModularRealmAuthenticator:

    import java.util.ArrayList;
    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.ModularRealmAuthenticator;
    import org.apache.shiro.realm.Realm;
    
    /**
     * @author Alan_Xiang 
     * 自定义Authenticator
     * 注意,当需要分别定义处理普通用户和管理员验证的Realm时,对应Realm的全类名应该包含字符串“User”,或者“Admin”。
     * 并且,他们不能相互包含,例如,处理普通用户验证的Realm的全类名中不应该包含字符串"Admin"。
     */
    public class CustomizedModularRealmAuthenticator extends ModularRealmAuthenticator {
    
        @Override
        protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken)
                throws AuthenticationException {
            // 判断getRealms()是否返回为空
            assertRealmsConfigured();
            // 强制转换回自定义的CustomizedToken
            CustomizedToken customizedToken = (CustomizedToken) authenticationToken;
            // 登录类型
            String loginType = customizedToken.getLoginType();
            // 所有Realm
            Collection<Realm> realms = getRealms();
            // 登录类型对应的所有Realm
            Collection<Realm> typeRealms = new ArrayList<>();
            for (Realm realm : realms) {
                if (realm.getName().contains(loginType))
                    typeRealms.add(realm);
            }
    
            // 判断是单Realm还是多Realm
            if (typeRealms.size() == 1)
                return doSingleRealmAuthentication(typeRealms.iterator().next(), customizedToken);
            else
                return doMultiRealmAuthentication(typeRealms, customizedToken);
        }
    
    }

      第四步:创建分别处理普通用户登录和管理员登录的Realm: 
       
      UserRealm:

    import javax.annotation.Resource;
    
    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.UnknownAccountException;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    import org.apache.shiro.util.ByteSource;
    
    import com.ang.elearning.po.User;
    import com.ang.elearning.service.IUserService;
    
    public class UserRealm extends AuthorizingRealm {
    
        @Resource
        IUserService userService;
    
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            return null;
        }
    
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            User user = null;
            // 1. 把AuthenticationToken转换为CustomizedToken
            CustomizedToken customizedToken = (CustomizedToken) token;
            // 2. 从CustomizedToken中获取email
            String email = customizedToken.getUsername();
            // 3. 若用户不存在,抛出UnknownAccountException异常
            user = userService.getUserByEmail(email);
            if (user == null)
                throw new UnknownAccountException("用户不存在!");
            // 4.
            // 根据用户的情况,来构建AuthenticationInfo对象并返回,通常使用的实现类为SimpleAuthenticationInfo
            // 以下信息从数据库中获取
            // (1)principal:认证的实体信息,可以是email,也可以是数据表对应的用户的实体类对象
            Object principal = email;
            // (2)credentials:密码
            Object credentials = user.getPassword();
            // (3)realmName:当前realm对象的name,调用父类的getName()方法即可
            String realmName = getName();
            // (4)盐值:取用户信息中唯一的字段来生成盐值,避免由于两个用户原始密码相同,加密后的密码也相同
            ByteSource credentialsSalt = ByteSource.Util.bytes(email);
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt,
                    realmName);
            return info;
        }
    
    }

      AdminRealm:

    import javax.annotation.Resource;
    
    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.UnknownAccountException;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    import org.apache.shiro.util.ByteSource;
    
    import com.ang.elearning.po.Admin;
    import com.ang.elearning.service.IAdminService;
    
    
    public class AdminRealm extends AuthorizingRealm {
    
        @Resource
        private IAdminService adminService;
    
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            // TODO Auto-generated method stub
            return null;
        }
    
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            Admin admin = null;
            // 1. 把AuthenticationToken转换为CustomizedToken
            CustomizedToken customizedToken = (CustomizedToken) token;
            // 2. 从CustomizedToken中获取username
            String username = customizedToken.getUsername();
            // 3. 若用户不存在,抛出UnknownAccountException异常
            admin = adminService.getAdminByUsername(username);
            if (admin == null)
                throw new UnknownAccountException("用户不存在!");
            // 4.
            // 根据用户的情况,来构建AuthenticationInfo对象并返回,通常使用的实现类为SimpleAuthenticationInfo
            // 以下信息从数据库中获取
            // (1)principal:认证的实体信息,可以是username,也可以是数据表对应的用户的实体类对象
            Object principal = username;
            // (2)credentials:密码
            Object credentials = admin.getPassword();
            // (3)realmName:当前realm对象的name,调用父类的getName()方法即可
            String realmName = getName();
            // (4)盐值:取用户信息中唯一的字段来生成盐值,避免由于两个用户原始密码相同,加密后的密码也相同
            ByteSource credentialsSalt = ByteSource.Util.bytes(username);
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt,
                    realmName);
            return info;
        }
    
    }

      第五步:在spring配置文件中指定使用自定义的认证器:(其他配置略)

      <!-- 配置SecurityManager -->
        <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
            <property name="cacheManager" ref="cacheManager" />
            <property name="authenticator" ref="authenticator"></property>
            <!-- 可以配置多个Realm,其实会把realms属性赋值给ModularRealmAuthenticator的realms属性 -->
            <property name="realms">
                <list>
                    <ref bean="userRealm" />
                    <ref bean="adminRealm"/>
                </list>
            </property>
        </bean>
    
      <!-- 配置使用自定义认证器,可以实现多Realm认证,并且可以指定特定Realm处理特定类型的验证 -->
        <bean id="authenticator" class="com.ang.elearning.shiro.CustomizedModularRealmAuthenticator">
            <!-- 配置认证策略,只要有一个Realm认证成功即可,并且返回所有认证成功信息 -->
            <property name="authenticationStrategy">
                <bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean>
            </property>
        </bean>
    
        <!-- 配置Realm -->
        <bean id="userRealm" class="com.ang.elearning.shiro.UserRealm">
            <!-- 配置密码匹配器 -->
            <property name="credentialsMatcher">
                <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                    <!-- 加密算法为MD5 -->
                    <property name="hashAlgorithmName" value="MD5"></property>
                    <!-- 加密次数 -->
                    <property name="hashIterations" value="1024"></property>
                </bean>
            </property>
        </bean>
    
        <bean id="adminRealm" class="com.ang.elearning.shiro.AdminRealm">
            <!-- 配置密码匹配器 -->
            <property name="credentialsMatcher">
                <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                    <!-- 加密算法为MD5 -->
                    <property name="hashAlgorithmName" value="MD5"></property>
                    <!-- 加密次数 -->
                    <property name="hashIterations" value="1024"></property>
                </bean>
            </property>
        </bean>

      第六步:配置控制器: 
       
      UserController:

    @Controller
    @RequestMapping("/user")
    public class UserController {
    
        private static final String USER_LOGIN_TYPE = LoginType.USER.toString();
    
        @Resource
        private IUserService userService;
    
        @RequestMapping(value = "login", method = RequestMethod.POST)
        public String login(@RequestParam("email") String email, @RequestParam("password") String password) {
            Subject currentUser = SecurityUtils.getSubject();
            if (!currentUser.isAuthenticated()) {
                CustomizedToken customizedToken = new CustomizedToken(email, password, USER_LOGIN_TYPE);
                customizedToken.setRememberMe(false);
                try {
                    currentUser.login(customizedToken);
                    return "user/index";
                } catch (IncorrectCredentialsException ice) {
                    System.out.println("邮箱/密码不匹配!");
                } catch (LockedAccountException lae) {
                    System.out.println("账户已被冻结!");
                } catch (AuthenticationException ae) {
                    System.out.println(ae.getMessage());
                }
            }
            return "redirect:/login.jsp";
        }
    }

      AdminController:

    @Controller
    @RequestMapping("/admin")
    public class AdminController {
    
        private static final String ADMIN_LOGIN_TYPE = LoginType.ADMIN.toString();
    
        @RequestMapping(value="/login",method=RequestMethod.POST)
        public String login(@RequestParam("username") String username,@RequestParam("password") String password){
            Subject currentUser = SecurityUtils.getSubject();
            if(!currentUser.isAuthenticated()){
                CustomizedToken customizedToken = new CustomizedToken(username, password, ADMIN_LOGIN_TYPE);
                customizedToken.setRememberMe(false);
                try {
                    currentUser.login(customizedToken);
                    return "admin/index";
                } catch (IncorrectCredentialsException ice) {
                    System.out.println("用户名/密码不匹配!");
                } catch (LockedAccountException lae) {
                    System.out.println("账户已被冻结!");
                } catch (AuthenticationException ae) {
                    System.out.println(ae.getMessage());
                }
            }
            return "redirect:/login.jsp";
        }
    
    }

    测试页面:login.jsp

    <body>
        <form action="${pageContext.request.contextPath }/user/login"
            method="POST">
            邮箱:<input type="text" name="email"> 
            <br><br> 
            密码:<input type="password" name="password"> 
            <br><br> 
            <input type="submit" value="用户登录">
        </form>
        <br>
        <br>
        <form action="${pageContext.request.contextPath }/admin/login"
            method="POST">
            用户名:<input type="text" name="username"> 
            <br><br> 
            密 码:<input type="password" name="password"> 
            <br><br> 
            <input type="submit" value="管理员登录">
        </form>
    </body>

      这就实现了UserRealm用以处理普通用户的登录验证,AdminRealm用以处理管理员的登录验证。 
      如果还需要添加其他类型,例如,需要添加一个教师登录模块,只需要再新建一个TeacherRealm,并且在枚举类loginType中添加教师的信息,再完成其他类似的配置即可。

  • 相关阅读:
    SpringBoot+ElementUI实现通用文件下载请求(全流程图文详细教程)
    Java中将String格式的标准时间字符串转换为Date格式的方法
    ElementUI中el-upload传递额外参数为date类型时后台SpringBoot接收不到
    模式、框架、架构和平台的区别
    架构、框架、模式和平台
    “模式”与“方式”的区别
    JAVA发送HTTP请求方式
    http status状态码,readyState状态码
    区分网络请求时http和ajax请求
    HTTP请求方式中8种请求方法
  • 原文地址:https://www.cnblogs.com/tuanz/p/9228267.html
Copyright © 2011-2022 走看看