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); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

  這段代碼的意思是:當只有一個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(); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

  第二步:新建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; } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

  第三步:新建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); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

  第四步:創建分別處理普通用戶登錄和管理員登錄的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; } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

  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; } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

  第五步:在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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

  第六步:配置控制器: 
   
  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"; } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

  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"; } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

測試頁面: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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

  這就實現了UserRealm用以處理普通用戶的登錄驗證,AdminRealm用以處理管理員的登錄驗證。 
  如果還需要添加其他類型,例如,需要添加一個教師登錄模塊,只需要再新建一個TeacherRealm,並且在枚舉類loginType中添加教師的信息,再完成其他類似的配置即可。


免責聲明!

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



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