假設現在有這樣一種需求:存在兩張表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