zoukankan      html  css  js  c++  java
  • shiro双realm验证

    假设现在有这样一种需求:存在两张表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中添加教师的信息,再完成其他类似的配置即可。

    本文内容转自:http://blog.csdn.net/xiangwanpeng/article/details/54802509

    http://blog.csdn.net/liiuijkiuu/article/details/53945062

  • 相关阅读:
    Luogu 1080 【NOIP2012】国王游戏 (贪心,高精度)
    Luogu 1314 【NOIP2011】聪明的质检员 (二分)
    Luogu 1315 【NOIP2011】观光公交 (贪心)
    Luogu 1312 【NOIP2011】玛雅游戏 (搜索)
    Luogu 1525 【NOIP2010】关押罪犯 (贪心,并查集)
    Luogu 1514 引水入城 (搜索,动态规划)
    UVA 1394 And Then There Was One / Gym 101415A And Then There Was One / UVAlive 3882 And Then There Was One / POJ 3517 And Then There Was One / Aizu 1275 And Then There Was One (动态规划,思维题)
    Luogu 1437 [HNOI2004]敲砖块 (动态规划)
    Luogu 1941 【NOIP2014】飞扬的小鸟 (动态规划)
    HDU 1176 免费馅饼 (动态规划)
  • 原文地址:https://www.cnblogs.com/fengwenzhee/p/7121059.html
Copyright © 2011-2022 走看看