springboot集成shiro控制登录权限

Shiro入门:

ApacheShiro是一个功能强大且易于使用的Java安全框架,提供了认证,授权,加密,和会话管理。

Shiro有三大核心组件:

Subject:即当前用户,在权限管理的应用程序里往往需要知道谁能够操作什么,谁拥有操作该程序的权利,shiro中则需要通过Subject来提供基础的当前用户信息,Subject 不仅仅代表某个用户,与当前应用交互的任何东西都是Subject,如网络爬虫等。所有的Subject都要绑定到SecurityManager上,与Subject的交互实际上是被转换为与SecurityManager的交互。

SecurityManager:即所有Subject的管理者,这是Shiro框架的核心组件,可以把他看做是一个Shiro框架的全局管理组件,用于调度各种Shiro框架的服务。作用类似于SpringMVC中的DispatcherServlet,用于拦截所有请求并进行处理。

Realm:Realm是用户的信息认证器和用户的权限人证器,我们需要自己来实现Realm来自定义的管理我们自己系统内部的权限规则。SecurityManager要验证用户,需要从Realm中获取用户。可以把Realm看做是数据源。

本文重点在于在springboot项目中集成Shiro,不在于讲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>
    <modules>
        <module>web</module>
        <module>dao</module>
        <module>service</module>
        <module>common</module>
    </modules>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.5.RELEASE</version>
        <!--<version>2.1.2.RELEASE</version>-->
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.loan</groupId>
    <artifactId>supermarket</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>supermarket</name>
    <packaging>pom</packaging>

    <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</artifactId>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- springboot监控 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!-- 热部署 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
            <scope>runtime</scope>
        </dependency>

        <!-- 日志工具类 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
        </dependency>

        <!-- Mybatis -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.1</version>
        </dependency>

        <!-- SpringBoot集成thymeleaf模板 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <!--<dependency>-->
            <!--<groupId>org.springframework.boot</groupId>-->
            <!--<artifactId>spring-boot-starter-data-jpa</artifactId>-->
        <!--</dependency>-->

        <!-- mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!-- alibaba的druid数据库连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>

        <!-- 分页插件 -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.1.2</version>
        </dependency>

        <!--常用工具类 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.4</version>
        </dependency>

        <!--集合常用工具类 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
            <version>4.2</version>
        </dependency>

        <!-- json处理-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.54</version>
        </dependency>

        <!--验证码 -->
        <dependency>
            <groupId>com.github.penggle</groupId>
            <artifactId>kaptcha</artifactId>
            <version>2.3.2</version>
        </dependency>

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

        <dependency>
            <groupId>org.crazycake</groupId>
            <artifactId>shiro-redis</artifactId>
            <version>2.4.2.1-RELEASE</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>

            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>cobertura-maven-plugin</artifactId>
                <version>2.5.1</version>
                <executions>
                    <execution>
                        <phase>process-classes</phase>
                        <goals>
                            <goal>cobertura</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

这是我的项目中用到的所有依赖项,但是继承shiro,只需要用到:

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

数据库sql如下:

-- 权限表 --

CREATE TABLE permission (

pid int(11) NOT NULL AUTO_INCREMENT,

name VARCHAR(255) NOT NULL DEFAULT '',

url VARCHAR(255) DEFAULT '',

PRIMARY KEY (pid)

) ENGINE = InnoDB DEFAULT CHARSET = utf8;

INSERT INTO permission VALUES ('1', 'add', '');

INSERT INTO permission VALUES ('2', 'delete', '');

INSERT INTO permission VALUES ('3', 'edit', '');

INSERT INTO permission VALUES ('4', 'query', '');

-- 用户表 --

CREATE TABLE user(

uid int(11) NOT NULL AUTO_INCREMENT,

username VARCHAR(255) NOT NULL DEFAULT '',

password VARCHAR(255) NOT NULL DEFAULT '',

PRIMARY KEY (uid)

) ENGINE = InnoDB DEFAULT CHARSET = utf8;

INSERT INTO user VALUES ('1', 'admin', '123');

INSERT INTO user VALUES ('2', 'demo', '123');

-- 角色表 --

CREATE TABLE role(

rid int(11) NOT NULL AUTO_INCREMENT,

rname VARCHAR(255) NOT NULL DEFAULT '',

PRIMARY KEY (rid)

) ENGINE = InnoDB DEFAULT CHARSET = utf8;

INSERT INTO role VALUES ('1', 'admin');

INSERT INTO role VALUES ('2', 'customer');

-- 权限角色关系表 --

CREATE TABLE permission_role (

rid int(11) NOT NULL ,

pid int(11) NOT NULL ,

KEY idx_rid (rid),

KEY idx_pid (pid)

) ENGINE = InnoDB DEFAULT CHARSET = utf8;

INSERT INTO permission_role VALUES ('1', '1');

INSERT INTO permission_role VALUES ('1', '2');

INSERT INTO permission_role VALUES ('1', '3');

INSERT INTO permission_role VALUES ('1', '4');

INSERT INTO permission_role VALUES ('2', '1');

INSERT INTO permission_role VALUES ('2', '4');

-- 用户角色关系表 --

CREATE TABLE user_role (

uid int(11) NOT NULL ,

rid int(11) NOT NULL ,

KEY idx_uid (uid),

KEY idx_rid (rid)

) ENGINE = InnoDB DEFAULT CHARSET = utf8;

INSERT INTO user_role VALUES (1, 1);

INSERT INTO user_role VALUES (2, 2);

建好表如下图:

实体类如下:

User存在:1、唯一ID 2、用户名 3、密码 4、拥有的Role

public class User {

    private Integer uid;

    private String username;

    private String password;

    private Set<Role> roles = new HashSet<>();
}

Role存在:1、唯一ID 2、角色名 3、角色拥有的权限 4、拥有该角色的用户(可以不需要)

public class Role {

    private Integer rid;

    private String rname;

    private Set<Permission> permissions = new HashSet<>();

    private Set<User> users = new HashSet<>();
}

Permission存在:1、唯一ID 2、权限名 3、。。。

public class Permission {

    private Integer pid;

    private String name;

    private String url;
}

dao层如下:

@Repository
public interface UserDao {
    User findUserByUsername(@Param("username") String username);
}

service代码如下:

@Service
public class UserServiece {
    @Autowired
    private UserDao userDao;

    public List<User> getUser() {
        return userDao.queryUserList();
    }

    public List<User> queryUserByName(String name) {
        return userDao.queryUserByName(name);
    }

    public User findByUsername(String username) {
        return userDao.findUserByUsername(username);
    }
}

mapper.xml的文件中添加对应sql

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.loan.supermarket.dao.UserDao">
    <!-- Result Map-->
    <resultMap id="BaseResultMap" type="com.loan.supermarket.mapper.User">
        <result column="password" property="password"/>
        <result column="uid" property="uid"/>
        <result column="username" property="username"/>
        <collection property="roles" ofType="com.loan.supermarket.mapper.Role">
            <id property="rid" column="rid"/>
            <result property="rname" column="rname"/>
            <collection property="permissions" ofType="com.loan.supermarket.mapper.Permission">
                <id property="pid" column="pid"/>
                <result property="name" column="name"/>
                <result property="url" column="url"/>
            </collection>
        </collection>
    </resultMap>

    <select id="findUserByUsername" parameterType="com.loan.supermarket.mapper.User" resultMap="BaseResultMap">
      SELECT u.*, r.*, p.*
      FROM USER u
        INNER JOIN user_role ur ON ur.uid = u.uid
        INNER JOIN role r ON r.rid = ur.rid
        INNER JOIN permission_role pr ON pr.rid = r.rid
        INNER JOIN permission p ON pr.pid = p.pid
      WHERE u.username =  #{username}
    </select>
</mapper>

添加Shiro的配置文件:

import com.loan.supermarket.web.shiro.AuthRealm;
import com.loan.supermarket.web.utils.CredentialMatcher;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
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.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.LinkedHashMap;

@Configuration
public class ShiroConfiguration {
    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager manager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(manager);

        // 登陆界面
        bean.setLoginUrl("/login");

        // 成功登陆后的界面
        bean.setSuccessUrl("/index");

        // 没有权限访问的界面
        bean.setUnauthorizedUrl("/unauthorized");

        // 定制相关表单是否需要相关权限的设定,具体配置信息请看:Shiro-内置的FilterChain
        HashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();

