SpringBoot+Shiro权限框架

[TOC]

本文主要记录Spring Boot与Shiro进行整合使用,实现强大的用户权限管理,其中涉及如何完成用户认证(即用户登录),用户授权,thymeleaf页面整合shiro权限标签等知识点。

Spring Boot与Shiro框架简介

Spring Boot框架简介

Spring的诞生是 Java 企业版(Java Enterprise Edition,JEE,也称 J2EE)的

轻量级代替品。无需开发重量级的 Enterprise JavaBean(EJB),Spring 为企业级

Java 开发提供了一种相对简单的方法,通过依赖注入和面向切面编程,用简单的Java 对象(Plain Old Java Object,POJO)实现了 EJB 的功能。

虽然 Spring 的组件代码是轻量级的,但它的配置却是重量级的。

所有Spring配置都代表了开发时的损耗。 因为在思考 Spring 特性配置和解决业务问题之间需要进行思维切换,所以写配置挤占了写应用程序逻辑的时间。除此之外,项目的依赖管理也是件吃力不讨好的事情。决定项目里要用哪些库就已经够让人头痛的了,你还要知道这些库的哪个版本和其他库不会有冲突,这难题实在太棘手。并且,依赖管理也是一种损耗,添加依赖不是写应用程序代码。一旦选错了依赖的版本,随之而来的不兼容问题毫无疑问会是生产力杀手。

Spring Boot 让这一切成为了过去。

Spring Boot 简化了基于Spring的应用开发,只需要“run”就能创建一个独立的、生产级别的Spring应用。Spring Boot为Spring平台及第三方库提供开箱即用的设置(提供默认设置),这样我们就可以简单的开始。多数Spring Boot应用只需要很少的Spring配置。

我们可以使用SpringBoot创建java应用,并使用java –jar 启动它,或者采用传统的war部署方式。

Spring Boot 主要目标是:

l 为所有 Spring 的开发提供一个从根本上更快的入门体验。

l 开箱即用,但通过自己设置参数,即可快速摆脱这种方式。

l 提供了一些大型项目中常见的非功能性特性,如内嵌服务器、安全、指标,健康检测、外部化配置等。

l 绝对没有代码生成,也无需 XML 配置。

Shiro框架简介

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

1、 Authentication 认证 ---- 用户登录

2、 Authorization 授权 --- 用户具有哪些权限

3、 Cryptography 安全数据加密

4、 Session Management 会话管理

5、 Web Integration web系统集成

6、 Interations 集成其它应用,spring、缓存框架

Spring Boot与Shiro整合实现用户认证

分析Shiro的核心API

Subject: 用户主体(把操作交给SecurityManager)

SecurityManager:安全管理器(关联Realm)

Realm:Shiro连接数据的桥梁

Spring Boot整合Shiro

导入shiro与spring整合依赖

修改pom.xml

<!-- shiro与spring整合依赖 -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>

自定义Realm类

package cn.sitcat.springbootshiro.configs;

import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

/**
 * @author Hiseico
 * @date 2019/4/13 18:07
 * @desc 自定义Realm
 */
public class UserRealm extends AuthorizingRealm {
    /**
     * 执行授权逻辑
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行授权:Authorization");
        return null;
    }

    /**
     * 执行认证逻辑
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行认证:Authentiction");
    }
}

编写Shiro配置类

package cn.sitcat.springbootshiro.configs;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

/**
 * @author hiseico
 * @date 2019/4/13 17:58
 * @desc Shiro的配置类
 */
//声明该类为配置类
@Configuration
public class ShiroConfig {
    /**
     * 创建ShiroFilterFactoyBean
     */
    @Bean(name = "shiroFilterFactoryBean")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("webSecurityManager") DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        //添加shiro内置过滤器
        /**
         * shiro内置过滤器,可以实现权限的拦截器
         * 常用的过滤器:
         *  anon:无需认证(登录)可以访问
         *  authc:必须认证才可以访问
         *  user:如果使用remeberMe的功能可以直接访问
         *  perms:该资源必须得到资源权限才可以访问
         *  role:该资源必须得到角色权限才可以访问
         */
        Map<String, String> filterMap = new LinkedHashMap<>();
       /* filterMap.put("/add","authc");
        filterMap.put("/update","authc");
        */
        //使用统配的过滤器拦截多个url
        filterMap.put("/login", "anon");
        filterMap.put("/test", "anon");
        filterMap.put("/*", "authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);

        //修改拦截后跳转的url
        shiroFilterFactoryBean.setLoginUrl("/login");

        return shiroFilterFactoryBean;
    }


