• shiro(三)——springboot整合shiro


    该整合项目完全参照 狂神说java 的《springboot整合shiro框架》教学视频完成,如有不懂的地方可以查看该教学视频。

    目录:

    1.该整合项目所需的依赖

        <dependencies>
    
            <!--
            subject 用户
            securityManager 管理所有用户
            realm  连接数据
            -->
    
            <!--连接数据库的依赖-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.15</version>
            </dependency>
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>1.2.17</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.1.13</version>
            </dependency>
    
            <!--引入mybatis,这是mybatis官方提供的适配springboot的,而不是springboot自己的-->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.1.2</version>
            </dependency>
    
            <!--不想书写setter、getter方法,导入此依赖-->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.16.22</version>
            </dependency>
    
            <!--shiro整合spring的包-->
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-spring</artifactId>
                <version>1.4.2</version>
            </dependency>
    
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
    
            <!--导入thymeleaf依赖-->
            <dependency>
                <groupId>org.thymeleaf</groupId>
                <artifactId>thymeleaf-spring5</artifactId>
                <version>3.0.11.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.thymeleaf.extras</groupId>
                <artifactId>thymeleaf-extras-java8time</artifactId>
                <version>3.0.4.RELEASE</version>
            </dependency>
    
            <!--shiro-thymeleaf整合-->
            <dependency>
                <groupId>com.github.theborakompanioni</groupId>
                <artifactId>thymeleaf-extras-shiro</artifactId>
                <version>2.0.0</version>
            </dependency>
        </dependencies>
        
        <repositories>
            <repository>
                <id>aliyun-repos</id>
                <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
            </repository>
        </repositories>
        <pluginRepositories>
            <pluginRepository>
                <id>aliyun-plugin</id>
                <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
            </pluginRepository>
        </pluginRepositories>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
        
    </project>

     2. application.yml设置连接数据库的相关配置

    spring:
      datasource:
        username: root
        password: 123456
        #?serverTimezone=UTC解决时区的报错
        url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
        driver-class-name: com.mysql.cj.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource
    
        #Spring Boot 默认是不注入这些属性值的,需要自己绑定
        #druid 数据源专有配置
        initialSize: 5
        minIdle: 5
        maxActive: 20
        maxWait: 60000
        timeBetweenEvictionRunsMillis: 60000
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1 FROM DUAL
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        poolPreparedStatements: true
    
        #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
        #如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority
        #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
        filters: stat,wall,log4j
        maxPoolPreparedStatementPerConnectionSize: 20
        useGlobalDataSourceStat: true
        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

     3.index.html

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.w3.org/1999/xhtml?"
          xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro" >
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <h1>首页</h1>
    
        <div th:if="${session.loginUser==null}">
            <a th:href="@{/toLogin}">登录</a>
        </div>
    
        <p th:text="${msg}"></p>
    
        <hr>
        <div shiro:hasPermission="user:add">
            <a th:href="@{/user/add}">add</a>
        </div>
        <div shiro:hasPermission="user:update">
            <a th:href="@{/user/update}">update</a>
        </div>
    
        <a th:href="@{/logout}">注销</a>
    
    </body>
    </html>

    4.add.html和update.html

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

    5.login.html

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.w3.org/1999/xhtml?">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <h1>登录</h1>
    <hr>
    <p th:text="${msg}" style="color:red;"></p>
    <form th:action="@{/login}">
        用户名:<input type="text" name="username"><br>
        密码:<input type="password" name="password">
        <br>
        <input type="submit" name="提交">
    </form>
    </body>
    </html>

    6.MyController.java

    @Controller
    public class MyController {
    
        @RequestMapping({"/","/index"})
        public String toIndex(Model model){
            model.addAttribute("msg","hello,shiro!");
            return "index";
        }
    
        @RequestMapping("/user/add")
        public String add(){
            return "user/add";
        }
    
        @RequestMapping("/user/update")
        public String update(){
            return "user/update";
        }
    
        @RequestMapping("/toLogin")
        public String toLogin(){
            return "login";
        }
    
        @RequestMapping("/login")
        public String login(String username,String password,Model model){
            //获取当前用户
            Subject subject = SecurityUtils.getSubject();
            //封装用户的登录数据
            UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    
            try{
                subject.login(token); //执行登录的方法,如果没有异常就说明ok了
                return "index";
            }catch (UnknownAccountException e){ //用户名不存在
                model.addAttribute("msg","用户名不存在!");
                return "login";
            }catch (IncorrectCredentialsException e){
                model.addAttribute("msg","密码错误!");
                return "login";
            }
        }
    
        @RequestMapping("/noauth")
        @ResponseBody
        public String unauthorized(){
            return "未授权无法访问此页面";
        }
    
        @RequestMapping("/logout")
        public String logout(){
            //获取当前用户
            Subject subject = SecurityUtils.getSubject();
            System.out.println(subject.getSession().getAttribute("loginUser"));
            subject.logout(); // session 会销毁,在SessionListener监听session销毁,清理权限缓存
            System.out.println(subject.getSession().getAttribute("loginUser"));
            System.out.println("执行了退出");
            return "login";
        }
    }

    7.ShiroConfig.java

    @Configuration
    public class ShiroConfig {
    
        //ShiroFilterFactoryBean (第三步:连接到前端)
        @Bean
        public ShiroFilterFactoryBean getShiroFilterBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
            ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
            //设置安全管理器
            bean.setSecurityManager(defaultWebSecurityManager);
    
            //添加shiro的内置过滤器
            /*
            anon: 无需认证即可访问
            authc: 必须认证才能用
            user: 必须拥有 “记住我” 功能才能用
            perms: 拥有对某个资源的权限才能用
            role: 拥有某个角色权限才能访问
            */
    
            Map<String,String> filterMap = new LinkedHashMap<>();
            //拦截
            filterMap.put("/user/add","authc");
            filterMap.put("/user/update","authc");
            //也可使用通配符*
            //filterMap.put("/user/*","authc");
    
            //授权,正常情况下没有授权会跳转到未授权页面
            filterMap.put("/user/add","perms[user:add]");
            filterMap.put("/user/update","perms[user:update]");
    
            bean.setFilterChainDefinitionMap(filterMap);
    
            //若访问时用户未认证,则跳转至登录页面
            bean.setLoginUrl("/toLogin");
            //若访问时用户未被授权,则跳转至未授权页面
            bean.setUnauthorizedUrl("/noauth");
    
            return bean;
        }
    
        //DefaultWebSecurityManager (第二步:管理realm对象)
        @Bean(name="securityManager") //@Bean注解后便被spring托管,不加name属性,默认name值为方法名,这里就加一下吧
        public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            //关联UserRealm
            securityManager.setRealm(userRealm);
            return securityManager;
    
        }
    
        //创建realm对象,需要自定义类 (第一步:创建realm对象)
        @Bean(name="userRealm")  //@Bean注解后便被spring托管,不加name属性,默认name值为方法名,这里就加一下吧
        public UserRealm userRealm(){
            return new UserRealm();
        }
    
        //整合ShiroDialect:用来整合shiro thymeleaf
        @Bean
        public ShiroDialect getShiroDialect(){
            return new ShiroDialect();
        }
    }

    8.UserRealm.java

    //自定义UserRealm extends AuthorizingRealm
    public class UserRealm extends AuthorizingRealm {
    
        @Autowired
        UserService userService;
    
    
        //授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            System.out.println("执行了授权");
    
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            //info.addStringPermission("user:add");
    
            //拿到当前登录的对象
            Subject subject = SecurityUtils.getSubject();
            User currentUser = (User) subject.getPrincipal(); //拿到user对象
    
            info.addStringPermission(currentUser.getPerms());
    
            return info;
        }
    
        //认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            System.out.println("执行了认证");
    
            UsernamePasswordToken userToken = (UsernamePasswordToken) token;
    
            /*
            //用户名、密码  模拟从数据库中获取
            String name = "root";
            String password = "1111";
    
            if (!userToken.getUsername().equals(name)){
                return null; //抛出异常 UnknownAccountException
            }
    
            //密码认证,shiro做~
            return new SimpleAuthenticationInfo("",password,"");
            */
    
            //连接真实数据库
            User user = userService.queryUserByName(userToken.getUsername());
            if (user==null){ //没有这个人
                return null; //抛出异常 UnknownAccountException
            }
            Subject currentSubject = SecurityUtils.getSubject();
            Session session = currentSubject.getSession();
            session.setAttribute("loginUser",user);
    
            //可以加密: MD5加密   MD5盐值加密
            //密码认证,shiro做~
            return new SimpleAuthenticationInfo(user,user.getPwd(),"");
    
        }
    }

     9.UserMapper.java

    @Repository
    @Mapper
    public interface UserMapper {
    
        public User queryUserByName(String name);
    
    }

    10.UserMapper.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.ztx.mapper.UserMapper">
        <select id="queryUserByName" parameterType="String" resultType="User">
            select * from mybatis.user where name=#{name}
        </select>
    </mapper>

    11.User.java

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User {
    
        private int id;
        private String name;
        private String pwd;
        private String perms;
    }

    12.UserService.java和UserServiceImpl.java

    public interface UserService {
    
        public User queryUserByName(String name);
    
    }
    
    -----------------------------------------------------
    
    @Service
    public class UserServiceImpl implements UserService{
    
        @Autowired
        UserMapper userMapper;
    
        @Override
        public User queryUserByName(String name) {
            return userMapper.queryUserByName(name);
        }
    }

     13.数据库中的user表

    个人对该项目执行流程进行了梳理,如有错误,请指正:

      1.ShiroConf.java中设置好拦截器,规定哪些页面需要用户具备何种要求才可访问,同时还设置当用户不满足要求时应该跳转至什么页面。对应本项目的代码:

      

      2.用户在未认证下,会跳转到登陆页面login.html。输入用户名密码,提交执行controller中的login()方法,该方法内将用户信息封装成token对象,传入subject.login()方法内进行登录验证。对应项目代码:

       

      3.执行登录验证时会跳转到UserRealm类,首先执行认证(doGetAuthenticationInfo):连接数据库判断是否有此人,若没有则返回结果null,若有,则将数据库中的用户信息存储在subject的session中。接着进行密码验证,若验证失败,则将登录不通过返回失败认证信息,反之,返回成功的认证信息,并执行用户授权(doGetAuthorizationInfo)操作,返回授权信息。对应项目代码:

      认证:

       

      授权:

       

      4.本项目中登录成功后跳转到index.html时依旧会再次执行授权操作,因为前端页面需要一个判断展示功能,代码如下:

       

      故控制台信息会是这样:(出现两次授权)

       

      5.之后进入add.html或update.html时,因为会被拦截,所以都要进行授权验证,而此时认证将不会在执行。

     

  • 相关阅读:
    Android Layout XML属性
    linux]ubuntu挂载U盘
    Android之NDK开发
    Android 创建永不Kill的Service
    如何编写可移植的c/c++代码
    Android写日志文件类
    Android Activity去除标题栏和状态栏
    linux .bash_profile和.bashrc的什么区别
    ListView.setOnItemClickListener、setOnCreateContextMenuListener无效 为什么
    WCF 第四章 绑定 使用队列技术进行通信
  • 原文地址:https://www.cnblogs.com/churujianghudezai/p/12961525.html
Copyright © 2020-2023  润新知