        // index界面需要鉴权
        filterChainDefinitionMap.put("/index", "authc");

        // login、loginUser表单不需要验证
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/loginUser", "anon");
        filterChainDefinitionMap.put("/unauthorized", "anon");

        // admin表单需要角色 admin 才能访问
        filterChainDefinitionMap.put("/admin", "roles[admin]");

        // edit表单需要权限 edit 才能访问
        filterChainDefinitionMap.put("/edit", "perms[edit]");
        filterChainDefinitionMap.put("/druid/**", "anon");
        filterChainDefinitionMap.put("/**", "user");
        bean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return bean;
    }

    @Bean("securityManager")
    public SecurityManager securityManager(@Qualifier("authRealm") AuthRealm authRealm) {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(authRealm);
        return manager;
    }

    @Bean("authRealm")
    public AuthRealm authRealm() {
        AuthRealm authRealm = new AuthRealm();

        /**
         * shiro自带的MemoryConstrainedCacheManager作缓存只能用于本机,那么在集群时就无法使用,
         * 如果使用ehcache、redis等其他缓存,可以参考https://www.cnblogs.com/lic309/p/4072848.html
         */
        authRealm.setCacheManager(new MemoryConstrainedCacheManager());

        // 用com.mmall.demo2.CredentialMatcher中自定义的密码比较器对密码进行比较
        authRealm.setCredentialsMatcher(new CredentialMatcher());
        return authRealm;
    }

    // 把shiro和spring进行绑定
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

    // 开启自动代码,设置为true即可
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);
        return creator;
    }
}

编写控制类代码:

import com.loan.supermarket.mapper.User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpSession;

@Controller
public class TestController {
    private static final Logger log =
            LoggerFactory.getLogger(TestController.class);

    @RequestMapping("login")
    public String login() {
        return "login";
    }

    @RequestMapping("/index")
    public String index() {
        return "index";
    }

    @RequestMapping("/logout")
    public String logout() {
        // 先验证主体
        Subject subject = SecurityUtils.getSubject();
        if (subject != null) {
            subject.logout();
        }
        return "login";
    }

    @RequestMapping("unauthorized")
    public String unauthorized() {
        return "unauthorized";
    }

    @RequestMapping("/admin")
    @ResponseBody
    public String admin() {
        return "admin success";
    }

    @RequestMapping("/edit")
    @ResponseBody
    public String edit() {
        return "edit success";
    }

    @RequestMapping("/loginUser")
    public String loginUser(@RequestParam("username") String username,
                            @RequestParam("password") String password,
                            HttpSession session) {
        // 初始化这个用户的token
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);

        // 获取事件的主体
        Subject subject = SecurityUtils.getSubject();
        try {
            // 尝试登录
            subject.login(token);

            // 获取用户的全部信息
            User user = (User) subject.getPrincipal();

            // 用于界面输出
            session.setAttribute("user", user);
            log.info("{} is login !!", username);
            return "index";
        } catch (Exception e) {
            log.error("{} has something wrong !!", username);
            return "unauthorized";
        }
    }
}

Realm是shiro框架中需要自己开发逻辑的内容,作用是对用户和用户权限进行验证:

import com.loan.supermarket.mapper.Permission;
import com.loan.supermarket.mapper.Role;
import com.loan.supermarket.mapper.User;
import com.loan.supermarket.service.UserServiece;
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 java.util.ArrayList;
import java.util.List;
import java.util.Set;

public class AuthRealm extends AuthorizingRealm {
    @Autowired
    private UserServiece userServiece;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // 获取User用户
        User user = (User) principals.fromRealm(this.getClass().getName()).iterator().next();
        List<String> permissionList = new ArrayList<>();
        List<String> roleNameList = new ArrayList<>();
        Set<Role> roleSet = user.getRoles();
        if (roleSet != null) {
            for (Role role : roleSet) {
                roleNameList.add(role.getRname());
                Set<Permission> permissionSet = role.getPermissions();
                if (permissionSet != null) {
                    for (Permission permission : permissionSet) {
                        permissionList.add(permission.getName());
                    }
                }
            }
        }

        // 需要把角色和权限放入info中
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        // 权限设定
        info.addStringPermissions(permissionList);

