Spring Boot 集成Shiro的多realm实现以及shiro基本入门

Spring Boot 集成Shiro的多realm实现以及shiro基本入门

情景

我的项目中有六个用户角色(学校管理员,学生等),需要进行分别登陆。如果在一个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;
        }

参考文章

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值