SpringBoot Shiro Thymeleaf Mybatisplus 多数据源 快速搭建一个权限框架

  快速搭建一个使用springboot、shiro、Mybatisplus、thymeleaf、多数据源的权限框架

1.环境搭建

pom

  使用JDK1.8,引入了一些常使用的jar包,下附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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <version>0.1</version>
    <name>quickFrame</name>
    <description>quick shiro frame for Spring Boot</description>
    <packaging>jar</packaging>
    <groupId>quickFrame</groupId>
    <artifactId>quickFrame</artifactId>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <!-- Shiro安全框架 -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.4.1</version>
        </dependency>

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
            <version>1.4.1</version>
        </dependency>

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.1</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.thymeleaf/thymeleaf -->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf</artifactId>
            <version>3.0.11.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.github.theborakompanioni/thymeleaf-extras-shiro -->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</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-websocket</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.1.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.22</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.microsoft.sqlserver/mssql-jdbc -->
<!--        <dependency>
            <groupId>com.microsoft.sqlserver</groupId>
            <artifactId>mssql-jdbc</artifactId>
            <version>6.5.2.jre8-preview</version>
        </dependency>-->

        <!-- https://mvnrepository.com/artifact/org.dom4j/dom4j -->
        <dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.1.1</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/jaxen/jaxen -->
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.2.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.20</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.20</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
            <version>2.5.6</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
        </dependency>

        <!-- Need this to compile JSP -->
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.9</version>
        </dependency>

        <!-- 从request获取设备信息 -->
        <dependency>
            <groupId>eu.bitwalker</groupId>
            <artifactId>UserAgentUtils</artifactId>
            <version>1.21</version>
        </dependency>

        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.28</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.1.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.58</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

application.yml

spring:
  application:
    name: qucikFrame
  mvc:
    view:
      # 页面默认前缀目录
      # 响应页面默认后缀
      suffix: .html
      prefix: classpath:/templates/
    static-path-pattern: /static/**
  jackson:
    time-zone: GMT+8
    date-format: yyyy/MM/dd HH:mm:ss
  datasource:
    dynamic:
      druid: #以下是全局默认值,可以全局更改
        # 初始连接数
        initialSize: 5
        # 最小连接池数量
        minIdle: 5
        # 最大连接池数量
        maxActive: 20
        # 配置获取连接等待超时的时间
        maxWait: 60000
        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        timeBetweenEvictionRunsMillis: 60000
        # 配置一个连接在池中最小生存的时间,单位是毫秒
        minEvictableIdleTimeMillis: 300000
        # 配置一个连接在池中最大生存的时间,单位是毫秒
        maxEvictableIdleTimeMillis: 900000
        # 配置检测连接是否有效
        validationQuery: SELECT 1 FROM DUAL
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        poolPreparedStatements: true
        maxPoolPreparedStatementPerConnectionSize: 20
        filters: stat,wall # 注意这个值和druid原生不一致,默认启动了stat,wall
        stat:
          merge-sql: true
          log-slow-sql: true
          slow-sql-millis: 1000
        wall:
          multi-statement-allow: true
      datasource:
        master:
          username: root
          password: aion
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://localhost:3306/quick_frame?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false&useSSL=true
        ls:
          username: root
          password: aion
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://localhost:3306/quick_frame_two?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false&useSSL=true

# Logger Config
logging:
  level:
    com.cc.ata.a.mapper.*: debug
    org.springframework.web: debug
mybatis-plus:
  mapper-locations: classpath:/mapper/*Mapper.xml

server:
#  port: 8081
  servlet:
    context-path: /

2.Mybatisplus

  具体使用方法请查看Mybatisplus文档,很简易上手。

3.Shiro相关配置

ShiroConfig


import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;


/**
 * @author :cc
 * @date : 2019/6/10
 */
