zoukankan      html  css  js  c++  java
  • Spring Boot 整合 Shiro

    一、简介


    Shiro是Apache下一个开源的安全框架,提供了认证、授权、加密、会话管理,与 Spring Security 相比,Shiro 是一个轻量级框架,使用了比较简单易懂易于使用的授权方式。

    1、Shiro特性

    • Authentication:身份认证/登录,验证用户是不是拥有相应的身份;

    • Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;

    • Session Manager:会话管理,用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;

    • Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;

    • Web Support:Web支持,可以非常容易的集成到Web环境;

    • Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;

    • Concurrency:Shiro支持多线程应用的并发验证,如在一个线程中开启另一个线程,能把权限自动传播过去;

    • Testing:提供测试支持;

    • Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;

    • Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次访问就不用登录了。

    2、High-Level Overview 高级概述

    • Subject:当前用户,Subject 可以是一个人,也可以是第三方服务;

    • SecurityManager:管理所有Subject,SecurityManager 是 Shiro 架构的核心,配合内部安全组件共同组成安全伞;

    • Realms:用于进行权限信息的验证,我们自己实现,本质上是一个特定的安全 DAO,它封装与数据源连接的细节,得到 Shiro 所需的相关的数据,在配置 Shiro 的时候,必须指定至少一个Realm 来实现认证(Authentication)或授权(Authorization)。

    二、快速上手


    1、添加pom依赖

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-spring</artifactId>
      <version>1.4.0</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>net.sourceforge.nekohtml</groupId>
      <artifactId>nekohtml</artifactId>
      <version>1.9.22</version>
    </dependency>

    2、配置文件

    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/shiro?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
        username: root
        password: root
        driver-class-name: com.mysql.jdbc.Driver
      jpa:
        database: mysql
        show-sql: true
        hibernate:
          ddl-auto: update
          naming:
            physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
      thymeleaf:
        cache: false
        mode: HTML

    3、新建实体类

    用户信息

    @Entity
    public class SysUser implements Serializable {
        @Id
        @GeneratedValue
        private Integer uid;
        @Column(unique =true)
        private String username;//帐号
        private String name;//名称
        private String password; //密码;
        private String salt;//加密密码的盐
        private byte state;//用户状态,0:创建未认证, 1:正常状态,2:用户被锁定.
        @ManyToMany(fetch= FetchType.EAGER)//立即从数据库中进行加载数据;
        @JoinTable(name = "SysUserRole", joinColumns = { @JoinColumn(name = "uid") }, inverseJoinColumns ={@JoinColumn(name = "roleId") })
        private List<SysRole> roleList;// 一个用户具有多个角色
    
        public Integer getUid() {
            return uid;
        }
    
        public void setUid(Integer uid) {
            this.uid = uid;
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        public String getSalt() {
            return salt;
        }
    
        public void setSalt(String salt) {
            this.salt = salt;
        }
    
        public byte getState() {
            return state;
        }
    
        public void setState(byte state) {
            this.state = state;
        }
    
        public List<SysRole> getRoleList() {
            return roleList;
        }
    
        public void setRoleList(List<SysRole> roleList) {
            this.roleList = roleList;
        }
    
        public String getCredentialsSalt(){
            return this.username+this.salt;
        }
    }

    角色信息

    @Entity
    public class SysRole implements Serializable{
        @Id@GeneratedValue
        private Integer id; // 编号
        private String role; // 角色
        private String description; // 角色描述
        private Boolean available = Boolean.FALSE; // 是否可用
    
        //角色 -- 权限关系:多对多关系;
        @ManyToMany(fetch= FetchType.EAGER)
        @JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="permissionId")})
        private List<SysPermission> permissions;
    
        // 用户 - 角色关系定义;
        @ManyToMany
        @JoinTable(name="SysUserRole",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="uid")})
        private List<SysUser> userInfos;// 一个角色对应多个用户
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getRole() {
            return role;
        }
    
        public void setRole(String role) {
            this.role = role;
        }
    
        public String getDescription() {
            return description;
        }
    
        public void setDescription(String description) {
            this.description = description;
        }
    
        public Boolean getAvailable() {
            return available;
        }
    
        public void setAvailable(Boolean available) {
            this.available = available;
        }
    
        public List<SysPermission> getPermissions() {
            return permissions;
        }
    
        public void setPermissions(List<SysPermission> permissions) {
            this.permissions = permissions;
        }
    
        public List<SysUser> getUserInfos() {
            return userInfos;
        }
    
        public void setUserInfos(List<SysUser> userInfos) {
            this.userInfos = userInfos;
        }
    }

    权限信息

    @Entity
    public class SysPermission implements Serializable {
        @Id@GeneratedValue
        private Integer id;//主键.
        private String name;//名称.
        @Column(columnDefinition="enum('menu','button')")
        private String resourceType;//资源类型,[menu|button]
        private String url;//资源路径.
        private String permission; //权限字符串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:view
        private Long parentId; //父编号
        private String parentIds; //父编号列表
        private Boolean available = Boolean.FALSE;
        @ManyToMany
        @JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="permissionId")},inverseJoinColumns={@JoinColumn(name="roleId")})
        private List<SysRole> roles;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getResourceType() {
            return resourceType;
        }
    
        public void setResourceType(String resourceType) {
            this.resourceType = resourceType;
        }
    
        public String getUrl() {
            return url;
        }
    
        public void setUrl(String url) {
            this.url = url;
        }
    
        public String getPermission() {
            return permission;
        }
    
        public void setPermission(String permission) {
            this.permission = permission;
        }
    
        public Long getParentId() {
            return parentId;
        }
    
        public void setParentId(Long parentId) {
            this.parentId = parentId;
        }
    
        public String getParentIds() {
            return parentIds;
        }
    
        public void setParentIds(String parentIds) {
            this.parentIds = parentIds;
        }
    
        public Boolean getAvailable() {
            return available;
        }
    
        public void setAvailable(Boolean available) {
            this.available = available;
        }
    
        public List<SysRole> getRoles() {
            return roles;
        }
    
        public void setRoles(List<SysRole> roles) {
            this.roles = roles;
        }
    }

    4、Shiro配置

    @Configuration
    public class MyShiroConfig {
      @Bean
      public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        System.out.println("ShiroConfiguration.shirFilter()");
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //拦截器.
        Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
        // 配置不会被拦截的链接 顺序判断
        filterChainDefinitionMap.put("/static/**", "anon");
        //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
        filterChainDefinitionMap.put("/logout", "logout");
        //<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
        //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
        filterChainDefinitionMap.put("/**", "authc");
        // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 登录成功后要跳转的链接
        shiroFilterFactoryBean.setSuccessUrl("/index");
    
        //未授权界面;
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
      }
    
      /**
       * 凭证匹配器
       * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了)
       * @return
       */
      @Bean
      public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:使用MD5算法;
        hashedCredentialsMatcher.setHashIterations(2);//散列的次数,相当于 md5(md5(""));
        return hashedCredentialsMatcher;
      }
    
      @Bean
      public MyShiroRealm myShiroRealm(){
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return myShiroRealm;
      }
    
    
      @Bean
      public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        return securityManager;
      }
    
      /**
       *  开启shiro aop注解支持.
       *  使用代理方式;所以需要开启代码支持;
       * @param securityManager
       * @return
       */
      @Bean
      public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
      }
    
      @Bean(name="simpleMappingExceptionResolver")
      public SimpleMappingExceptionResolver
      createSimpleMappingExceptionResolver() {
        SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver();
        Properties mappings = new Properties();
        mappings.setProperty("DatabaseException", "databaseError");//数据库异常处理
        mappings.setProperty("UnauthorizedException","403");
        r.setExceptionMappings(mappings);
        r.setDefaultErrorView("error");
        r.setExceptionAttribute("ex");
        return r;
      }
    }
    public class MyShiroRealm extends AuthorizingRealm {
        @Resource
        private SysUserService sysUserService;
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
            SysUser sysUser  = (SysUser)principals.getPrimaryPrincipal();
            for(SysRole role:sysUser.getRoleList()){
                authorizationInfo.addRole(role.getRole());
                for(SysPermission p:role.getPermissions()){
                    authorizationInfo.addStringPermission(p.getPermission());
                }
            }
            return authorizationInfo;
        }
    
        /*主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。*/
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
                throws AuthenticationException {
            System.out.println("MyShiroRealm.doGetAuthenticationInfo()");
            //获取用户的输入的账号.
            String username = (String)token.getPrincipal();
            System.out.println(token.getCredentials());
            //通过username从数据库中查找 User对象,如果找到,没找到.
            //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
            SysUser sysUser = sysUserService.findByUsername(username);
            System.out.println("sysUser:   "+sysUser);
            if(sysUser == null){
                return null;
            }
            SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                    sysUser, //用户
                    sysUser.getPassword(), //密码
                    ByteSource.Util.bytes(sysUser.getCredentialsSalt()),//salt=username+salt
                    getName()  //realm name
            );
            return authenticationInfo;
        }
    }

    5、Dao层实现

    public interface SysUserDao extends CrudRepository<SysUser,Long> {
        public SysUser findByUsername(String username);
    }

    6、Service层实现

    public interface SysUserService {
        public SysUser findByUsername(String username);
    }
    @Service
    public class SysUserServiceImpl implements SysUserService {
        @Resource
        private SysUserDao sysUserDao;
        @Override
        public SysUser findByUsername(String username) {
            System.out.println("UserInfoServiceImpl.findByUsername()");
            return sysUserDao.findByUsername(username);
        }
    }

    7、Controller层实现

    @Controller
    @RequestMapping("/sysUser")
    public class SysUserController {
    
        /**
         * 用户查询.
         * @return
         */
        @RequestMapping("/query")
        @RequiresPermissions("sysUser:query")
        public String userList(){
            return "queryUser";
        }
    
        /**
         * 用户添加;
         * @return
         */
        @RequestMapping("/add")
        @RequiresPermissions("sysUser:add")
        public String addUser(){
            return "addUser";
        }
    
        /**
         * 用户删除;
         * @return
         */
        @RequestMapping("/del")
        @RequiresPermissions("sysUser:del")
        public String delUser(){
            return "delUser";
        }
    }
    @Controller
    public class HomeController {
        @RequestMapping({"/","/index"})
        public String index(){
            return"/index";
        }
    
        @RequestMapping("/login")
        public String login(HttpServletRequest request, Map<String, Object> map) throws Exception{
            System.out.println("HomeController.login()");
            // 登录失败从request中获取shiro处理的异常信息。
            // shiroLoginFailure:异常类的全类名.
            String exception = (String) request.getAttribute("shiroLoginFailure");
            System.out.println("exception=" + exception);
            String msg = "";
            if (exception != null) {
                if (UnknownAccountException.class.getName().equals(exception)) {
                    System.out.println("UnknownAccountException -- > 账号不存在");
                    msg = "UnknownAccountException -- > 账号不存在";
                } else if (IncorrectCredentialsException.class.getName().equals(exception)) {
                    System.out.println("IncorrectCredentialsException -- > 密码不正确");
                    msg = "IncorrectCredentialsException -- > 密码不正确";
                } else if ("kaptchaValidateFailed".equals(exception)) {
                    System.out.println("kaptchaValidateFailed -- > 验证码错误");
                    msg = "kaptchaValidateFailed -- > 验证码错误";
                } else {
                    msg = "other "+exception;
                    System.out.println("other -- >" + exception);
                }
            }else {
                msg = "";
            }
            map.put("msg", msg);
            // 此方法不处理登录成功,由shiro进行处理
            return "/login";
        }
    
        @RequestMapping("/403")
        public String unauthorizedRole(){
            System.out.println("403 -- > 没有权限");
            return "403";
        }
    }

    8、新建6个简单页面

    index.html(首页)

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>index</title>
    </head>
    <body>
    <h1>首页</h1>
    </body>
    </html>

    login.html(登录页)

    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>login</title>
    </head>
    <body>
    <form action="" method="post">
        <p>账号:<input type="text" name="username" value="admin"/></p>
        <p>密码:<input type="text" name="password" value="123456"/></p>
        <p><input type="submit" value="登录"/></p>
    </form>
    <div th:if="${msg != null && msg != ''}">
        错误信息:<h4 th:text="${msg}"></h4>
    </div>
    </body>
    </html>

    addUser.html(新增页)

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>addUser</title>
    </head>
    <body>
    <h1>新增界面</h1>
    </body>
    </html>

    delUser.html(删除页)

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>delUser</title>
    </head>
    <body>
    <h3>删除界面</h3>
    </body>
    </html>

    queryUser.html(查询页)

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>queryUser</title>
    </head>
    <body>
    <h1>查询界面</h1>
    </body>
    </html>

    403.html(无权限提示页面)

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>403</title>
    </head>
    <body>
    <h1>没有权限</h1>
    </body>
    </html>

    9、新增几条数据

    INSERT INTO `sys_user` (`uid`,`username`,`name`,`password`,`salt`,`state`) VALUES ('1', 'admin', '管理员', 'd3c59d25033dbf980d29554025c23a75', '8d78869f470951332959580424d4bf4f', 0);
    INSERT INTO `sys_permission` (`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`) VALUES (1,0,'查询用用',0,'0/','sysUser:query','menu','sysUser/query');
    INSERT INTO `sys_permission` (`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`) VALUES (2,0,'新增用户',1,'0/1','sysUser:add','button','sysUser/add');
    INSERT INTO `sys_permission` (`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`) VALUES (3,0,'删除用户',1,'0/1','sysUser:del','button','sysUser/del');
    INSERT INTO `sys_role` (`id`,`available`,`description`,`role`) VALUES (1,0,'管理员','admin');
    INSERT INTO `sys_role` (`id`,`available`,`description`,`role`) VALUES (3,1,'测试','test');
    INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (1,1);
    INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (2,1);
    INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (3,2);
    INSERT INTO `sys_user_role` (`role_id`,`uid`) VALUES (1,1);

    三、测试

    1、访问 http://localhost:8080/index ,由于未登录跳转到首页 http://localhost:8080/login
    2、点击登录,登录后再访问 http://localhost:8080/index 和 http://localhost:8080/sysUser/add 正常跳转;
    再访问 http://localhost:8080/sysUser/del,跳转到没有权限提示页面

    MyShiroRealm的doGetAuthorizationInfo方法进行权限校验

     

    参考:

    https://412887952-qq-com.iteye.com/blog/2299777

  • 相关阅读:
    codova 打包vue项目的坑
    vscode 开发wtl 笔记
    redis
    展开/收缩 ul
    ueditor
    xml
    NPOI
    滚动效果,有些浏览器不支持
    fileupload控件上传、文件下载
    excel函数
  • 原文地址:https://www.cnblogs.com/ityard/p/11147159.html
Copyright © 2011-2022 走看看