• 使用shiro安全管理


    之前介绍了springboot使用security进行权限管理,这篇文件介绍一下springboot使用shiro进行安全管理。

    简述本文的场景,本文使用springboot1.5.9+mysql+jpa+thymeleaf+shiro制作一个简单的验证,其中有2个角色,分别是admin和user,admin可以使用select和delete功能,user只能使用select功能。

    新建项目,加入shiro依赖,pom文件如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.dalaoyang</groupId>
        <artifactId>springboot_shiro</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <packaging>jar</packaging>
    
        <name>springboot_shiro</name>
        <description>springboot_shiro</description>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>1.5.9.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
    
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-jpa</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-thymeleaf</artifactId>
            </dependency>
            <dependency>
                <groupId>net.sourceforge.nekohtml</groupId>
                <artifactId>nekohtml</artifactId>
                <version>1.9.15</version>
            </dependency>
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-spring</artifactId>
                <version>1.4.0</version>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    
    </project>
    

    配置文件如下:

    ##端口号
    server.port=8888
    
    
    
    ##数据库配置
    ##数据库地址
    spring.datasource.url=jdbc:mysql://localhost:3306/shiro?characterEncoding=utf8&useSSL=false
    ##数据库用户名
    spring.datasource.username=root
    ##数据库密码
    spring.datasource.password=root
    ##数据库驱动
    spring.datasource.driver-class-name=com.mysql.jdbc.Driver
    
    
    ##validate  加载hibernate时,验证创建数据库表结构
    ##create   每次加载hibernate,重新创建数据库表结构,这就是导致数据库表数据丢失的原因。
    ##create-drop        加载hibernate时创建,退出是删除表结构
    ##update                 加载hibernate自动更新数据库结构
    ##validate 启动时验证表的结构,不会创建表
    ##none  启动时不做任何操作
    spring.jpa.hibernate.ddl-auto=update
    
    ##控制台打印sql
    spring.jpa.show-sql=true
    
    
    # 建议在开发时关闭缓存,不然没法看到实时页面
    spring.thymeleaf.cache=false
    ##去除thymeleaf的html严格校验
    spring.thymeleaf.mode=LEGACYHTML5
    

    创建了三个实体类,分别是
    SysUser(用户表)

    package com.dalaoyang.entity;
    
    import org.hibernate.validator.constraints.NotEmpty;
    
    import javax.persistence.*;
    import java.io.Serializable;
    import java.util.List;
    
    /**
     * @author dalaoyang
     * @Description
     * @project springboot_learn
     * @package com.dalaoyang.entity
     * @email yangyang@dalaoyang.cn
     * @date 2018/5/2
     */
    @Entity
    public class SysUser implements Serializable {
    
        @Id
        @GeneratedValue
        private Integer userId;
        @NotEmpty
        private String userName;
        @NotEmpty
        private String passWord;
    
        //多对多关系
        @ManyToMany(fetch= FetchType.EAGER)
        //急加载,加载一个实体时,定义急加载的属性会立即从数据库中加载
        //FetchType.LAZY:懒加载,加载一个实体时,定义懒加载的属性不会马上从数据库中加载
        @JoinTable(name = "SysUserRole", joinColumns = { @JoinColumn(name = "userId") },
                inverseJoinColumns ={@JoinColumn(name = "roleId") })
        private List<SysRole> roleList;// 一个用户具有多个角色
    
    
        public Integer getUserId() {
            return userId;
        }
    
        public void setUserId(Integer userId) {
            this.userId = userId;
        }
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName) {
            this.userName = userName;
        }
    
        public String getPassWord() {
            return passWord;
        }
    
        public void setPassWord(String passWord) {
            this.passWord = passWord;
        }
    
        public List<SysRole> getRoleList() {
            return roleList;
        }
    
        public void setRoleList(List<SysRole> roleList) {
            this.roleList = roleList;
        }
    }
    

    SysRole(角色表)

    package com.dalaoyang.entity;
    
    import org.hibernate.validator.constraints.NotEmpty;
    
    import javax.persistence.*;
    import java.io.Serializable;
    import java.util.List;
    
    /**
     * @author dalaoyang
     * @Description
     * @project springboot_learn
     * @package com.dalaoyang.entity
     * @email yangyang@dalaoyang.cn
     * @date 2018/5/2
     */
    @Entity
    public class SysRole implements Serializable {
    
        @Id
        @GeneratedValue
        private Integer roleId;
        private String roleName;
    
        //多对多关系
        @ManyToMany(fetch= FetchType.EAGER)
        @JoinTable(name="SysRoleMenu",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="menuId")})
        private List<SysMenu> menuList;
    
        //多对多关系
        @ManyToMany
        @JoinTable(name="SysUserRole",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="userId")})
        private List<SysUser> userList;// 一个角色对应多个用户
    
        public Integer getRoleId() {
            return roleId;
        }
    
        public void setRoleId(Integer roleId) {
            this.roleId = roleId;
        }
    
        public String getRoleName() {
            return roleName;
        }
    
        public void setRoleName(String roleName) {
            this.roleName = roleName;
        }
    
        public List<SysMenu> getMenuList() {
            return menuList;
        }
    
        public void setMenuList(List<SysMenu> menuList) {
            this.menuList = menuList;
        }
    
        public List<SysUser> getUserList() {
            return userList;
        }
    
        public void setUserList(List<SysUser> userList) {
            this.userList = userList;
        }
    }
    
    

    SysMenu(菜单表)

    package com.dalaoyang.entity;
    
    import javax.persistence.*;
    import java.io.Serializable;
    import java.util.List;
    
    /**
     * @author dalaoyang
     * @Description
     * @project springboot_learn
     * @package com.dalaoyang.entity
     * @email yangyang@dalaoyang.cn
     * @date 2018/5/2
     */
    @Entity
    public class SysMenu implements Serializable {
    
        @Id
        @GeneratedValue
        private Integer menuId;
        private String menuName;
    
        @ManyToMany
        @JoinTable(name="SysRoleMenu",joinColumns={@JoinColumn(name="menuId")},inverseJoinColumns={@JoinColumn(name="roleId")})
        private List<SysRole> roleList;
    
        public Integer getMenuId() {
            return menuId;
        }
    
        public void setMenuId(Integer menuId) {
            this.menuId = menuId;
        }
    
        public String getMenuName() {
            return menuName;
        }
    
        public void setMenuName(String menuName) {
            this.menuName = menuName;
        }
    
        public List<SysRole> getRoleList() {
            return roleList;
        }
    
        public void setRoleList(List<SysRole> roleList) {
            this.roleList = roleList;
        }
    }
    

    创建一个UserRepository用于查询用户信息:

    package com.dalaoyang.repository;
    
    import com.dalaoyang.entity.SysUser;
    import org.springframework.data.repository.CrudRepository;
    
    /**
     * @author dalaoyang
     * @Description
     * @project springboot_learn
     * @package com.dalaoyang.repository
     * @email yangyang@dalaoyang.cn
     * @date 2018/5/2
     */
    public interface UserRepository extends CrudRepository<SysUser,Long> {
    
        SysUser findByUserName(String username);
    }
    

    创建几个前台页面进行测试,分别是:
    login.html:

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
    <head>
        <meta charset="UTF-8">
        <title>Login</title>
    </head>
    <body>
    错误信息:<h4 th:text="${msg}"></h4>
    <form action="" method="post">
        <p>账号:<input type="text" name="username" value="dalaoyang"/></p>
        <p>密码:<input type="text" name="password" value="123"/></p>
        <p><input type="submit" value="登录"/></p>
    </form>
    </body>
    </html>
    

    index.html

    
    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    index
    <br/>
    <form th:action="@{/logout}" method="post">
        <p><input type="submit" value="注销"/></p>
    </form>
    </body>
    </html>
    

    delete.html

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

    select.html

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

    403.html

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

    创建一个ShiroConfig,代码如下:

    package com.dalaoyang.config;
    
    import org.apache.shiro.mgt.SecurityManager;
    import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
    import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
    import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
    
    import java.util.Date;
    import java.util.LinkedHashMap;
    import java.util.Map;
    import java.util.Properties;
    
    /**
     * @author dalaoyang
     * @Description
     * @project springboot_learn
     * @package com.dalaoyang.config
     * @email yangyang@dalaoyang.cn
     * @date 2018/5/2
     */
    @Configuration
    public class ShiroConfig {
        private final Logger logger = LoggerFactory.getLogger(this.getClass());
    
        @Bean
        public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
            logger.info("启动shiroFilter--时间是:" + new Date());
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            shiroFilterFactoryBean.setSecurityManager(securityManager);
            //shiro拦截器
            Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
            //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
            //<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->
    
            // 配置不被拦截的资源及链接
            filterChainDefinitionMap.put("/static/**", "anon");
            // 退出过滤器
            filterChainDefinitionMap.put("/logout", "logout");
    
            //配置需要认证权限的
            filterChainDefinitionMap.put("/**", "authc");
            // 如果不设置默认会自动寻找Web工程根目录下的"/login"页面,即本文使用的login.html
            shiroFilterFactoryBean.setLoginUrl("/login");
            // 登录成功后要跳转的链接
            shiroFilterFactoryBean.setSuccessUrl("/index");
    
            //未授权界面
            shiroFilterFactoryBean.setUnauthorizedUrl("/403");
            shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
            return shiroFilterFactoryBean;
        }
    
        //自定义身份认证Realm(包含用户名密码校验,权限校验等)
        @Bean
        public MyShiroRealm myShiroRealm(){
            MyShiroRealm myShiroRealm = new MyShiroRealm();
            return myShiroRealm;
        }
    
    
        @Bean
        public SecurityManager securityManager(){
            DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
            securityManager.setRealm(myShiroRealm());
            return securityManager;
        }
    
        //开启shiro aop注解支持,不开启的话权限验证就会失效
        @Bean
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
            AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
            authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
            return authorizationAttributeSourceAdvisor;
        }
    
        //配置异常处理,不配置的话没有权限后台报错,前台不会跳转到403页面
        @Bean(name="simpleMappingExceptionResolver")
        public SimpleMappingExceptionResolver
        createSimpleMappingExceptionResolver() {
            SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver();
            Properties mappings = new Properties();
            mappings.setProperty("DatabaseException", "databaseError");//数据库异常处理
            mappings.setProperty("UnauthorizedException","403");
            simpleMappingExceptionResolver.setExceptionMappings(mappings);  // None by default
            simpleMappingExceptionResolver.setDefaultErrorView("error");    // No default
            simpleMappingExceptionResolver.setExceptionAttribute("ex");     // Default is "exception"
            return simpleMappingExceptionResolver;
        }
    }
    

    在配置一个MyShiroRealm用于登录认证和授权认证,代码如下:

    package com.dalaoyang.config;
    
    import com.dalaoyang.entity.SysMenu;
    import com.dalaoyang.entity.SysRole;
    import com.dalaoyang.entity.SysUser;
    import com.dalaoyang.repository.UserRepository;
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authc.SimpleAuthenticationInfo;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.authz.SimpleAuthorizationInfo;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    
    import javax.annotation.Resource;
    
    /**
     * @author dalaoyang
     * @Description
     * @project springboot_learn
     * @package com.dalaoyang.config
     * @email yangyang@dalaoyang.cn
     * @date 2018/5/2
     */
    public class MyShiroRealm extends AuthorizingRealm {
    
        @Resource
        private UserRepository userRepository;
    
        //授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
            SysUser userInfo  = (SysUser)principals.getPrimaryPrincipal();
            for(SysRole role:userInfo.getRoleList()){
                authorizationInfo.addRole(role.getRoleName());
                for(SysMenu menu:role.getMenuList()){
                    authorizationInfo.addStringPermission(menu.getMenuName());
                }
            }
            return authorizationInfo;
        }
    
        //认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
                throws AuthenticationException {
            //获得当前用户的用户名
            String username = (String)token.getPrincipal();
            System.out.println(token.getCredentials());
            //根据用户名找到对象
            //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
            SysUser userInfo = userRepository.findByUserName(username);
            if(userInfo == null){
                return null;
            }
            //这里会去校验密码是否正确
            SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                    userInfo, //用户名
                    userInfo.getPassWord(),//密码
                    getName()
            );
            return authenticationInfo;
        }
    }
    

    最后新建一个controller,其中本文使用了2种验证权限的方法,select方法使用@RequiresPermissions("select")来验证用户是否具有select权限,delete方法使用@RequiresRoles("admin")来验证用户是否是admin,代码如下:

    package com.dalaoyang.controller;
    
    import org.apache.shiro.authc.IncorrectCredentialsException;
    import org.apache.shiro.authc.UnknownAccountException;
    import org.apache.shiro.authz.annotation.RequiresPermissions;
    import org.apache.shiro.authz.annotation.RequiresRoles;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import javax.servlet.http.HttpServletRequest;
    import java.util.Map;
    
    /**
     * @author dalaoyang
     * @Description
     * @project springboot_learn
     * @package com.dalaoyang.controller
     * @email yangyang@dalaoyang.cn
     * @date 2018/5/2
     */
    @Controller
    public class TestController {
    
        @GetMapping({"/","/index"})
        public String index(){
            return"index";
        }
    
        @GetMapping("/403")
        public String unauthorizedRole(){
            return "403";
        }
    
        @GetMapping("/delete")
        //@RequiresPermissions("delete")
        @RequiresRoles("admin")
        public String delete(){
            return "delete";
        }
    
        @GetMapping("/select")
        @RequiresPermissions("select")
        public String select(){
            return "select";
        }
    
        @RequestMapping("/login")
        public String login(HttpServletRequest request, Map<String, Object> map) throws Exception{
            System.out.println("HomeController.login()");
            // 登录失败从request中获取shiro处理的异常信息。
            // shiroLoginFailure:就是shiro异常类的全类名.
            String exception = (String) request.getAttribute("shiroLoginFailure");
            String msg = "";
            //根据异常判断错误类型
            if (exception != null) {
                if (UnknownAccountException.class.getName().equals(exception)) {
                    msg = "账号不存在";
                } else if (IncorrectCredentialsException.class.getName().equals(exception)) {
                    msg = "密码不正确";
                } else {
                    msg = "else >> "+exception;
                }
            }
            map.put("msg", msg);
            // 此方法不处理登录成功,由shiro进行处理
            return "/login";
        }
    
        @GetMapping("/logout")
        public String logout(){
            return "/login";
        }
    }
    

    为了方便测试,本人插入了几条初始数据,sql如下:

    INSERT INTO `shiro`.`sys_menu`(`menu_id`, `menu_name`) VALUES (1, 'add');
    INSERT INTO `shiro`.`sys_menu`(`menu_id`, `menu_name`) VALUES (2, 'delete');
    INSERT INTO `shiro`.`sys_menu`(`menu_id`, `menu_name`) VALUES (3, 'update');
    INSERT INTO `shiro`.`sys_menu`(`menu_id`, `menu_name`) VALUES (4, 'select');
    INSERT INTO `shiro`.`sys_role`(`role_id`, `role_name`) VALUES (1, 'admin');
    INSERT INTO `shiro`.`sys_role`(`role_id`, `role_name`) VALUES (2, 'user');
    INSERT INTO `shiro`.`sys_role_menu`(`role_id`, `menu_id`) VALUES (1, 1);
    INSERT INTO `shiro`.`sys_role_menu`(`role_id`, `menu_id`) VALUES (1, 2);
    INSERT INTO `shiro`.`sys_role_menu`(`role_id`, `menu_id`) VALUES (1, 3);
    INSERT INTO `shiro`.`sys_role_menu`(`role_id`, `menu_id`) VALUES (1, 4);
    INSERT INTO `shiro`.`sys_role_menu`(`role_id`, `menu_id`) VALUES (2, 4);
    INSERT INTO `shiro`.`sys_user`(`user_id`, `pass_word`, `user_name`) VALUES (1, '123', 'dalaoyang');
    INSERT INTO `shiro`.`sys_user`(`user_id`, `pass_word`, `user_name`) VALUES (2, '123', 'xiaoli');
    INSERT INTO `shiro`.`sys_user_role`(`role_id`, `user_id`) VALUES (1, 1);
    INSERT INTO `shiro`.`sys_user_role`(`role_id`, `user_id`) VALUES (2, 2);
    

    启动项目,我在这里就不一一截图了,口述一下,访问http://localhost:8888/select由于没有登录的原因,会自动跳转到http://localhost:8888/login,输入错误的用户名和密码会出现对应的提示。输入角色user的用户名xiaoli,密码123。访问http://localhost:8888/select页面会正常跳转,访问http://localhost:8888/delete会拦截到403页面。

    如果使用角色为admin的用户dalaoyang密码123登录,以上请求全可以正常访问。

    源码下载 :大老杨码云

    个人网站:https://dalaoyang.cn

  • 相关阅读:
    ChineseAlphabetUtil获取汉字首字母工具类
    RandomCodeUtil随机数工具类,随机生成数字、字母、数字字母组合、中文姓名
    ValidateUtil常用验证工具类,如手机、密码、邮箱等
    聊天项目
    日期
    字符串
    java中属性,set get 以及如何学习类的一些用法
    继承 多态 封装
    方法 属性 构造方法和包
    面向对象知识
  • 原文地址:https://www.cnblogs.com/dalaoyang/p/8981285.html
Copyright © 2020-2023  润新知