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

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/11495.html

(0)
上一篇 2021年7月19日 11:17
下一篇 2021年7月19日 11:17

相关推荐

发表回复

登录后才能评论