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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM