Spring Boot 集成Shiro的多realm配置

我在做毕设的时候采用shiro进行登录认证和权限管理的实现。其中需求涉及使用三个角色分别是:学生、教师、管理员。现在要三者实现分开登录。即需要三个Realm——StudentRealm和TeacherRealm、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。

为了实现需求,我会创建一个org.apache.shiro.authc.pam.ModularRealmAuthenticator的子类,并重写doAuthenticate()方法,让特定的Realm完成特定的功能。如何区分呢?我会同时创建一个org.apache.shiro.authc.UsernamePasswordToken的子类,在其中添加一个字段loginType,用来标识登录的类型,即是学生登录、教师登录,还是管理员登录。具体步骤如下(我的代码使用的是Groovy):

enum  LoginType {
    STUDENT("Student"),  ADMIN("Admin"), TEACHER("Teacher")

    private String type

    private LoginType(String type) {
        this.type = type
    }

    @Override
    public String toString() {
        return this.type.toString()
    }
}

接下来新建org.apache.shiro.authc.UsernamePasswordToken的子类UserToken


import org.apache.shiro.authc.UsernamePasswordToken

class UserToken extends UsernamePasswordToken {

    //登录类型,判断是学生登录,教师登录还是管理员登录
    private String loginType

    public UserToken(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的子类UserModularRealmAuthenticator:

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
import org.slf4j.Logger
import org.slf4j.LoggerFactory

/**
 * 当配置了多个Realm时,我们通常使用的认证器是shiro自带的org.apache.shiro.authc.pam.ModularRealmAuthenticator,其中决定使用的Realm的是doAuthenticate()方法
 *
 * 自定义Authenticator
 * 注意,当需要分别定义处理学生和教师和管理员验证的Realm时,对应Realm的全类名应该包含字符串“Student”“Teacher”,或者“Admin”。
 * 并且,他们不能相互包含,例如,处理学生验证的Realm的全类名中不应该包含字符串"Admin"。
 */
class UserModularRealmAuthenticator extends ModularRealmAuthenticator {

    private static final Logger logger = LoggerFactory.getLogger(UserModularRealmAuthenticator.class)

    @Override
    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken)
            throws AuthenticationException {
        logger.info("UserModularRealmAuthenticator:method doAuthenticate() execute ")
        // 判断getRealms()是否返回为空
        assertRealmsConfigured()
        // 强制转换回自定义的CustomizedToken
        UserToken userToken = (UserToken) authenticationToken
        // 登录类型
        String loginType = userToken?.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){
            logger.info("doSingleRealmAuthentication() execute ")
            return doSingleRealmAuthentication(typeRealms?.get(0), userToken)
        }
        else{
            logger.info("doMultiRealmAuthentication() execute ")
            return doMultiRealmAuthentication(typeRealms, userToken)
        }
    }
}

第四步:创建分别处理学生登录和教师登录、管理员登录的Realm:
我这里直接贴出了我项目中的代码,你们可以根据具体的需求进行操作。
AdminShiroRealm :

package com.ciyou.edu.config.shiro.admin
import com.ciyou.edu.config.shiro.common.UserToken
import com.ciyou.edu.entity.Admin
import com.ciyou.edu.service.AdminService
import com.ciyou.edu.service.PermissionService
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.AuthorizationException
import org.apache.shiro.authz.AuthorizationInfo
import org.apache.shiro.authz.SimpleAuthorizationInfo
import org.apache.shiro.realm.AuthorizingRealm
import org.apache.shiro.subject.PrincipalCollection
import org.apache.shiro.util.ByteSource
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Lazy


class AdminShiroRealm extends AuthorizingRealm {

     private static final Logger logger = LoggerFactory.getLogger(AdminShiroRealm.class)
     @Autowired
     @Lazy
     private AdminService adminService


    @Autowired
    @Lazy
    private PermissionService permissionService



    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        logger.info("开始Admin身份认证...")
        UserToken userToken = (UserToken)token
        String adminName =  userToken?.getUsername() //获取用户名,默认和login.html中的adminName对应。
        Admin admin = adminService?.findByAdminName(adminName)

        if (admin == null) {
            //没有返回登录用户名对应的SimpleAuthenticationInfo对象时,就会在LoginController中抛出UnknownAccountException异常
            throw new UnknownAccountException("用户不存在!")
        }

        //验证通过返回一个封装了用户信息的AuthenticationInfo实例即可。
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                admin, //用户信息
                admin?.getPassword(), //密码
                getName() //realm name
        )
        authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(admin?.getAdminName())) //设置盐
        logger.info("返回Admin认证信息:" + authenticationInfo)
        return authenticationInfo
    }

