Spring Boot 集成Shiro的多realm實現(xiàn)以及shiro基本入門教程
情景
我的項目中有六個用戶角色(學校管理員,學生等),需要進行分別登陸。如果在一個realm中,對controller封裝好的Token進行Service驗證,需要在此realm中注入六個數(shù)據(jù)庫操作對象,然后寫一堆if語句來判斷應該使用那個Service服務,然后再在驗證方法(doGetAuthorizationInfo)中寫一堆if來進行分別授權,這樣寫不僅會讓代碼可讀性會非常低而且很難后期維護修改(剛寫完的時候只有上帝和你能看懂你寫的是什么,一個月之后你寫的是什么就只有上帝能看懂了)。
所以一定要配置多個realm來分別進行認證授權操作。shiro有對多個realm的處理,當配置了多個Realm時,shiro會用自帶的org.apache.shiro.authc.pam.ModularRealmAuthenticator類的doAuthenticate方法來進行realm判斷,源碼:
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);
}
}
assertRealmsConfigured();的作用是驗證realm列表是否為空,如果一個realm也沒有則會拋出IllegalStateException異常(爆紅:Configuration error: No realms have been configured! One or more realms must be present to execute an authentication attempt.)
當realm只有一個時直接返回,當realm有多個時返回所有的realm。而我們要做的就是寫多個realm后重寫ModularRealmAuthenticator下的doAuthenticate方法,使它能滿足我們的項目需求。
那么改怎么重寫ModularRealmAuthenticator下的doAuthenticate方法,使它能滿足我們的項目需求呢?這就需要分析我們使用shiro的使用方法了。
shiro的使用
1.Controller層中,獲取當前用戶后將用戶名和密碼封裝UsernamePasswordToken對象,然后調用Subject中的登陸方法subject.login(UsernamePasswordToken)
@RequestMapping("/user/login")
@ResponseBody
public String Login(String userName,String password){
//獲取當前用戶 subject
Subject subject = SecurityUtils.getSubject();
//封裝用戶的登陸數(shù)據(jù)
UsernamePasswordToken token =
new UsernamePasswordToken(userName, password);
try{
subject.login(token);//執(zhí)行登陸方法
return "登陸成功";
}catch (UnknownAccountException e){//用戶名不存在
model.addAttribute("msg","用戶名不存在");
return "用戶名不存在";
}catch (IncorrectCredentialsException e){//密碼錯誤
model.addAttribute("msg","密碼錯誤");
return "密碼錯誤";
}
}
(為了測試方便,我用了@ResponseBody返回字符串)
2.完善自定義Realm類,繼承于AuthorizingRealm,主要實現(xiàn)doGetAuthorizationInfo和doGetAuthenticationInfo方法
(需要實現(xiàn)認證和授權方法,在這里方便測試主要是認證)
public class StudentRealm extends AuthorizingRealm {
@Resource
private StudentsService studentsService;
//授權
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
//認證
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("Shiro=========Student認證");
UserToken userToken = (UserToken) token;
Students students = studentsService.queryByNum(userToken.getUsername());
//賬號不存在
if (students == null) {
System.out.println("學生不存在");
//向上層提交UnknownAccountException異常,在controller層處理
throw new UnknownAccountException();
}
//密碼認證,shiro來做,可以自定義加密方式
return new SimpleAuthenticationInfo("", students.getPassword(), USER_LOGIN_TYPE);
}
}
3.配置shiro,將realm配置進shiro(很多教程是使用xml配置或者ini配置,在這里用java代碼配置,功能都是一樣的,看個人習慣了)
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//設置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
return bean;
}
//DefaultWebSecurityManager 默認web安全管理器
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//關聯(lián)realm
securityManager.setRealm(userRealm);
return securityManager;
}
//創(chuàng)建自定義 realm
@Bean
public UserRealm userRealm() {
return new UserRealm();
}
}
記得加@Configuration注解?。。。。。?!
經過以上三步,可以看出shiro的簡略工作流程(非常簡略)就是,在web 啟動階段,讀取
@Configuration注解將自定義的ream配置進默認web安全管理器(DefaultWebSecurityManager)然后將DefaultWebSecurityManager與ShiroFilterFactoryBean相關聯(lián)。
當用戶登陸時,從前端拿到username和password,封裝好Token后,進入realm進行認證和授權,而realm就來自于剛才的shiro的DefaultWebSecurityManager配置
多realm實現(xiàn)原理
根據(jù)上面的shiro簡略流程可知,shiro配置中寫入多個realm后,在controller提交token時,只要多攜帶一個參數(shù),用來進行org.apache.shiro.authc.pam.ModularRealmAuthenticator類的doAuthenticate(重寫后)的驗證即可明確應該用那個realm。那么,我們需要重寫org.apache.shiro.authc.UsernamePasswordToken(令其攜帶身份參數(shù)用于選擇realm)和org.apache.shiro.authc.pam.ModularRealmAuthenticator(令其根據(jù)token中的身份參數(shù)來進行選擇realm)即可。
多realm實現(xiàn)具體操作
1.寫多個自定義的realm
public class AdminRealm extends AuthorizingRealm {
@Resource
private AdminService adminService;
private static final String USER_LOGIN_TYPE = UserType.AdminRealm;
@Override
public String getName() {
return UserType.AdminRealm;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("Shiro=========Admin認證");
UserToken userToken = (UserToken) token;
Admin admin = adminService.queryById(userToken.getUsername());
if(admin == null){
System.out.println("管理員不存在");
throw new UnknownAccountException();
}
return new SimpleAuthenticationInfo("", admin.getAdminpassword(), USER_LOGIN_TYPE);
}
}
2.創(chuàng)建靜態(tài)變量類(用于realm選擇)
public class UserType {
//實習學校管理員
public static final String SchoolAdminRealm = "schooladminrealm";
//學生
public static final String StudentRealm ="studentrealm";
//管理員
public static final String AdminRealm ="adminrealm_1";
//導員
public static final String InstructorRealm ="instructorrealm";
//實習帶隊老師
public static final String UniversityteacherRealm ="universityteacherrealm";
//實習指導老師
public static final String SchoolTeacherRealm ="schoolteacherrealm";
}
3.重寫UsernamePasswordToken,令其可以攜帶身份參數(shù)
@Component
public class UserModularRealmAuthenticator extends ModularRealmAuthenticator {
@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) {
// 判斷getRealms()是否返回為空,ModularRealmAuthenticator 自帶
assertRealmsConfigured();
// 強制轉換回自定義的UserToken
UserToken token = (UserToken) authenticationToken;
String loginType = token.getLoginType();
Collection<Realm> realms = getRealms();
for (Realm realm : realms) {
System.out.println(realm.getName().toLowerCase());
if (realm.getName().toLowerCase().contains(loginType)){
//找到登錄類型對應的指定Realm
return doSingleRealmAuthentication(realm, token);
}
}
//沒找到正確的realm的異常處理
String msg = "Configuration error: Didn't find the right realm";
throw new IllegalStateException(msg);
}
}
4.shiro的配置中寫入自定義的realm,還有其它配置
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//設置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
return bean;
}
//DefaultWebSecurityManager
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(
@Qualifier("schoolAdminRealm") SchoolAdminRealm schoolAdminRealm,
@Qualifier("studentRealm") StudentRealm studentRealm,
@Qualifier("adminRealm") AdminRealm adminRealm,
@Qualifier("schoolTeacherRealm") SchoolTeacherRealm schoolTeacherRealm,
@Qualifier("instructorRealm") InstructorRealm instructorRealm,
@Qualifier("universityteacherRealm") UniversityteacherRealm universityteacherRealm,
@Qualifier("userModularRealmAuthenticator") UserModularRealmAuthenticator userModularRealmAuthenticator
) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setAuthenticator(userModularRealmAuthenticator);
/**關聯(lián)realm
*securityManager.setRealm() 是配置單個realm,不可用它配置多個realm
*securityManager.setRealms()配置多個realm,
*List<Realm> realms可以直接被set進去
*/
List<Realm> realms = new ArrayList<Realm>();
realms.add(schoolAdminRealm);
realms.add(studentRealm);
realms.add(adminRealm);
realms.add(schoolTeacherRealm);
realms.add(instructorRealm);
realms.add(universityteacherRealm);
securityManager.setRealms(realms);
System.out.println(securityManager.getRealms().toString());
return securityManager;
}
//實習學校管理員
@Bean(name = "schoolAdminRealm")
public SchoolAdminRealm SchoolAdminRealm() {
return new SchoolAdminRealm();
}
//學生
@Bean(name = "studentRealm")
public StudentRealm StudentRealm() {
return new StudentRealm();
}
//管理員
@Bean(name = "adminRealm")
public AdminRealm AdminRealm() {
return new AdminRealm();
}
//導員
@Bean(name = "instructorRealm")
public InstructorRealm InstructorRealm() {
return new InstructorRealm();
}
//實習帶隊老師
@Bean(name = "universityteacherRealm")
public UniversityteacherRealm UniversityteacherRealm() {
return new UniversityteacherRealm();
}
//實習指導老師
@Bean(name = "schoolTeacherRealm")
public SchoolTeacherRealm SchoolTeacherRealm() {
return new SchoolTeacherRealm();
}
}
5.在controller中使用重寫后的UsernamePasswordToken(UserToken)即可
//管理員登陸
@RequestMapping(value = "/AdminLogin", produces = "text/html;charset=UTF-8")
@ResponseBody//為了測試方便,返回字符串
public String AdminLogin(
@RequestParam(value = "username") String username,
@RequestParam(value = "password") String password) {
//獲取當前用戶 subject
Subject subject = SecurityUtils.getSubject();
//封裝用戶的登陸數(shù)據(jù)
UserToken token = new UserToken(username, Md5.getMd5(password), USER_LOGIN_TYPE);
try {
System.out.println("AdminLogin");
subject.login(token);//執(zhí)行登陸方法
return null;
} catch (UnknownAccountException e) {//用戶名不存在
System.out.println("用戶名錯誤");
return null;
} catch (IncorrectCredentialsException e) {//密碼錯誤
System.out.println("密碼錯誤");
return null;
}
到此這篇關于Spring Boot 集成Shiro的多realm實現(xiàn)以及shiro基本入門的文章就介紹到這了,更多相關Spring Boot 集成Shiro內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
使用mybatisPlus生成oracle自增序列遇到的坑及解決
這篇文章主要介紹了使用mybatisPlus生成oracle自增序列遇到的坑及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-03-03
Java實現(xiàn)自定義Excel數(shù)據(jù)排序的方法詳解
通常,我們可以在Excel中對指定列數(shù)據(jù)執(zhí)行升序或者降序排序,在需要自定義排序情況下,我們也可以自行根據(jù)排序需要編輯數(shù)據(jù)排列順序。本文將通過Java應用程序來實現(xiàn)如何自定義排序,需要的可以參考一下2022-09-09
Java使用Iterator迭代器遍歷集合數(shù)據(jù)的方法小結
這篇文章主要介紹了Java使用Iterator迭代器遍歷集合數(shù)據(jù)的方法,結合實例形式分析了java迭代器進行集合數(shù)據(jù)遍歷的常見操作技巧,需要的朋友可以參考下2019-11-11

