SpringBoot 整合shiro实现多Realm的控制

1、SpringBoot整合shiro

1.1、基本的整合

1.1.1、导包
 <!-- <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
      <!--  <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>-->

       <!-- <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>-->

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!--导入的是spring对shiro的支持包-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
1.1.2、配置模板引擎的配置(application.properties)
#配置模板引擎
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.cache=false
1.1.3、编写login.html页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

   <form action="/login" method="post">
       用户名:<input  type="text" name="userName"/><span th:text="${userNameError}"></span><br>
       密码:<input type="password" name="password"><span th:text="${passwordError}"></span><br>
       <input type="submit" value="登陆">
   </form>

</body>
</html>
1.1.4、编写index.html页面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

   this is index Page And you?

</body>
</html>
1.1.5、编写ShiroConfig的配置文件
@SpringBootConfiguration
public class ShiroConfig {

    private Logger logger= LoggerFactory.getLogger(ShiroConfig.class);

    //配置咋们的过滤器拦截请求
    @Bean
     public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
         ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
         logger.info("shiro的过滤器执行了.....");

         //配置如果认证没有通过的话 那么跳转的这个页面
         shiroFilterFactoryBean.setLoginUrl("/toLogin");


         Map<String,String> maps=new LinkedHashMap<>();
         //第一个参数是路劲  第二个参数是过滤器的名字
        /**
         * 常见的过滤器的名字以及含义
         * /**:当前以及目录和后面的所有子目录全部都匹配上
         *       127.0.0.1:8080/bobo     127.0.0.1:8080/bobo/xiaobobo
         * /* :这个相当于只是匹配当前这一级的节点   127.0.0.1:8080/bobo
         *      127.0.0.1:8080/bobo/xiaobobo
         * authc:认证的过滤器
         * anon: 表示的是/toIndex这个请求 不认证就可以访问 (匿名访问)
         *        maps.put("/toIndex","anon");
         * logout:登陆过滤器
         *        maps.put("/logout","logout")
         * perms:权限控制的
         * roles:具有某一个角色才能访问
         *
         * 注意事项:  /** 这个配置一定是最后 一个
         *
         */
         //maps.put("/toIndex","anon");  //表示的是不需要认证就可以访问

        maps.put("/login","anon");  //访问请求的地址
        maps.put("/**","authc");   //所有的请求都必须在用户认证之后才能访问


         shiroFilterFactoryBean.setFilterChainDefinitionMap(maps);

         //设置这个安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);
         return shiroFilterFactoryBean;
     }


    //配置的是安全管理器
    @Bean
    public DefaultWebSecurityManager securityManager(@Qualifier("myRealm") MyRealm myRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //设置校验的realm对象
        logger.info("securityManager的过滤器执行了.....");
        securityManager.setRealm(myRealm);
        return securityManager;
    }



    //配置的是realm
    @Bean
    public MyRealm myRealm(){
        MyRealm myRealm = new MyRealm();
        logger.info("myRealm的过滤器执行了.....");
        return myRealm;
    }

}

1.1.6、controller的编写
@Controller
public class UserController {

    private Logger logger= LoggerFactory.getLogger(UserController.class);

    @RequestMapping("/toLogin")
    public String toLogin(){
        return "login";
    }

    /**
     * 跳转到Index页面的方法
     * @return
     */
    @RequestMapping("toIndex")
    public String toIndex(){
      return "index";
    }


    /**
     * 登陆的方法
     * @return
     */
    @RequestMapping(value = "login")
    public String login(User user, Model model){
         //封装成请求对象
        UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(), user.getPassword());
        //获取登陆的主体对象
        Subject subject = SecurityUtils.getSubject();
        //登陆
        try{
            subject.login(token);
        }catch (UnknownAccountException err){  //用户名不对
            logger.error("用户名不对");
            model.addAttribute("userNameError","用户名不对");
            return "login";
        }catch (IncorrectCredentialsException err){ //说明是密码不对
            logger.error("密码不对");
            model.addAttribute("passwordError","密码不对");
            return "login";
        }catch (Exception err){
            logger.error("其他问题造成登陆失败:"+err.fillInStackTrace());
            model.addAttribute("otherError","其他问题造成登陆失败");
            return "login";
        }
       return "index";
    }
}
1.1.7、Realm的编写
public class MyRealm extends AuthorizingRealm {