@Configuration
public class ShiroConfig {
    /**
     * 记住我:自动登录-1
     */
    @Bean
    public SimpleCookie getSimpleCookie() {
        SimpleCookie simpleCookie = new SimpleCookie();
        simpleCookie.setName("rememberMe");
        simpleCookie.setMaxAge(180 * 24 * 60 * 60);
        return simpleCookie;
    }

    /**
     * 记住我:自动登录-2
     */
    @Bean
    public CookieRememberMeManager getCookieRememberMeManager() {
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(getSimpleCookie());
        //rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
        cookieRememberMeManager.setCipherKey(Base64.decode("3qDVdLawoIr1xFd6ietnwg=="));
        return cookieRememberMeManager;

    }

//    /**
//     * 开启MD5加密
//     * @return
//     */
//    @Bean
//    public HashedCredentialsMatcher getMatcher(){
//        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
//        matcher.setHashAlgorithmName("md5");
//        matcher.setHashIterations(1);
//        return matcher;
//    }

    /**
     * 自定义Realm密码验证与加密
     *
     * @return
     */
    @Bean
    public CustomRealm getCustomRealm() {
        CustomRealm customRealm = new CustomRealm();
//        customRealm.setCredentialsMatcher(getMatcher());
        return customRealm;
    }

    /**
     * 创建SecurityManager环境
     *
     * @return
     */
    @Bean
    public DefaultWebSecurityManager getSecurityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(getCustomRealm());
        securityManager.setRememberMeManager(getCookieRememberMeManager());
        return securityManager;
    }

    /**
     * Shiro在Web项目中的过滤
     *
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean getfilterFactoryBean() {
        ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
        filterFactoryBean.setSecurityManager(getSecurityManager());
        /**
         *  只有在下面配置路径访问权限,Shiro才会执行自动跳转。
         *  如果使用Shiro注解权限,就只会报异常,
         *  就只能采用统一异常处理的方法。
         */
        //拦截器.
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        filterFactoryBean.setLoginUrl("/login");
        // 登录成功后要跳转的链接
        filterFactoryBean.setSuccessUrl("/index");
        //未授权界面;
        filterFactoryBean.setUnauthorizedUrl("/unauthc");
        // 配置不会被拦截的链接 顺序判断
        //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
        filterChainDefinitionMap.put("/logout", "logout");
        //<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
        //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/static/json/**", "user");
        filterChainDefinitionMap.put("/static/index.html", "user");
        filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/json/**", "anon");
        filterChainDefinitionMap.put("/**", "roles");

        filterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return filterFactoryBean;
    }

    /**
     * 保证Shiro的声明周期
     *
     * @return
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 开启Shiro授权生效
     *
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        return new AuthorizationAttributeSourceAdvisor();
    }

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

Realm

  CustomRealm


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.cc.ata.constant.Constant;
import com.cc.ata.entity.QUser;
import com.cc.ata.service.IQUserService;
import org.apache.shiro.authc.*;
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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

/**
 * @author :cc
 * @date : 2019/6/10
 */
public class CustomRealm extends AuthorizingRealm {
    private String ClassName = this.getClass().getName();

    @Autowired
    @Lazy   //必须懒加载,否则Ehcache缓存注解及事务管理注解无效
    private IQUserService userService;

    {
        super.setName(ClassName);
    }