//当访问到页面的时候,链接配置了相应的权限或者shiro标签才会执行此方法否则不会执行
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        logger.info("开始Admin权限授权(进行权限验证!!)")
        if (principals == null) {
            throw new AuthorizationException("PrincipalCollection method argument cannot be null.")
        }
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo()
        if(principals?.getPrimaryPrincipal() instanceof Admin){
            Admin admin = (Admin) principals?.getPrimaryPrincipal()
            logger.info("当前Admin :" + admin )
            authorizationInfo?.addRole("Admin")
            //每次都从数据库重新查找,确保能及时更新权限
            admin?.setPermissionList(permissionService?.findPermissionByAdmin(admin?.getAdminId()))
            admin?.getPermissionList()?.each {current_Permission ->
                authorizationInfo?.addStringPermission(current_Permission?.getPermission())
            }
            logger.info("当前Admin授权角色:" +authorizationInfo?.getRoles() + ",权限:" + authorizationInfo?.getStringPermissions())
            return authorizationInfo
        }
    }
}

TeacherShiroRealm :

package com.ciyou.edu.config.shiro.teacher

import com.ciyou.edu.config.shiro.common.UserToken
import com.ciyou.edu.entity.Teacher
import com.ciyou.edu.service.TeacherService
import org.apache.shiro.authc.*
import org.apache.shiro.authz.AuthorizationException
import org.apache.shiro.authz.AuthorizationInfo
import org.apache.shiro.authz.SimpleAuthorizationInfo
import org.apache.shiro.realm.AuthorizingRealm
import org.apache.shiro.subject.PrincipalCollection
import org.apache.shiro.util.ByteSource
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Lazy


class TeacherShiroRealm extends AuthorizingRealm {
    private static final Logger logger = LoggerFactory.getLogger(TeacherShiroRealm.class)

    //在自定义Realm中注入的Service声明中加入@Lazy注解即可解决@cacheble注解无效问题
    //解决同时使用Redis缓存数据和缓存shiro时,@cacheble无效的问题
     @Autowired
     @Lazy
     private TeacherService teacherService



    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        logger.info("开始Teacher身份认证..")
        UserToken userToken = (UserToken)token
        String teacherId =  userToken?.getUsername()
        Teacher teacher = teacherService?.findByTeacherId(teacherId)

        if (teacher == null) {
            //没有返回登录用户名对应的SimpleAuthenticationInfo对象时,就会在LoginController中抛出UnknownAccountException异常
            throw new UnknownAccountException("用户不存在!")
        }

        //验证通过返回一个封装了用户信息的AuthenticationInfo实例即可。
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                teacher, //用户信息
                teacher?.getPassword(), //密码
                getName() //realm name
        )
        authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(teacher?.getTeacherId())) //设置盐

        return authenticationInfo
    }

//当访问到页面的时候,链接配置了相应的权限或者shiro标签才会执行此方法否则不会执行
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        logger.info("开始Teacher权限授权")
        if (principals == null) {
            throw new AuthorizationException("PrincipalCollection method argument cannot be null.")
        }
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo()
        if(principals?.getPrimaryPrincipal() instanceof Teacher){
            authorizationInfo?.addRole("Teacher")
            return authorizationInfo
        }
    }
}

StudentShiroRealm :

package com.ciyou.edu.config.shiro.student

import com.ciyou.edu.config.shiro.common.UserToken
import com.ciyou.edu.entity.Student
import com.ciyou.edu.service.StudentService
import org.apache.shiro.authc.*
import org.apache.shiro.authz.AuthorizationException
import org.apache.shiro.authz.AuthorizationInfo
import org.apache.shiro.authz.SimpleAuthorizationInfo
import org.apache.shiro.realm.AuthorizingRealm
import org.apache.shiro.subject.PrincipalCollection
import org.apache.shiro.util.ByteSource
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Lazy


class StudentShiroRealm extends AuthorizingRealm {
    private static final Logger logger = LoggerFactory.getLogger(StudentShiroRealm.class)

    //在自定义Realm中注入的Service声明中加入@Lazy注解即可解决@cacheble注解无效问题
    //解决同时使用Redis缓存数据和缓存shiro时,@cacheble无效的问题
     @Autowired
     @Lazy
     private StudentService studentService



    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        logger.info("开始Student身份认证..")
        UserToken userToken = (UserToken)token
        String studentId =  userToken?.getUsername()
        Student student = studentService?.findByStudentId(studentId)