    @Override
    public String getName() {
        return "MyRealm";
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //第一步:获取用户名
        String userName= (String) authenticationToken.getPrincipal();
        //通过用户名查询用户对象
        if(!(userName.equals("xiaobobo"))){
           return null;
        }
        //假设查询出来了
        User user = new User(1, "xiaobobo", "123");
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user.getUserName(), user.getPassword(), getName());

        return simpleAuthenticationInfo;
    }
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
}
1.1.8、理解图

在这里插入图片描述

1.2、退出的问题

1.2.1、在页面上编写退出按钮
<a href="/logout">退出</a>
1.2.2、在全局的过滤器中进行登出过滤器的配置
 maps.put("/logout","logout");  //这个就是退出功能

1.3、密码散列的问题

1.3.1、在配置文件申明凭证匹配器
//配置密码散列的问题
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        //这里就是设置散列的方法的地方
        hashedCredentialsMatcher.setHashAlgorithmName("MD5");
        //设置的是散列的次数
        hashedCredentialsMatcher.setHashIterations(1);
        return hashedCredentialsMatcher;
    }
1.3.2、改造realm让他支持盐
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //第一步:获取用户名
        String userName= (String) authenticationToken.getPrincipal();
        //通过用户名查询用户对象
        if(!(userName.equals("xiaobobo"))){
           return null;
        }
        //假设查询出来了
        User user = new User(1, "xiaobobo", "e99a18c428cb38d5f260853678922e03");
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
                user.getUserName(),
                user.getPassword(),
                ByteSource.Util.bytes("abc")
                ,getName());
        return simpleAuthenticationInfo;
    }

1.4、在首页显示用户信息的问题

1.4.1、通过代码实现在首页显示用户信息

改造 realm

 @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //第一步:获取用户名
        String userName= (String) authenticationToken.getPrincipal();
        //通过用户名查询用户对象
        if(!(userName.equals("xiaobobo"))){
           return null;
        }
        //假设查询出来了
        User user = new User(1, "xiaobobo", "e99a18c428cb38d5f260853678922e03");
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
                user,
                user.getPassword(),
                ByteSource.Util.bytes("abc")
                ,getName());

        return simpleAuthenticationInfo;
    }

改造Cotnroller

 public String login(User user, Model model){
         //封装成请求对象
        UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(), user.getPassword());
        //获取登陆的主体对象
        Subject subject = SecurityUtils.getSubject();
        //登陆
        try{
            subject.login(token);
        }catch (UnknownAccountException err){  //用户名不对
            logger.error("用户名不对");
            model.addAttribute("userNameError","用户名不对");
            return "login";
        }catch (IncorrectCredentialsException err){ //说明是密码不对
            logger.error("密码不对");
            model.addAttribute("passwordError","密码不对");
            return "login";
        }catch (Exception err){
            logger.error("其他问题造成登陆失败:"+err.fillInStackTrace());
            model.addAttribute("otherError","其他问题造成登陆失败");
            return "login";
        }
        //首页要显示用户信息
        //获取用户信息
        //这个方法返回的数据 实际上就是 realm中认证的SimpleAuthticationInfo的第一个参数的数据
        User user1= (User) SecurityUtils.getSubject().getPrincipal();
        model.addAttribute("user",user);
       return "index";
    }

改在index.html文件

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

   欢迎<span th:text="${user.userName}"></span>大爷再次回来.....
   this is index Page And you? <br>

   <a href="/logout">退出</a>

</body>
</html>

1.5、授权有很多方式

代码授权

1.5.1过滤器授权

需求:假设在我的首页有一个按钮 这个按钮访问后台数据的时候 addUser接口这个接口必须要用户具有 user:add权限才能访问

