假设现在有这样一种需求:存在两张表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; }}
- 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 { assertRealmsConfigured(); CustomizedToken customizedToken = (CustomizedToken) authenticationToken; String loginType = customizedToken.getLoginType(); Collection<Realm> realms = getRealms(); Collection<Realm> typeRealms = new ArrayList<>(); for (Realm realm : realms) { if (realm.getName().contains(loginType)) typeRealms.add(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
第四步:创建分别处理普通用户登录和管理员登录的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; CustomizedToken customizedToken = (CustomizedToken) token; String email = customizedToken.getUsername(); user = userService.getUserByEmail(email); if (user == null) throw new UnknownAccountException("用户不存在!"); Object principal = email; Object credentials = user.getPassword(); String realmName = getName(); 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
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) { return null; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { Admin admin = null; CustomizedToken customizedToken = (CustomizedToken) token; String username = customizedToken.getUsername(); admin = adminService.getAdminByUsername(username); if (admin == null) throw new UnknownAccountException("用户不存在!"); Object principal = username; Object credentials = admin.getPassword(); String realmName = getName(); 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
第五步:在spring配置文件中指定使用自定义的认证器:(其他配置略)
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="cacheManager" ref="cacheManager" /> <property name="authenticator" ref="authenticator"></property> <property name="realms"> <list> <ref bean="userRealm" /> <ref bean="adminRealm"/> </list> </property> </bean> <bean id="authenticator" class="com.ang.elearning.shiro.CustomizedModularRealmAuthenticator"> <property name="authenticationStrategy"> <bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean> </property> </bean> <bean id="userRealm" class="com.ang.elearning.shiro.UserRealm"> <property name="credentialsMatcher"> <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <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"> <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
第六步:配置控制器:
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
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
测试页面: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
这就实现了UserRealm用以处理普通用户的登录验证,AdminRealm用以处理管理员的登录验证。
如果还需要添加其他类型,例如,需要添加一个教师登录模块,只需要再新建一个TeacherRealm,并且在枚举类loginType中添加教师的信息,再完成其他类似的配置即可。