2023-03-11
原文作者:柒's Blog 原文地址:https://blog.52itstyle.vip/archives/1304/

202303111617438871.png

前言

为了管理我的小黄图,特别安排了一个后台管理系统,权限管理当然是首选 Shiro 了。

表设计

至少需要以下几张表,具体如何设计要视自己的业务而定:

  • sys_user:用户表
  • sys_role:角色表
  • sys_menu:菜单表
  • sys_user_role:用户对应角色
  • sys_role_menu:用户对应菜单

整合

pom.xml引入:

    <dependency>
       <groupId>org.apache.shiro</groupId>
       <artifactId>shiro-spring-boot-web-starter</artifactId>
       <version>1.4.2</version>
    </dependency>

定义 UserRealm

    /**
     * 用户认证
     */
    public class UserRealm extends AuthorizingRealm {
    
        @Autowired
        private SysUserService userService;
    
        /**
         * 获取授权
         * @param principalCollection
         * @return
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            Long userId = ShiroUtils.getUserId();
            List<String> rolesSet = userService.listUserRoles(userId);
            List<String> permsSet = userService.listUserPerms(userId);
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            info.setRoles(new HashSet<>(rolesSet));
            info.setStringPermissions(new HashSet<>(permsSet));
            return info;
        }
    
        /**
         * 获取认证
         * @param authenticationToken
         * @return
         * @throws AuthenticationException
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
                throws AuthenticationException {
            String username = (String) authenticationToken.getPrincipal();
            String password = new String((char[]) authenticationToken.getCredentials());
            SysUser user = userService.getUser(username);
            if (user == null) {
                throw new UnknownAccountException("账户不存在");
            }
            if(!password.equals(user.getPassword())) {
                throw new IncorrectCredentialsException("密码不正确");
            }
            return new SimpleAuthenticationInfo(user, password, getName());
        }
    }

定义 ShiroConfig

    /**
     * Shiro权限配置
     */
    @Configuration
    public class ShiroConfig {
    
        @Bean
        public UserRealm userRealm() {
            return new UserRealm();
        }
    
        @Bean
        public ShiroFilterFactoryBean shiroFilterFactoryBean (SecurityManager securityManager) {
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            // 必须设置SecuritManager
            shiroFilterFactoryBean.setSecurityManager(securityManager);
            //设置登录页
            shiroFilterFactoryBean.setLoginUrl("/login.shtml");
            // 登录成功后要跳转的链接,如果是ajax这里无效
            shiroFilterFactoryBean.setSuccessUrl("/index");
            // 未授权界面;
            shiroFilterFactoryBean.setUnauthorizedUrl("/403");
            // 拦截器
            Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
            // 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边
            // authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
            /**
             * 静态文件
             */
            filterChainDefinitionMap.put("/css/**","anon");
            filterChainDefinitionMap.put("/images/**","anon");
            filterChainDefinitionMap.put("/js/**","anon");
            filterChainDefinitionMap.put("/file/**","anon");
            /**
             * 登录注册
             */
            filterChainDefinitionMap.put("/register.shtml","anon");
            filterChainDefinitionMap.put("/login.shtml","anon");
            filterChainDefinitionMap.put("/sys/logout","anon");
            filterChainDefinitionMap.put("/sys/login","anon");
            filterChainDefinitionMap.put("/sys/register","anon");
            /**
             * 管理后台
             */
            filterChainDefinitionMap.put("/sys/**", "roles[admin]");
            filterChainDefinitionMap.put("/**", "authc");
            shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
            return shiroFilterFactoryBean;
        }
    
        @Bean
        public SessionsSecurityManager securityManager() {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            securityManager.setRealm(userRealm());
            securityManager.setSessionManager(sessionManager());
            return securityManager;
        }
        @Bean
        public DefaultWebSessionManager sessionManager() {
            DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
            //去掉小尾巴 JSESSIONID
            sessionManager.setSessionIdUrlRewritingEnabled(false);
            long time = 2*60*60*1000;
            sessionManager.setGlobalSessionTimeout(time);
            return sessionManager;
        }
    }

登录 LoginController

    /**
     * 登录
     */
    @Controller
    @RequestMapping("/sys")
    public class LoginController {
    
        private static final Logger logger = LoggerFactory.getLogger(LoginController.class);
    
        @Autowired
        private SysUserService sysUserService;
    
        /**
         * 登录
         */
        @PostMapping("/login")
        @ResponseBody
        public Result login(String username, String password){
            logger.info("用户登录");
            try{
                Subject subject = ShiroUtils.getSubject();
                password = MD5Utils.encrypt(username, password);
                UsernamePasswordToken token = new UsernamePasswordToken(username, password);
                subject.login(token);
            }catch (Exception e) {
                e.printStackTrace();
                return Result.error("登录失败");
            }
            return Result.ok("登录成功");
        }
    }

过滤器

shiro提供和多个默认的过滤器,我们可以用这些过滤器来配置过滤指定url的访问权限,这里只介绍几个比较常用的。

配置缩写 对应的过滤器 功能描述
anon AnonymousFilter 指定url可以匿名访问
logout LogoutFilter 登出过滤器,配置指定url就可以实现退出功能,非常方便
perms PermissionsAuthorizationFilter 需要指定权限才能访问
roles RolesAuthorizationFilter 需要指定角色才能访问
user UserFilter 需要已登录或“记住我”的用户才能访问

shiro常用的权限控制注解,可以在控制器类上使用。

注解 功能
@RequiresGuest 只有游客可以访问
@RequiresAuthentication 需要登录才能访问
@RequiresUser 已登录的用户或“记住我”的用户能访问
@RequiresRoles 已登录的用户需具有指定的角色才能访问
@RequiresPermissions 已登录的用户需具有指定的权限才能访问

前端使用

过滤器实现了后端的权限控制访问,那么如何在前端来使用注解实现按钮的显示呢。thymeleafSpringBoot 的标配模板了,这里我们使用第三方插件来实现。

pom.xml引入:

    <dependency>
        <groupId>com.github.theborakompanioni</groupId>
        <artifactId>thymeleaf-extras-shiro</artifactId>
        <version>2.0.0</version>
    </dependency>

ShiroConfig 追加以下代码:

    @Bean
    public ShiroDialect shiroDialect(){
        return new ShiroDialect();
    }

页面头部引入:

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org"
                    xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">

页面使用:

    <!-- 显示登陆用户昵称 -->
    <shiro:principal property="nickname"/>
    
    <!-- 游客 -->
    <p shiro:guest="">Please <a href="login.html">login</a></p>
    
    <!-- 认证通过或已记住的用户。 -->
    <p shiro:user="">
    Welcome back John! Not John? Click <a href="login.html">here</a> to login.
    </p>
    
    <!-- 已认证通过的用户。不包含已记住的用户,这是与user标签的区别所在。 -->
    <p shiro:authenticated="">
    Hello, <span shiro:principal=""></span>, how are you today?
    </p>
    
    <a shiro:authenticated="" href="updateAccount.html">Update your contact information</a>
    
    <!-- 输出当前用户信息,通常为登录帐号信息。 -->
    <p>Hello, <shiro:principal/>, how are you today?</p>
    
    <!-- 未认证通过用户,与authenticated标签相对应。与guest标签的区别是,该标签包含已记住用户。 -->
    <p shiro:notAuthenticated="">
    Please <a href="login.html">login</a> in order to update your credit card information.
    </p>
    
    <!-- 验证当前用户是否属于该角色。 -->
    <a shiro:hasRole="admin" href="admin.html">Administer the system</a><!-- 拥有该角色 -->
    
    <!-- 与hasRole标签逻辑相反,当用户不属于该角色时验证通过。 -->
    <p shiro:lacksRole="developer"><!-- 没有该角色 -->
    Sorry, you are not allowed to developer the system.
    </p>
    
    <!-- 验证当前用户是否属于以下所有角色。 -->
    <p shiro:hasAllRoles="developer, 2"><!-- 角色与判断 -->
    You are a developer and a admin.
    </p>
    
    <!-- 验证当前用户是否属于以下任意一个角色。 -->
    <p shiro:hasAnyRoles="admin, vip, developer,1"><!-- 角色或判断 -->
    You are a admin, vip, or developer.
    </p>
    
    <!--验证当前用户是否拥有指定权限。 -->
    <a shiro:hasPermission="userInfo:add" href="createUser.html">添加用户</a><!-- 拥有权限 -->
    
    <!-- 与hasPermission标签逻辑相反,当前用户没有制定权限时,验证通过。 -->
    <p shiro:lacksPermission="userInfo:del"><!-- 没有权限 -->
    Sorry, you are not allowed to delete user accounts.
    </p>
    
    <!-- 验证当前用户是否拥有以下所有角色。 -->
    <p shiro:hasAllPermissions="userInfo:view, userInfo:add"><!-- 权限与判断 -->
    You can see or add users.
    </p>
    
    <!-- 验证当前用户是否拥有以下任意一个权限。 -->
    <p shiro:hasAnyPermissions="userInfo:view, userInfo:del"><!-- 权限或判断 -->
    You can see or delete users.
    </p>
    <a shiro:hasPermission="pp" href="createUser.html">Create a new User</a>
阅读全文