1、在realm中查询用户的权限和角色放到缓存中

 @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //第一步:先获取用户名
        User user= (User) principalCollection.getPrimaryPrincipal();
        //第二步:通过用户名查询数据库  当前用户具有的权限  以及角色
        // ....
        // ...
        Set<String> perms=new HashSet<>();
        perms.add("ad:add");

        Set<String> roles=new HashSet<>();
        roles.add("seller");


        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(roles);
        simpleAuthorizationInfo.setStringPermissions(perms);
        return simpleAuthorizationInfo;
    }

2、在HTML页面上设置访问元素

 <a href="/addUser">添加用户</a>

3、在配置文件中配置过滤器授权

maps.put("/addUser","perms[user:add]"); //要请求这个地址 用户必
1.5.2、注解授权

简单的、它要写个注解就可以了

1、在配置文件中配置aop对注解的支持

  //下面配置AOP对注解的支持(也就是shiro中注解的支持)
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
            @Qualifier("securityManager") DefaultWebSecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
    @Bean
    @ConditionalOnMissingBean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

2、在方法上使用注解来完成权限的判断

  @RequestMapping("toDelete")
    @RequiresPermissions({"user:delete"})  //要访问当前的方法必须具有user:delete的权限才能访问
    //@RequiresRoles({"seller"})
    public String toDelete(){
        return "delete";
    }
1.5.3、HTML页面上基于Thymeleaf的支持

1、导包

<!--导入thymeleaf对shiro的支持包-->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>

2、配置方言

  /*配置shiro-dialect这个方言*/
    @Bean
    public ShiroDialect shiroDialect(){
        return new ShiroDialect();
    }

3、使用shiro的标签

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
   欢迎<span th:text="${user.userName}"></span>大爷再次回来.....
   this is index Page And you? <br>
   <a href="/logout">退出</a><br>

   <hr>
   <a href="/addUser">添加用户(测试过滤器授权)</a>
   <br>
   <a href="/toDelete">用户删除(测试注解授权)</a>

   <!--下面玩下shiro标签库中的标签-->
   <!--<shiro:authenticated>
       <span>
           用户的身份验证是成功的
       </span>
   </shiro:authenticated>

   <shiro:guest>
       <span>你是游客</span>
   </shiro:guest>

   <shiro:hasPermission name="user:add">
        <span>用户必须具有某一个权限才能访问</span>
   </shiro:hasPermission>

   <shiro:hasAllRoles name="buyer,seller">
       <span>拥有某一个角色下面才显示</span>
   </shiro:hasAllRoles>

   <shiro:lacksPermission>
       <span>没有某一个权限的时候才能访问</span>
   </shiro:lacksPermission>

   <shiro:lacksRole name="xxx">
        <span>没有某一个角色的时候才能访问</span>
   </shiro:lacksRole>


   <shiro:notAuthenticated>
       <span>没有认证通过才能显示</span>
   </shiro:notAuthenticated>
-->
   <hr>
   <!--下面就是显示用户信息的-->
   <shiro:principal property="userName"/> <br>
   <!--下面这个标签就表示的是用户已经登陆-->
   <shiro:user>
       <label>欢迎[<shiro:principal property="userName"/>]登陆</label>
   </shiro:user>
</body>
</html>

1.6、缓存的使用

明白一个问题、为什么要使用缓存

演示了每一次 授权的时候 都会去访问咋们的 realm中的授权的方法

这样的话咋们的数据库的压力就会比较大 这种情况下 咋们缓存就应运而生了

缓存是缓存的是咋们的的这个授权信息的(不是其他信息)

1.6.1、导包
 <!--下面就是缓存需要的包-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>2.10.4</version>
        </dependency>

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>1.4.0</version>
        </dependency>
1.6.2、在resources下创建ehcache.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" dynamicConfig="false">
    <diskStore path="G:\mytemp" />

    <cache name="users"
           timeToLiveSeconds="300"
           maxEntriesLocalHeap="1000"/>
    <!--
        name:缓存名称。
        maxElementsInMemory:缓存最大个数。
        eternal:对象是否永久有效,一但设置了,timeout将不起作用。
        timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
        timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
        overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。
        diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
        maxElementsOnDisk:硬盘最大缓存个数。
        diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
        diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
        memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
        clearOnFlush:内存数量最大时是否清除。
    -->
    <defaultCache name="defaultCache"
                  maxElementsInMemory="10000"
                  eternal="false"
                  timeToIdleSeconds="120"
                  timeToLiveSeconds="120"
                  overflowToDisk="false"
                  maxElementsOnDisk="100000"
                  diskPersistent="false"
                  diskExpiryThreadIntervalSeconds="120"
                  memoryStoreEvictionPolicy="LRU"/>