    /**
     * 权限处理
     *
     * @param principals 用户名
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        Long id = (Long) principals.getPrimaryPrincipal();
        QUser user = userService.getById(id);
        // 从数据库或者缓存中获得角色数据 此处简化
        Set<String> roles = new HashSet<>();
        Set<String> permissions = new HashSet<>();
        if (Objects.equals(Constant.ROLE_ADMIN, user.getType())) {
            //如果是管理员
            roles.add(Constant.ROLE_ADMIN);
            roles.add(Constant.ROLE_USER);
        } else if (Objects.equals(Constant.ROLE_USER, user.getType())) {
            //为0 是普通用户
            roles.add(Constant.ROLE_USER);
        }
        //上面的service层方法需要自己写
        //permissions简化 只分用户类型
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.setStringPermissions(permissions);
        simpleAuthorizationInfo.setRoles(roles);
        return simpleAuthorizationInfo;
    }

    /**
     * 认证处理
     *
     * @param token 凭证
     * @throws AuthenticationException 异常
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 1.从主体传过来的认证信息中,获得用户名
        String email = (String) token.getPrincipal();
        String password = new String((char[]) token.getCredentials()).toLowerCase();
        QUser user;
        // 2.通过用户名到数据库中获取凭证
        user = userService.getOne(new QueryWrapper<>(new QUser().setEmail(email)));
        if (null == user) {
            throw new UnknownAccountException();
        }

        if (!Objects.equals(password, user.getPwd())) {
            throw new CredentialsException();
        }
        /*禁止登录时*/
        if (Objects.equals(Constant.LOGIN_FORBID, user.getFlag())) {
            throw new LockedAccountException();
        }
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        user.setLoginTime(sdf.format(new Date()));

        boolean b = userService.updateById(user);
        if (!b) {
            throw new RuntimeException();
        }
        return new SimpleAuthenticationInfo(user.getId(), password, ClassName);
    }
}

ShiroUtils

  更新shiro缓存 当传入的不是email,而是User对象时使用


import com.cc.ata.entity.QUser;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.Subject;

/*更新shiro缓存 当传入的不是email 而是QUser对象时使用*/
public class ShiroUtils {
    public static Subject getSubjct(){
        return SecurityUtils.getSubject();
    }

    public static QUser getUser(){
        return (QUser) getSubjct().getPrincipal();
    }
    public static void setUser(QUser user) {
        Subject subject = SecurityUtils.getSubject();
        PrincipalCollection principalCollection = subject.getPrincipals();
        String realmName = principalCollection.getRealmNames().iterator().next();
        PrincipalCollection newPrincipalCollection =
            new SimplePrincipalCollection(user, realmName);
        subject.runAs(newPrincipalCollection);
    }
}

4.登录

登录页面(仅供参考)

<!DOCTYPE html>
<html  xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <title>登录</title>
    <link rel="stylesheet" href="../static/assets/libs/layui/css/layui.css"/>
    <link rel="stylesheet" href="../static/assets/css/login.css?v=315">
    <link rel="stylesheet" href="../static/assets/module/admin.css?v=315">

    <script>
        if (window !== top) {
            top.location.replace(location.href);
        }
    </script>
    <style type="text/css">
        .top_header {
            height: 50%;
            background-repeat: no-repeat;

        }
    </style>
</head>
<body>
<div class="login-wrapper">
    <div style="height: 100px">

    </div>
    <div class="login-body" >
        <div class="layui-card">
            <div class="layui-card-header">
                    <i class="layui-icon layui-icon-engine" ></i>&nbsp;&nbsp;用户登录
            </div>
            <br>
            <form class="layui-card-body layui-form layui-form-pane" >
                <div class="layui-form-item">
                    <label class="layui-form-label"><i class="layui-icon layui-icon-username"></i></label>
                    <div class="layui-input-block">
                        <input name="email" type="text" placeholder="账号" class="layui-input"
                               lay-verType="tips" lay-verify="required" required/>
                    </div>
                </div>
                <div class="layui-form-item">
                    <label class="layui-form-label"><i class="layui-icon layui-icon-password"></i></label>
                    <div class="layui-input-block">
                        <input name="password" type="password" placeholder="密码" class="layui-input"
                               lay-verType="tips" lay-verify="required" required/>
                    </div>
                </div>

                <div class="layui-form-item">
                    <a id="registerUser" href="javascript:;" class="layui-link pull-right">帐号注册</a>
<!--                    <a href="javascript:;" class="layui-link pull-right">忘记密码?</a>-->
                </div>
                <div class="layui-form-item">
                    <button lay-filter="login-submit" class="layui-btn layui-btn-fluid" lay-submit>登 录</button>
                </div>
                <div class="layui-form-item login-other" style="height: 15px;text-align: center">
                    <p ><span style="font-weight: bold;color: #009688">Cc.  仅个人娱乐使用。</span></p>
                </div>
            </form>
        </div>
    </div>

    <div class="login-footer">
        <p>

        </p>
    </div>