    /**
     * 创建DefaultWebSecurityManager
     *
     * @Qualifier 从ioc容器中找到这个对象
     */
    @Bean(name = "webSecurityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("realm") UserRealm userRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    /**
     * 创建Realm
     * 使用@Bean将该方法的返回值放到IOC容器中
     */
    @Bean(name = "realm")
    public UserRealm getShiroRealm() {
        return new UserRealm();
    }
}

实现用户认证(登录)操作

登录页面

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body style="margin:0 auto">
<h3><font color="red" th:text="${msg}"></font></h3>
<form action="login" method="post">
    用户名:<input type="text" name="name"/><br>
    密码:<input type="password" name="password"/><br>
    <input type="submit" value="登录"></input>

</form>
</body>
</html>

Controller登录逻辑

package cn.sitcat.springbootshiro.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;


/**
 * @author liusong
 * @date 2019/4/13 17:40
 * @desc
 */
@Controller
public class HelloController {
    @RequestMapping("/test")
    public String hello(Model model) {
        model.addAttribute("msg", "springboot project created success !");
        return "success";
    }

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

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

    /**
     * 登录操作
     *
     * @param name
     * @param password
     * @param model
     * @return
     */
    @RequestMapping("/login")
    public String login(String name, String password, Model model) {
        /**
         * 使用shiro编写认证的操作
         */
        //1.获取Subject
        Subject subject = SecurityUtils.getSubject();
        //2.封装用户数据
        UsernamePasswordToken token = new UsernamePasswordToken(name, password);
        try {
//3.执行登录方法
            subject.login(token);

        } catch (UnknownAccountException e) {
            //登录失败,用户名错误
            model.addAttribute("msg", "用户名不存在!");
            return "login";
        } catch (IncorrectCredentialsException e) {
            //登录失败,密码错误
            model.addAttribute("msg", "用户名或密码错误!");
            return "login";
        }
        return "redirect:/test";
    }
}

实现Realm的判断逻辑

package cn.sitcat.springbootshiro.configs;

import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

/**
 * @author Hiseico
 * @date 2019/4/13 18:07
 * @desc 自定义Realm
 */
public class UserRealm extends AuthorizingRealm {
    /**
     * 执行授权逻辑
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行授权:Authorization");
        return null;
    }

    /**
     * 执行认证逻辑
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行认证:Authentiction");

        //假设数据库的用户名和密码
        String name = "admin";
        String password = "admin";

        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        if (!name.equals(token.getUsername())) {
            //用户不存在
            return null; // shiro底层会抛出UnknowAccountException
        }


        //判断密码
        return new SimpleAuthenticationInfo("", password, "");
    }
}

整合MyBatis实现登录

导入mybatis相关的依赖

    <!--打开数据库连接相关依赖 使用阿里的druid连接池-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.0.9</version>
    </dependency>
    <!-- mysql -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <!-- SpringBoot的Mybatis启动器 -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>1.3.1</version>
    </dependency>

创建用户表

-- 创建用户表
CREATE TABLE `user`(
 `id` BIGINT(20) NOT NULL PRIMARY KEY AUTO_INCREMENT,
    `name` varchar(200),
    `password` varchar(200)
);

在Springboot中配置相关的JDBC连接、连接池及Mybatis包扫描

# 配置数据源
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

#jdbc url
spring.datasource.url=jdbc:mysql:///shiro

# 连接数据库用户名和密码
spring.datasource.username=root
spring.datasource.password=root

# 设置连接池类型
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

# 配置MyBatis包扫描
mybatis.type-aliases-package=cn.sitcat.springbootshiro.dao

编写User实体

package cn.sitcat.springbootshiro.model;

/**
 * @author hiseico
 * @date 2019/4/17 21:16
 * @desc
 */
public class User {
    private Long id;