</ehcache>
1.6.3、在ShiroConfig中进行缓存的配置
   //下面进行缓存的配置
    @Bean
    public EhCacheManager ehCacheManager(){
        EhCacheManager ehCacheManager = new EhCacheManager();
        ehCacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
        return ehCacheManager;
    }
1.6.4、在安全管理器上面进行配置
 //配置的是安全管理器
    @Bean
    public DefaultWebSecurityManager securityManager(@Qualifier("myRealm") MyRealm myRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //设置校验的realm对象
        logger.info("securityManager的过滤器执行了.....");
        securityManager.setRealm(myRealm);
        //下面就设置缓存了
        securityManager.setCacheManager(ehCacheManager());
        return securityManager;
    }

1.7、session的管理

1.7.1、在ShiroConfig中进行配置
 //Session的管理
    public DefaultWebSessionManager sessionManager(){
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        //Session到底的时候是否自动删除
        sessionManager.setDeleteInvalidSessions(true);
        //设置session的超时时间
        sessionManager.setGlobalSessionTimeout(1);
        return sessionManager;
    }
1.7.2、在SecurityManager
@Bean
    public DefaultWebSecurityManager securityManager(@Qualifier("myRealm") MyRealm myRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //设置校验的realm对象
        logger.info("securityManager的过滤器执行了.....");
        securityManager.setRealm(myRealm);
        //下面就设置缓存了
        securityManager.setCacheManager(ehCacheManager());
        //设置Session的管理
        securityManager.setSessionManager(sessionManager());
        return securityManager;
    }

1.8、rememeberMe功能的实现

Session+Cookie的模式来做 记住我这个功能

shiro跟咋们的思路也是一样的

1.8.1、配置ShiroConfig
 //下面做的就是rememeberMe的这个功能
    @Bean
    public CookieRememberMeManager rememberMeManager(){
        CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();
        //设置咋们的Cookie
        rememberMeManager.setCookie(simpleCookie());
        return rememberMeManager;
    }

    /**
     * 自定义一个Cookie对象
     * @return
     */
    @Bean
    public SimpleCookie simpleCookie(){
        SimpleCookie simpleCookie = new SimpleCookie("rememebenrMe");
        simpleCookie.setMaxAge(2592000);
        return simpleCookie;
    }
1.8.2、在HTML页面上添加记住我的这个功能
 <form action="/login" method="post">
       用户名:<input  type="text" name="userName"/><span th:text="${userNameError}"></span><br>
       密码:<input type="password" name="password"><span th:text="${passwordError}"></span><br>
       是否使用记住我的这个功能:<input type="checkbox" name="rememberMe"><br>
       <input type="submit" value="登陆">
   </form>
1.8.3、在认证的token上设置记住我的这个方法
 @RequestMapping(value = "login")
    public String login(User user, Model model,boolean rememberMe){

        logger.info("接受到前端的数据rememebenrMe="+rememberMe);

         //封装成请求对象
        UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(), user.getPassword());
        //设置记住我的这个功能
        token.setRememberMe(rememberMe);
        //获取登陆的主体对象
        Subject subject = SecurityUtils.getSubject();
        //登陆
        try{
            subject.login(token);
        }catch (UnknownAccountException err){  //用户名不对
            logger.error("用户名不对");
            model.addAttribute("userNameError","用户名不对");
            return "login";
        }catch (IncorrectCredentialsException err){ //说明是密码不对
            logger.error("密码不对");
            model.addAttribute("passwordError","密码不对");
            return "login";
        }catch (Exception err){
            logger.error("其他问题造成登陆失败:"+err.fillInStackTrace());
            model.addAttribute("otherError","其他问题造成登陆失败");
            return "login";
        }
        //首页要显示用户信息
        //获取用户信息
        //这个方法返回的数据 实际上就是 realm中认证的SimpleAuthticationInfo的第一个参数的数据
        User user1= (User) SecurityUtils.getSubject().getPrincipal();
        model.addAttribute("user",user);
       return "index";
    }