</div>

<!-- js部分 -->
<script type="text/javascript" src="../static/assets/libs/layui/layui.js"></script>
<script type="text/javascript" src="../static/assets/js/common.js?v=315"></script>
<script>
    layui.use(['layer', 'form','admin','notice'], function () {
        var $ = layui.jquery;
        var layer = layui.layer;
        var form = layui.form;
        var notice = layui.notice;
        var admin = layui.admin;

        // 表单弹窗
        $('#registerUser').click(function () {
            parent.layui.admin.open({
                id: 'register',
                type: 2,
                title: '注册',
                shade: 0,
                area: ['360px', '287px'],
                content: getProjectUrl() + 'page/login/register.html'
            });
        });

        // 表单提交
        form.on('submit(login-submit)', function (obj) {
            // layer.msg(JSON.stringify(obj.field), {icon: 1,});
            $('login-submit').attr('disabled', 'true');
            admin.req('/login', {
                email: obj.field.email,
                password: obj.field.password
            }, function (res) {
                // alert(res.data + '-' + res.msg);
                if (res.code === SUCCESS_CODE) {
                    noticeSuccess(res.msg);
                    window.location.replace("/index");
                }  else {
                    $('login-submit').attr('disabled', 'false');
                    noticeError(res.msg);
                }
            }, 'post');
            return false;
        });

    });
</script>
</body>
</html>

/login

    @RequestMapping(value = "/login", method = RequestMethod.POST)
    @ResponseBody
    public RestBean login(@RequestParam(name = "email") String email,
                          @RequestParam(name = "password") String password) {
        ServerSet server = serverService.getById(1);
        if (Objects.equals(Constant.LOGIN_FORBID, server.getLoginFlag())) {
            return new RestBean(Constant.FAIL_CODE, "服务器暂时禁止登录!");
        }
        Subject subject = SecurityUtils.getSubject();
        // 在认证提交前准备 token(令牌)
        UsernamePasswordToken token = new UsernamePasswordToken(email, password);
        // 执行认证登陆
        try {
            token.setRememberMe(true);//记住我
            subject.login(token);
        } catch (LockedAccountException lae) {
            return new RestBean(Constant.FAIL_CODE, "账户已锁定!");
        } catch (ExcessiveAttemptsException eae) {
            return new RestBean(Constant.FAIL_CODE, "输入错误次数过多!");
        } catch (CredentialsException ice) {
            return new RestBean(Constant.FAIL_CODE, "邮箱或密码错误!");
        } catch (RuntimeException re) {
            return new RestBean(Constant.FAIL_CODE, "系统错误!");
        }
        if (subject.isAuthenticated()) {
            return new RestBean(Constant.SUCCESS_CODE, "登录成功!");
        } else {
            token.clear();
            return new RestBean(Constant.FAIL_CODE, "邮箱或密码错误!");
        }
    }

5.shiro标签

  在页面上使用shiro控制权限

1.在Thymeleaf中使用需要引入jar包

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

2.在html标签引入

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

3.使用举例
使用标签<shiro:xxxxx></shiro>

   <shiro:hasRole name="ROLE_USER">
      <li class="layui-nav-item">
      <a lay-href="/welcome"><i class="layui-icon layui-icon-home"></i>&emsp;<cite>首页</cite></a>
      </li>
   </shiro:hasRole>

6.Demo查看

此处内容需要评论回复后(审核通过)方可阅读。




版权声明:本文为原创文章,版权归 CC 所有。

本文链接:https://wzmoe.com/index.php/archives/42/

所有原创文章采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。
您可以自由的转载和修改,但请务必注明文章来源并且不可用于商业目的。
Last modification:November 11th, 2019 at 08:12 pm
如果觉得我的文章对你有用,请随意赞赏

One comment

  1. pllthxh

    six six six

Leave a Comment