    private String name;

    private String password;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    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;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

UserMapper

package cn.sitcat.springbootshiro.dao;

import cn.sitcat.springbootshiro.model.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Component;

@Component
@Mapper
public interface UserMapper{
    @Select("select * from User where name = #{name}")
    User selectUserByName(@Param("name") String name);

}

UserService

package cn.sitcat.springbootshiro.service;

import cn.sitcat.springbootshiro.model.User;

/**
 * @author hiseico
 * @date 2019/4/17 21:40
 * @desc
 */
public interface UserService {
    /**
     * 根据用户名查询用户信息
     * @param name
     * @return
     */
    User getUserByName(String name);
}

这里可以使用@Mapper注解直接注入Mapper,也可以在SpringBoot启动类上加包扫描注解@MapperScan("cn.sitcat.springbootshiro.dao")

UserServiceImpl

package cn.sitcat.springbootshiro.service;

import cn.sitcat.springbootshiro.dao.UserMapper;
import cn.sitcat.springbootshiro.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @author hiseico
 * @date 2019/4/17 21:40
 * @desc
 */
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    /**
     * 根据用户名查询用户信息
     *
     * @param name
     * @return
     */
    @Override
    public User getUserByName(String name) {
        return userMapper.selectUserByName(name);
    }
}

修改UserRealm的认证方法 从数据库查询用户信息

    /**
     * 执行认证逻辑
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行认证:Authentiction");

        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

        User user = userService.getUserByName(token.getUsername());
        if (user == null) {
            //用户不存在
            return null; // shiro底层会抛出UnknowAccountException
        }

        //判断密码
        return new SimpleAuthenticationInfo("", user.getPassword(), "");
    }

SpringBoot与Shiro整合实现用户授权

使用Shiro内置过滤器拦截资源

实例需求:给添加和更新页面增加用户权限,当用户有相应页面的权限才能访问。
修改ShiroConfig配置类中的ShiroFilterFactoyBean的内容

    /**
     * 创建ShiroFilterFactoyBean
     */
    @Bean(name = "shiroFilterFactoryBean")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("webSecurityManager") DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        //添加shiro内置过滤器
        /**
         * shiro内置过滤器,可以实现权限的拦截器
         * 常用的过滤器:
         *  anon:无需认证(登录)可以访问
         *  authc:必须认证才可以访问
         *  user:如果使用remeberMe的功能可以直接访问
         *  perms:该资源必须得到资源权限才可以访问
         *  role:该资源必须得到角色权限才可以访问
         */
        Map<String, String> filterMap = new LinkedHashMap<>();
       /* filterMap.put("/add","authc");
        filterMap.put("/update","authc");
        */
        //使用统配的过滤器拦截多个url
        filterMap.put("/login", "anon");
        filterMap.put("/test", "anon");

        //授权过滤器
        //注意:当授权拦截后,shiro会自动跳转到未授权页面
        filterMap.put("/add", "perms[user:add]");
//        filterMap.put("/update","perms[user:update]");

        //认证过滤器
        filterMap.put("/*", "authc");

        //设置未授权提示页面
        shiroFilterFactoryBean.setUnauthorizedUrl("/noAuth");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);

        //修改拦截后跳转的url
        shiroFilterFactoryBean.setLoginUrl("/login");
        return shiroFilterFactoryBean;
    }

在Controller中配置/noAuth页面跳转到无权限访问提示页面

    @RequestMapping("noAuth")
    public String noAuth(Model model) {
        model.addAttribute("msg", "无权限!");
        return "noAuth";
    }

完成Shiro的资源授权

修改UserRealm类的认证方法的实现