1.8.4、在过滤器中配置哪些页面使用了rememberme这个公共之后可以直接访问
 public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
         ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
         logger.info("shiro的过滤器执行了.....");

         //配置如果认证没有通过的话 那么跳转的这个页面
         shiroFilterFactoryBean.setLoginUrl("/toLogin");
         //如果没有 权限访问 那么就跳转到一个友好的也好的页面去提示用户
         shiroFilterFactoryBean.setUnauthorizedUrl("/toUnAuthziration");


         Map<String,String> maps=new LinkedHashMap<>();
         //第一个参数是路劲  第二个参数是过滤器的名字
        /**
         * 常见的过滤器的名字以及含义
         * /**:当前以及目录和后面的所有子目录全部都匹配上
         *       127.0.0.1:8080/bobo     127.0.0.1:8080/bobo/xiaobobo
         * /* :这个相当于只是匹配当前这一级的节点   127.0.0.1:8080/bobo
         *      127.0.0.1:8080/bobo/xiaobobo
         * authc:认证的过滤器
         * anon: 表示的是/toIndex这个请求 不认证就可以访问 (匿名访问)
         *        maps.put("/toIndex","anon");
         * logout:登陆过滤器
         *        maps.put("/logout","logout")
         * perms:权限控制的
         * roles:具有某一个角色才能访问
         *
         * 注意事项:  /** 这个配置一定是最后 一个
         *
         */
         //maps.put("/toIndex","anon");  //表示的是不需要认证就可以访问
        maps.put("/login","anon");  //访问请求的地址
        maps.put("/logout","logout");  //这个就是退出功能

        //设置哪些鞋面是使用了 rememeberMe功能之后 可以直接访问  不用认证了
        maps.put("/addUser","user"); //这个设置的意思:假设我们使用了rememberMe这个功能那么 /addUser这个页面 就可以直接访问不需要认证了
        maps.put("/toIndex","user"); //这个设置的意思:假设我们使用了rememberMe这个功能那么 /addUser这个页面 就可以直接访问不需要认证了
        maps.put("/toDeleteUser","user"); //这个设置的意思:假设我们使用了rememberMe这个功能那么 /addUser这个页面 就可以直接访问不需要认证了

        maps.put("/addUser","perms[user:add]"); //要请求这个地址 用户必须具有 user:add的权限
        maps.put("/add","roles[seller]"); //要请求这个地址 用户必须具有 user:add的权限
        maps.put("/**","authc");   //所有的请求都必须在用户认证之后才能访问       shiroFilterFactoryBean.setFilterChainDefinitionMap(maps);

         //设置这个安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);
         return shiroFilterFactoryBean;
     }

SpringBoot 整合shiro实现多Realm的控制

场景:假设在我们的系统中 有两个角色 用户 和 管理员

用户有100W 管理员只有 2个

假设是让你设计这个表 你会怎么设计?

经典的五张表(用户和管理员 都是用户? 难道你要设计到一张表 )

现在的管理员登陆:通过用户名查询 用户数据(100W+)

如果是我们能够将这两个角色的数据设计到两张表里面

管理员(2个人) 用户表

管理员的表 用户的表 假设我们都使用shiro来进行认证 ? 怎么办?

设计思路?

就是在用户登陆时候 设置一个 登陆类型

重写 UserNameAndPasswordToken 在执行正式的登陆的时候?根据登陆的类型选择 我们要使用的Realm 最终去进行认证 那么这个功能就搞定了

存在两个问题

​ 1:在哪里去做选择 执行哪一个realm?

​ ModularRealmAuthenticator.java类进行拓展(直接和realm打交道的)

​ 2:我们的登陆类型如何来定

作业:编写一个用户的基本的CRUD 通过权限控制几个方法的请求 顺便缓存、rememeberme功能实现了

