情景
我的项目中有六个用户角色(学校管理员,学生等),需要进行分别登陆。如果在一个realm中,对controller封装好的Token进行Service验证,需要在此realm中注入六个数据库操作对象,然后写一堆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(); //封装用户的登陆数据 UsernamePasswordToken token = new UsernamePasswordToken(userName, password); try{ subject.login(token);//执行登陆方法 return "登陆成功"; }catch (UnknownAccountException e){//用户名不存在 model.addAttribute("msg","用户名不存在"); return "用户名不存在"; }catch (IncorrectCredentialsException e){//密码错误 model.addAttribute("msg","密码错误"); return "密码错误"; } }
(为了测试方便,我用了@ResponseBody返回字符串)
2.完善自定义Realm类,继承于AuthorizingRealm,主要实现doGetAuthorizationInfo和doGetAuthenticationInfo方法
(需要实现认证和授权方法,在这里方便测试主要是认证)
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(); //关联realm securityManager.setRealm(userRealm); return securityManager; } //创建自定义 realm @Bean public UserRealm userRealm() { return new UserRealm(); } }
记得加@Configuration注解!!!!!!!
经过以上三步,可以看出shiro的简略工作流程(非常简略)就是,在web 启动阶段,读取
@Configuration注解将自定义的ream配置进默认web安全管理器(DefaultWebSecurityManager)然后将DefaultWebSecurityManager与ShiroFilterFactoryBean相关联。
当用户登陆时,从前端拿到username和password,封装好Token后,进入realm进行认证和授权,而realm就来自于刚才的shiro的DefaultWebSecurityManager配置
多realm实现原理
根据上面的shiro简略流程可知,shiro配置中写入多个realm后,在controller提交token时,只要多携带一个参数,用来进行org.apache.shiro.authc.pam.ModularRealmAuthenticator类的doAuthenticate(重写后)的验证即可明确应该用那个realm。那么,我们需要重写org.apache.shiro.authc.UsernamePasswordToken(令其携带身份参数用于选择realm)和org.apache.shiro.authc.pam.ModularRealmAuthenticator(令其根据token中的身份参数来进行选择realm)即可。
多realm实现具体操作
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.创建静态变量类(用于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,令其可以携带身份参数
@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); /**关联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(); //封装用户的登陆数据 UserToken token = new UserToken(username, Md5.getMd5(password), USER_LOGIN_TYPE); try { System.out.println("AdminLogin"); subject.login(token);//执行登陆方法 return null; } catch (UnknownAccountException e) {//用户名不存在 System.out.println("用户名错误"); return null; } catch (IncorrectCredentialsException e) {//密码错误 System.out.println("密码错误"); return null; }
参考文章
到此这篇关于Spring Boot 集成Shiro的多realm实现以及shiro基本入门的文章就介绍到这了,更多相关Spring Boot 集成Shiro内容请搜索自学编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持自学编程网!
- 本文固定链接: https://zxbcw.cn/post/197740/
- 转载请注明:必须在正文中标注并保留原文链接
- QQ群: PHP高手阵营官方总群(344148542)
- QQ群: Yii2.0开发(304864863)