    /**
     * 执行授权逻辑
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行授权:Authorization");

        //给资源进行授权
        SimpleAuthorizationInfo simpleAuthenticationInfo  = new SimpleAuthorizationInfo();
        //添加资源的授权字符串
        simpleAuthenticationInfo.addStringPermission("user:add");

        return simpleAuthenticationInfo;
    }

将写死的授权字符串改为动态获取

1.在User表中增加perms字段,存储用户权限,在model也增加相应的字段

id name password perms
1 root root user:add
1 admin admin user:update

在Mapper和中增加根据用户id查询用户信息的方法

Mapper

    @Select("select * from User where id = #{id}")
    User selectUserById(@Param("id") Long id);

Service

    /**
     * 根据id查询用户信息
     *
     * @param id
     * @return
     */
    @Override
    public User selectUserById(Long id) {
        return userMapper.selectUserById(id);
    }

4.修改UserRealm中授权方法

package cn.sitcat.springbootshiro.configs;

import cn.sitcat.springbootshiro.model.User;
import cn.sitcat.springbootshiro.service.UserService;
import org.apache.shiro.SecurityUtils;
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.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * @author hiseico
 * @date 2019/4/13 18:07
 * @desc 自定义Realm
 */
public class UserRealm extends AuthorizingRealm {


    /**
     * 执行授权逻辑
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行授权:Authorization");

        //给资源进行授权
        SimpleAuthorizationInfo simpleAuthenticationInfo = new SimpleAuthorizationInfo();
        //添加资源的授权字符串
        //simpleAuthenticationInfo.addStringPermission("user:add");


        //从数据库查询当前登录的用户的授权字符串
        Subject subject = SecurityUtils.getSubject();
    /**
     *此处获取的User对象为下面执行认证逻辑中调用
     * return new SimpleAuthenticationInfo(user, user.getPassword(), "");时
     * 将user对象放到改方法的principal参数中传递了过来,该参数的类型为Object
     */
        User user = (User) subject.getPrincipal();
        User dbUser = userService.selectUserById(user.getId());

        simpleAuthenticationInfo.addStringPermission(dbUser.getPerms());

        return simpleAuthenticationInfo;
    }

    @Autowired
    private UserService userService;

    /**
     * 执行认证逻辑
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行认证:Authentiction");

        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

        User user = userService.getUserByName(token.getUsername());
        if (user == null) {
            //用户不存在
            return null; // shiro底层会抛出UnknowAccountException
        }

        //判断密码
        return new SimpleAuthenticationInfo(user, user.getPassword(), "");
    }
}

thymeleaf和shiro标签整合使用

1.导入thymeleaf模板引擎对shiro的拓展

        <!--thymeleaf 对shiro的扩展坐标-->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>

2.配置ShiroDialect

在ShiroConfig配置类里面添加getShiroDialect方法

    /**
    * 配置shiroDialect,用于thymeleaf和shiro标签配置使用
    * @return
    */
   @Bean
   public ShiroDialect getShiroDialect(){
       return new ShiroDialect();
   }

3.在页面上使用shiro标签

让对应权限的用户只能看到相应的内容

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1 th:text="${msg}"></h1>
<div shiro:hasPermission="user:add">
    用户添加功能:<a href="/add">添加</a>
</div>
<br>
<div shiro:hasPermission="user:update">
    用户编辑功能:<a href="/update">编辑</a>
</div>
</body>
</html>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • [TOC] 本文主要记录Spring Boot与Shiro进行整合使用,实现强大的用户权限管理,其中涉及如何完成用...
    Hiseico阅读 298评论 0 0
  • 后台管理系统 业务场景 spring boot + mybatis后台管理系统框架; layUI前端界面; shi...
    汉若已认证阅读 9,964评论 2 69
  • 我爱发朋友圈。 就发朋友圈这件小事,家人、朋友、网友都有不同反馈。 女人天生是爱分享的动物,和大多数女人一样,我的...
    向慧容阅读 818评论 6 6
  • 大家好,为了让大家更好地休息,公司准备了大巴集体出发去美丽的阿那亚。 请于29号(周日)早上8:00整华府会门口集...
    秦小妍阅读 223评论 0 0
  • Status Code 完整的 HTTP 1.1规范说明书来自于RFC 2616,HTTP 1.1的状态码被标记为...
    刘圣凯阅读 547评论 0 0