1.1、导包

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!--导入shiro的功能包-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>

1.2、配置application.properies

#Thymeleaf的配置
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.cache=false

1.3、编写登陆页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

   下面是User的登陆<br>
<form action="/userLogin" method="post">
    用户名:<input type="text" name="userName"><br>
    密码:<input type="password" name="password"><br>
    <input type="submit" value="用户登陆">
</form>

<hr>
   下面是Admin的登陆
   <form action="/adminLogin" method="post">
       用户名:<input type="text" name="userName"><br>
       密码:<input type="password" name="password"><br>
       <input type="submit" value="用户登陆">
   </form>

</body>
</html>

1.4、编写user模块登陆成功的页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

this is user index page  And you?

</body>
</html>

1.5、编写Admin模块登陆成功的页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

   this is admin index page And you?

</body>
</html>

1.6、编写登陆类型的枚举

public enum LoginType {
    USER("User"), ADMIN("Admin");

    private String type;    //定义的是登陆的类型

    private LoginType(String type){
       this.type=type;
    }

    @Override
    public String toString() {
        return this.type.toString();
    }
}

1.7、编写校验器

public class CustomModularRealmAuthenticator extends ModularRealmAuthenticator {

    /**
     * 想干一件事
     *    就是通过传入数据的类型  来选择使用哪一个Realm
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        //做Realm的一个校验
        assertRealmsConfigured();
        //获取前端传递过来的token
        CustomToken customToken=(CustomToken)authenticationToken;
        //现在就可以获取这个登陆的类型了
        String loginType = customToken.getLoginType();  //  登陆类型   1:User   Admin
        //获取所有的realms()
        Collection<Realm> realms = getRealms();
        //登陆类型对应的所有realm全部获取到
        Collection<Realm> typeRealms=new ArrayList<>();
        for (Realm realm:realms){
            //realm类型和现在登陆的类型做一个对比
            if(realm.getName().contains(loginType)){   //就能分开这两个realm
                typeRealms.add(realm);
            }
        }

        if(typeRealms.size()==1){
             return doSingleRealmAuthentication(typeRealms.iterator().next(),customToken);
        }else{
            return doMultiRealmAuthentication(typeRealms,customToken);
        }
    }
}

1.8、编写UserController

@Controller
public class UserController {

    //用户登陆的类型
    private static final String LOGIN_TYPE= LoginType.USER.toString();

    private Logger logger= LoggerFactory.getLogger(UserController.class);

    /**
     * 用户登陆的方法
     * @param user
     * @return
     */
    @RequestMapping("userLogin")
    public String login(User user){
        //封装请求对象
        CustomToken customToken=new CustomToken(user.getUserName(),user.getPassword(),LOGIN_TYPE);
        //获取登陆主体
        Subject subject = SecurityUtils.getSubject();
        try{
            subject.login(customToken);
            if(subject.isAuthenticated()){
                //说明认证是成功的
                return "user_index";
            }
        }catch (UnknownAccountException err){  //用户名不对
            logger.error("用户名不对");
        }catch (IncorrectCredentialsException err){//密码不对
            logger.error("密码不对");
        }catch (Exception err){    //其他错误 造成登陆失败
            logger.error("其他错误造成了登陆错误");
        }
        return "login";
    }


}

1.9、编写AdminController

@Controller
public class AdminController {

    //用户登陆的类型
    private static final String LOGIN_TYPE= LoginType.ADMIN.toString();

    private Logger logger= LoggerFactory.getLogger(UserController.class);

    /**
     * 用户登陆的方法
     * @param admin
     * @return
     */
    @RequestMapping("adminLogin")
    public String login(Admin admin){
        //封装请求对象
        CustomToken customToken=new CustomToken(admin.getUserName(),admin.getPassword(),LOGIN_TYPE);
        //获取登陆主体
        Subject subject = SecurityUtils.getSubject();
        try{
            subject.login(customToken);
            if(subject.isAuthenticated()){
                //说明认证是成功的
                return "admin_index";
            }
        }catch (UnknownAccountException err){  //用户名不对
            logger.error("用户名不对");
        }catch (IncorrectCredentialsException err){//密码不对
            logger.error("密码不对");
        }catch (Exception err){    //其他错误 造成登陆失败
            logger.error("其他错误造成了登陆错误");
        }
        return "login";
    }

}

