Spring Boot 集成Shiro的多realm配置


轉自:https://blog.csdn.net/cckevincyh/article/details/79629022

 

我在做畢設的時候采用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);
}
}
1
2
3
4
5
6
7
8
9
上述代碼的意思就是如果有多個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()
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
接下來新建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
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
第三步:新建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)
}
}
}
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
第四步:創建分別處理學生登錄和教師登錄、管理員登錄的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
}
}
}
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
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
}
}
}
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
57
58
59
60
61
62
63
64
65
66
67
68
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
}
}
}
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
57
58
59
60
61
62
63
64
65
66
67
68
接下來是對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;
}
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
57
58
59
60
61
62
接下來就是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"
}


}
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
現在Spring Boot中集成Shiro實現多realm配置就完成了。

感謝以下博文,在我學習的過程中給了我很多幫助,我的博文也有一些內容參考他們的,還不夠清楚的讀者可以參考:
shiro實現不同身份使用不同Realm進行驗證
SpringBoot+Shiro學習之數據庫動態權限管理和Redis緩存
Springboot多realm集成,無ini文件,無xml配置

想看項目具體源碼,或者對我項目感興趣的可以查看:CIYOU

點贊 7
————————————————
版權聲明:本文為CSDN博主「c.」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/cckevincyh/article/details/79629022


免責聲明!

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



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