        if (student == null) {
            //没有返回登录用户名对应的SimpleAuthenticationInfo对象时,就会在LoginController中抛出UnknownAccountException异常
            throw new UnknownAccountException("用户不存在!")
        }

        //验证通过返回一个封装了用户信息的AuthenticationInfo实例即可。
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                student, //用户信息
                student?.getPassword(), //密码
                getName() //realm name
        )
        authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(student?.getStudentId())) //设置盐

        return authenticationInfo
    }

//当访问到页面的时候,链接配置了相应的权限或者shiro标签才会执行此方法否则不会执行
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        logger.info("开始Student权限授权")
        if (principals == null) {
            throw new AuthorizationException("PrincipalCollection method argument cannot be null.")
        }
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo()
        if(principals?.getPrimaryPrincipal() instanceof Student){
            authorizationInfo?.addRole("Student")
            return authorizationInfo
        }
    }
}

接下来是对Shiro进行多realm的注解配置。
这里直接贴出我项目中的代码。
这里写图片描述
上面是我进行shiro进行配置的类,下面是主要的一些代码:


 //SecurityManager 是 Shiro 架构的核心,通过它来链接Realm和用户(文档中称之为Subject.)
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager()
        //设置realm.
        securityManager.setAuthenticator(modularRealmAuthenticator())
        List<Realm> realms = new ArrayList<>()
        //添加多个Realm
        realms.add(adminShiroRealm())
        realms.add(teacherShiroRealm())
        realms.add(studentShiroRealm())
        securityManager.setRealms(realms)
        // 自定义缓存实现 使用redis
        securityManager.setCacheManager(cacheManager())
        // 自定义session管理 使用redis
        securityManager.setSessionManager(sessionManager())
        //注入记住我管理器;
        securityManager.setRememberMeManager(rememberMeManager())
        return securityManager
    }

    /**
     * 系统自带的Realm管理,主要针对多realm
     * */
    @Bean
    public ModularRealmAuthenticator modularRealmAuthenticator(){
        //自己重写的ModularRealmAuthenticator
        UserModularRealmAuthenticator modularRealmAuthenticator = new UserModularRealmAuthenticator()
        modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy())
        return modularRealmAuthenticator
    }

    @Bean
    public AdminShiroRealm adminShiroRealm() {
        AdminShiroRealm adminShiroRealm = new AdminShiroRealm()
        adminShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher())//设置解密规则
        return adminShiroRealm
    }

    @Bean
    public StudentShiroRealm studentShiroRealm() {
        StudentShiroRealm studentShiroRealm = new StudentShiroRealm()
        studentShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher())//设置解密规则
        return studentShiroRealm
    }

    @Bean
    public TeacherShiroRealm teacherShiroRealm() {
        TeacherShiroRealm teacherShiroRealm = new TeacherShiroRealm()
        teacherShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher())//设置解密规则
        return teacherShiroRealm
    }

    //因为我们的密码是加过密的,所以,如果要Shiro验证用户身份的话,需要告诉它我们用的是md5加密的,并且是加密了两次。同时我们在自己的Realm中也通过SimpleAuthenticationInfo返回了加密时使用的盐。这样Shiro就能顺利的解密密码并验证用户名和密码是否正确了。
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher()
        hashedCredentialsMatcher.setHashAlgorithmName("md5")//散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashIterations(2)//散列的次数,比如散列两次,相当于 md5(md5(""));
        return hashedCredentialsMatcher;
    }

接下来就是Controller中实现登录的功能了,这里我只贴出我项目中Admin登录的代码:

package com.ciyou.edu.controller.admin

import com.ciyou.edu.config.shiro.common.LoginType
import com.ciyou.edu.config.shiro.common.UserToken
import com.ciyou.edu.entity.Admin
import com.ciyou.edu.utils.JSONUtil
import org.apache.shiro.SecurityUtils
import org.apache.shiro.authc.AuthenticationException
import org.apache.shiro.subject.Subject
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestMethod
import org.springframework.web.bind.annotation.ResponseBody


/**
 * @Author C.
 * @Date 2018-02-02 20:46
 * admin登录Controller
 */
@Controller
class AdminLoginController {

    private static final Logger logger = LoggerFactory.getLogger(AdminLoginController.class)
    private static final String ADMIN_LOGIN_TYPE = LoginType.ADMIN.toString()