        // 角色设定
        info.addRoles(roleNameList);
        return info;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
        String username = usernamePasswordToken.getUsername();
        User user = userServiece.findByUsername(username);
        if (null != user) {
            return new SimpleAuthenticationInfo(user, user.getPassword(), this.getClass().getName());
        } else {
            return new SimpleAuthenticationInfo();
        }
    }
}

配置文件中,对于Realm配置的时候会用到自己对于用户验证的匹配器:

import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;

//@Component
public class CredentialMatcher extends SimpleCredentialsMatcher
{
    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info)
    {
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
        String password = new String(usernamePasswordToken.getPassword());
        String dbPassword = (String) info.getCredentials();
        if(StringUtils.isNotBlank(password)&&StringUtils.isNotBlank(dbPassword)){
            return this.equals(password, dbPassword);
        }
        return false;
    }
}

由于这次我们把shiro基础配置直接放在代码中,所以application.properties文件可以沿用之前的文件内容,无需修改:

# 端口配置
server.port=8000

## 数据源配置
spring.datasource.url=jdbc:mysql://localhost:3306/shiro?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=
spring.datasource.driver-class-name=com.mysql.jdbc.Driver


# 初始化时建立物理连接的个数
spring.datasource.druid.initial-size=5
# 最大连接池数量
spring.datasource.druid.max-active=30
# 最小连接池数量
spring.datasource.druid.min-idle=5
# 获取连接时最大等待时间,单位毫秒
spring.datasource.druid.max-wait=60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.druid.time-between-eviction-runs-millis=60000
# 连接保持空闲而不被驱逐的最小时间
spring.datasource.druid.min-evictable-idle-time-millis=300000
# 用来检测连接是否有效的sql,要求是一个查询语句
spring.datasource.druid.validation-query=SELECT 1 FROM DUAL
# 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
spring.datasource.druid.test-while-idle=true
# 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
spring.datasource.druid.test-on-borrow=false
# 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
spring.datasource.druid.test-on-return=false
# 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
spring.datasource.druid.pool-prepared-statements=true
# 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。
spring.datasource.druid.max-pool-prepared-statement-per-connection-size=50
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计
spring.datasource.druid.filters=stat,wall
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
spring.datasource.druid.connection-properties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
# 合并多个DruidDataSource的监控数据
spring.datasource.druid.use-global-data-source-stat=true

# druid连接池监控
spring.datasource.druid.stat-view-servlet.login-username=admin
spring.datasource.druid.stat-view-servlet.login-password=123
# 排除一些静态资源,以提高效率
spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*

# mybatis
mybatis.type-aliases-package=com.loan.supermarket.mapper
mybatis.mapper-locations=classpath:mapping/*.xml
mybatis.configuration.map-underscore-to-camel-case=true

# THYMELEAF (ThymeleafAutoConfiguration)
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
# ;charset=<encoding> is added
#spring.thymeleaf.content-type=text/html
# set to false for hot refresh
spring.thymeleaf.cache=false

spring.devtools.restart.enabled=true

另外我们使用thymeleaf写几个用户登录相关的页面,用来验证功能:

index.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Home</title>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
</head>
<body>
<h1>欢迎登录 : <span th:text="${session.user.username}"></span></h1>
</body>
</html>

login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>登录</title>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
</head>
<body>
<h1>欢迎登录</h1>
<form action="/loginUser" method="post">
    <input type="text" name="username"><br/>
    <input type="password" name="password"><br/>
    <input type="submit" value="提交"><br/>
</form>

</body>
</html>

unauthorized.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Unauthorized</title>
    <meta charset="UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
</head>
<body>
<h1>Unauthorized!</h1>
</body>
</html>

代码传送门:https://github.com/bruceq/supermarket

撸完代码,我们开始展示Shiro功能的实现效果:

服务启动后,访问登录界面,输入admin用户的用户名和密码:

验证成功后,可直接跳转到index页面:

如果我们填写错误的用户名和密码,会跳转到unauthorized页面:

至此,代码和相关展示全部结束,另附学习Shiro时相关链接:

Shiro简介

使用Shiro实现权限验证

30分钟学会如何使用Shiro

在前后端分离的SpringBoot项目中集成Shiro权限框架

发布了60 篇原创文章 · 获赞 57 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/qixinbruce/article/details/87833477