Spring Boot 集成Shiro的多realm配置过程

网友投稿 313 2022-11-17


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 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 realms = getRealms()

// 登录类型对应的所有Realm

Collection 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 SimpleAuthorizYkqLNywgationInfo()

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 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


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Java实现KFC点餐系统过程解析
下一篇:Java利用反射实现框架类的方法实例
相关文章

 发表评论

暂时没有评论,来抢沙发吧~