    /**
     * admin登录
     * @param admin
     * @return 登录结果
     */
    @RequestMapping(value="/adminLogin",method=RequestMethod.POST, produces="application/json;charset=UTF-8")
    @ResponseBody
    public String loginAdmin(Admin admin){
        logger.info("登录Admin: " + admin)
        //后台校验提交的用户名和密码
        if(!admin?.getAdminName() || admin?.adminName?.trim() == ""){
            return JSONUtil.returnFailReuslt("账号不能为空")
        }else if(!admin?.getPassword() || admin?.getPassword()?.trim() == ""){
            return JSONUtil.returnFailReuslt("密码不能为空")
        }else if(admin?.getAdminName()?.length() < 3 || admin?.getAdminName()?.length() >15){
            return JSONUtil.returnFailReuslt("账号长度必须在3~15之间")
        }else if(admin?.getPassword()?.length() < 3 || admin?.getPassword()?.length() >15){
            return JSONUtil.returnFailReuslt("密码长度必须在3~15之间")
        }

        //获取Subject实例对象
        //在shiro里面所有的用户的会话信息都会由Shiro来进行控制,那么也就是说只要是与用户有关的一切的处理信息操作都可以通过Shiro取得,
        // 实际上可以取得的信息可以有用户名、主机名称等等,这所有的信息都可以通过Subject接口取得
        Subject subject = SecurityUtils.getSubject()

        //将用户名和密码封装到继承了UsernamePasswordToken的userToken
        UserToken userToken = new UserToken(admin?.getAdminName(), admin?.getPassword(), ADMIN_LOGIN_TYPE)
        userToken.setRememberMe(false)
        try {
            //认证
            // 传到ModularRealmAuthenticator类中,然后根据ADMIN_LOGIN_TYPE传到AdminShiroRealm的方法进行认证
            subject?.login(userToken)
            //Admin存入session
            SecurityUtils.getSubject()?.getSession()?.setAttribute("admin",(Admin)subject?.getPrincipal())
            return JSONUtil.returnSuccessResult("登录成功")
        } catch (AuthenticationException e) {
            //认证失败就会抛出AuthenticationException这个异常,就对异常进行相应的操作,这里的处理是抛出一个自定义异常ResultException
            //到时候我们抛出自定义异常ResultException,用户名或者密码错误
            logger.info("认证错误:" + e.getMessage())
            return JSONUtil.returnFailReuslt("账号或者密码错误")
        }
    }



    @RequestMapping(value="/admin/adminLogout")
    public String logoutAdmin(){
        SecurityUtils.getSubject()?.logout()
        return "redirect:/adminLogin"
    }


}

现在Spring Boot中集成Shiro实现多realm配置就完成了。

感谢以下博文,在我学习的过程中给了我很多帮助,我的博文也有一些内容参考他们的,还不够清楚的读者可以参考:
shiro实现不同身份使用不同Realm进行验证
SpringBoot+Shiro学习之数据库动态权限管理和Redis缓存
Springboot多realm集成,无ini文件,无xml配置

想看项目具体源码,或者对我项目感兴趣的可以查看:CIYOU

  • 12
    点赞
  • 70
    收藏
    觉得还不错? 一键收藏
  • 12
    评论
Shiro是一个开源的权限框架,它提供了认证和授权功能。认证是指验证用户的身份,通常通过用户名和密码进行验证。授权是指确定用户是否有权访问特定资源。在Shiro中,这两个功能都是通过Realm来实现的。 在Shiro中,Realm是其核心组件之一。Realm负责处理认证和授权的逻辑。在自定义的UserRealm类中,有两个关键方法,即doGetAuthenticationInfo和doGetAuthorizationInfo。doGetAuthenticationInfo用于验证用户的身份信息,而doGetAuthorizationInfo用于获取用户的权限信息。 在UserRealm类中,我们可以根据实际需求来编写验证用户身份和获取权限信息的逻辑。这些方法会在用户登录时进行调用,以完成认证和授权的功能。 总结起来,Shiro的认证功能是通过验证用户的身份信息来确定其是否合法用户,而授权功能是根据用户的身份信息确定其是否有权访问特定资源。通过自定义Realm类中的方法可以实现具体的认证和授权逻辑。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [源码分析shiro认证授权流程](https://blog.csdn.net/achuo/article/details/50819658)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [springboot环境下Shiro+Token+Redis安全认证方案](https://blog.csdn.net/qq_38785977/article/details/126014978)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [shrio的用户认证跟权限访问小测试程序代码](https://blog.csdn.net/weixin_52514567/article/details/115566403)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值