1.10、编写User对象

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
    private static final long serialVersionUID = 1841757056845722315L;
    private int id;
    private String userName;
    private String password;
}

1.11.编写Admin对象

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Admin implements Serializable {
    private int id;
    private String userName;
    private String password;
}

1.12、编写配置文件

@SpringBootConfiguration
public class ShiroConfig {

    //拦截的过滤器的配置
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(
            @Qualifier("securityManager")DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置下如果认证没有成功的登陆地址
        shiroFilterFactoryBean.setLoginUrl("/toLogin");
        Map<String,String> maps=new HashMap<>();
        maps.put("/toLogin","anon");
        maps.put("/userLogin","anon");
        maps.put("/adminLogin","anon");
        maps.put("/**","authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(maps);

        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        return shiroFilterFactoryBean;
    }

    //配置下SecurityManager
    @Bean
    public DefaultWebSecurityManager securityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //设置校验器
        securityManager.setAuthenticator(authenticator());
        List<Realm> realms=new ArrayList<>();
        realms.add(userRealm());
        realms.add(adminRealm());
        //设置Realm
        securityManager.setRealms(realms);
        return securityManager;
    }


    @Bean
    public UserRealm userRealm(){
        UserRealm userRealm = new UserRealm();
        return userRealm;
    }

    @Bean
    public AdminRealm adminRealm(){
        AdminRealm adminRealm = new AdminRealm();
        return adminRealm;
    }


    //下面就是认证器的配置
    @Bean
    public CustomModularRealmAuthenticator authenticator(){
        CustomModularRealmAuthenticator authenticator = new CustomModularRealmAuthenticator();
        return authenticator;
    }
}

1.13、编写用户的Realm

public class UserRealm extends AuthorizingRealm {

    private Logger logger= LoggerFactory.getLogger(AdminRealm.class);


    @Override
    public String getName() {
        return "UserRealm";
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        logger.info("UserRealm执行了....");

        //固定的
        //第一步:获取用户名
        String userName = (String) authenticationToken.getPrincipal();
        //第二步:查询数据库
        //查询中....
        //查询中....
        if(!(userName.equals("xiaobobo"))){
            return null;
        }

        //下面就是查询出来的对象
        User user = new User(1, "xiaobobo", "123");
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUserName(), user.getPassword(), getName());
        return authenticationInfo;
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
}

1.14、编写Admin的Realm

public class AdminRealm extends AuthorizingRealm {


    private Logger logger= LoggerFactory.getLogger(AdminRealm.class);

    @Override
    public String getName() {
        return "AdminRealm";
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //固定的
        //第一步:获取用户名
        String userName = (String) authenticationToken.getPrincipal();
        //第二步:查询数据库
        //查询中....
        //查询中....
        if(!(userName.equals("xiaowangzi"))){
            return null;
        }

        logger.info("AdminRealm执行了....");

        //下面就是查询出来的对象
        Admin admin = new Admin(1, "xiaowangzi", "123");
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(admin.getUserName(), admin.getPassword(), getName());
        return authenticationInfo;
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
}

1.15、token的拓展类的编写

public class CustomToken extends UsernamePasswordToken {

    //定义登陆的类型是为了在后面的校验中 去选择使用哪一个realm
    private String loginType;

    public CustomToken(String userName,String password,String loginType){
          super(userName,password);
          this.loginType=loginType;
    }

    public void setLoginType(String loginType) {
        this.loginType = loginType;
    }

    public String getLoginType() {
        return loginType;
    }
}

1.16、登陆页面跳转的Controller编写

@Controller
public class IndexController {
    @RequestMapping("toLogin")
    public String toLogin(){
        return "login";
    }
}
发布了50 篇原创文章 · 获赞 21 · 访问量 3708

猜你喜欢

转载自blog.csdn.net/wmlwml0000/article/details/104555047