shiro实现不同身份使用不同Realm进行验证

来源:互联网 发布:云豹直播app源码 编辑:程序博客网 时间:2024/06/06 11:41

假设现在有这样一种需求:存在两张表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

  这段代码的意思是:当只有一个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

  第二步:新建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 {        // 判断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

  第四步:创建分别处理普通用户登录和管理员登录的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

  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

  第五步:在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

  第六步:配置控制器: 
   
  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中添加教师的信息,再完成其他类似的配置即可。

版权声明:本文为博主原创文章,转载请声明原文出处:http://blog.csdn.net/xiangwanpeng
阅读全文
0 0