zoukankan      html  css  js  c++  java
  • 使用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

  • 相关阅读:
    Java实现 LeetCode 69 x的平方根
    Java实现 LeetCode 68 文本左右对齐
    Java实现 LeetCode 68 文本左右对齐
    Java实现 LeetCode 68 文本左右对齐
    Java实现 LeetCode 67 二进制求和
    Java实现 LeetCode 67 二进制求和
    Java实现 LeetCode 67 二进制求和
    Java实现 LeetCode 66 加一
    Java实现 LeetCode 66 加一
    CxSkinButton按钮皮肤类
  • 原文地址:https://www.cnblogs.com/dalaoyang/p/8981285.html
Copyright © 2011-2022 走看看