整合 SpringMVC & Shiro

测试数据

准备测试数据及相应的测试页面

工程中定义几个地址:
/gologin.html 不需要权限验证就可以访问
/login.html 不需要验证就可以访问
/doadd.html 要有perm1和perm2权限才可以访问,访问成功后页面显示add
/doget.html 要有admin权限才可以访问,访问成功后页面显示get
/doupdate.html 要有perm1权限才能访问,访问成功后页面显示update
/dodel.html 要有perm2权限才可以访问,访问成功后页面显示del

添加依赖

<!--shiro核心类库-->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.2.3</version>
</dependency>
<!--日志工具包-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.6.1</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.6.1</version>
</dependency>
<!--mysql驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.32</version>
</dependency>
<!--spring相关包-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>4.3.11.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>4.3.11.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>4.3.11.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.4.0</version>
</dependency>

配置文件

db.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/数据库?characterEncoding=utf-8
jdbc.username=用户名
jdbc.password=密码

log4j.properties

log4j.rootLogger=INFO, stdout, 

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n

spring-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd">

    <!--读取配置文件-->
    <context:property-placeholder location="classpath:db.properties" ignore-unresolvable="true"/>
    <!--从配置文件中获取数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <!-- 配置Jdbc模板 -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <bean id="shiroDAO" class="dao.ShiroDAO">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>
    <bean id="shiroService" class="service.ShiroService">
        <property name="shiroDAO" ref="shiroDAO"/>
    </bean>
    <bean id="myShiroRealm" class="util.MyShiroRealm">
        <property name="shiroService" ref="shiroService"/>
    </bean>

    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="myShiroRealm"/>
    </bean>
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <!--去登录的地址-->
        <property name="loginUrl" value="/gologin.html"/>
        <!--登录成功的跳转地址-->
        <property name="successUrl" value="/index.html"/>
        <!--验证失败的跳转地址-->
        <property name="unauthorizedUrl" value="/error.jsp"/>
        <!--定义过滤的规则-->
        <!--复杂的系统中,url和权限都可以从数据库中读取-->
        <!--anon是不需要验证,authc时需要验证,perms[admin]代表要admin权限-->
        <property name="filterChainDefinitions">
            <value>
                /gologin.html = anon
                /login.html = anon
                /doadd.html = authc, perms[perm1,perm2]
                /doget.html = authc, perms[admin]
                /doupdate.html = authc, perms[perm1]
                /dodel.html = authc, perms[perm2]
            </value>
        </property>
    </bean>
</beans>

配置说明
shiro过滤器过滤属性含义:

  • securityManager:这个属性是必须的。
  • loginUrl :没有登录的用户请求需要登录的页面时自动跳转到登录页面,不是必须的属性,不输入地址的话会自动寻找项目web项目的根目录下的”/login.jsp”页面。
  • successUrl :登录成功默认跳转页面,不配置则跳转至”/”。如果登陆前点击的一个需要登录的页面,则在登录自动跳转到那个需要登录的页面。不跳转到此。
  • unauthorizedUrl :没有权限默认跳转的页面
    其权限过滤器及配置释义:

anon:
例子/admins/=anon 没有参数,表示可以匿名使用。
authc:
例如/admins/user/
=authc表示需要认证(登录)才能使用,没有参数
roles(角色)
例子/admins/user/=roles[admin],参数可以写多个,参数之间用逗号分割,当有多个参数时,例如admins/user/=roles["admin,guest"],每个参数通过才算通过,相当于hasAllRoles()方法。
perms(权限)
例子/admins/user/=perms[add],参数可以写多个,例如/admins/user/=perms["add, modify"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。
rest
例子/admins/user/=rest[user],根据请求的方法,相当于/admins/user/=perms[user:method] ,其中method为post,get,delete等。
port
例子/admins/user/=port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString
是你访问的url里的?后面的参数。
authcBasic
例如/admins/user/
=authcBasic没有参数.表示httpBasic认证
ssl:
例子/admins/user/=ssl没有参数,表示安全的url请求,协议为https
user:
例如/admins/user/
=user没有参数表示必须存在用户,当登入操作时不做检查
spring-mvc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd">
    <context:annotation-config />
    <!-- 启动自动扫描 -->
    <context:component-scan base-package="controller">
        <!-- 制定扫包规则 ,只扫描使用@Controller注解的JAVA类 -->
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
</beans>

web.xml

    <!-- 配置shiro的核心拦截器 -->
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>*.html</url-pattern>
    </filter-mapping>

DAO层

package dao;

import org.springframework.jdbc.core.JdbcTemplate;

import java.util.List;

public class ShiroDAO {
    private JdbcTemplate jdbcTemplate;

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    /**
     * 根据用户名查询密码
     */
    public String getPasswordByUserName(String username) {
        String sql = "select PASSWORD from SHIRO_USER where USER_NAME = ?";
        String password = jdbcTemplate.queryForObject(sql, String.class, username);
        return password;
    }

    /**
     * 查询当前用户对应的权限
     */
    public List<String> getPermissionByUserName(String username) {
        String sql = "select P.PERM_NAME from SHIRO_ROLE_PERMISSION P inner join SHIRO_USER_ROLE R on P.ROLE_NAME=R.ROLE_NAME where R.USER_NAME = ?";
        List<String> perms = jdbcTemplate.queryForList(sql, String.class, username);
        return perms;
    }
}

Service层

package service;

import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
import dao.ShiroDAO;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;

import java.util.List;

public class ShiroService {
    private ShiroDAO shiroDAO;

    public void setShiroDAO(ShiroDAO shiroDAO) {
        this.shiroDAO = shiroDAO;
    }

    /**
     * 登录
     */
    public void doLogin(String username, String password) throws Exception {
        Subject currentUser = SecurityUtils.getSubject();
        if (!currentUser.isAuthenticated()) {
            UsernamePasswordToken token =
                    new UsernamePasswordToken(username, password);
            token.setRememberMe(true);//是否记住用户
            try {
                currentUser.login(token);//执行登录
            } catch (UnknownAccountException uae) {
                throw new Exception("账户不存在");
            } catch (IncorrectCredentialsException ice) {
                throw new Exception("密码不正确");
            } catch (LockedAccountException lae) {
                throw new Exception("用户被锁定了 ");
            } catch (AuthenticationException ae) {
                ae.printStackTrace();
                throw new Exception("未知错误");
            }
        }
    }

    /**
     * 根据用户名查询密码
     */
    public String getPasswordByUserName(String username) {
        return shiroDAO.getPasswordByUserName(username);
    }

    /**
     * 查询用户所有权限
     */
    public List<String> getPermissionByUserName(String username) {
        return shiroDAO.getPermissionByUserName(username);
    }
}

注:rememberMe后浏览器里会生成一个cookie:

如果访问的路径,要求权限是user,所有使用过rememberMe的用户就都可以访问。但是它只是记录你登录过,不会记住你是谁以及你的权限信息。

SpringMVC Controller

package controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import service.ShiroService;

@Controller
public class ShiroController {
    @Autowired
    private ShiroService shiroService;

    @RequestMapping("/gologin.html")
    public String goLogin() {
        return "/login.jsp";
    }

    @RequestMapping("/login.html")
    public ModelAndView login(String username, String password) {
        try {
            shiroService.doLogin(username, password);
        } catch (Exception e) {
            return new ModelAndView("/error.jsp", "msg", e.getMessage());
        }
        return new ModelAndView("/index.jsp");
    }
    @RequestMapping("/logout.html")
    public String logout() {
        Subject currentUser = SecurityUtils.getSubject();
        currentUser.logout();
        return "/login.jsp";
    }

    /**
     * 模拟不同的请求,在配置文件中对请求进行权限拦截
     */
    @RequestMapping("/do{act}.html")
    public ModelAndView get(@PathVariable String act) {
        //简化代码,省略数据库操作
        //通过页面上显示的信息查看请求是否被拦截
        return new ModelAndView("/page.jsp", "page", act);
    }
}

自定义Realm

package util;

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 service.ShiroService;

import java.util.List;

public class MyShiroRealm extends AuthorizingRealm {
    private ShiroService shiroService;

    public void setShiroService(ShiroService shiroService) {
        this.shiroService = shiroService;
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo
                 (PrincipalCollection principalCollection) {
        //根据自己的需求编写获取授权信息,这里简化代码获取了用户对应的所有权限
        String username = 
        (String) principalCollection.fromRealm(getName()).iterator().next();
        if (username != null) {
            List<String> perms = shiroService.getPermissionByUserName(username);
            if (perms != null && !perms.isEmpty()) {
                SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
                for (String each : perms) {
                    //将权限资源添加到用户信息中
                    info.addStringPermission(each);
                }
                return info;
            }
        }
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo
        (AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        // 通过表单接收的用户名,调用currentUser.login的时候执行
        String username = token.getUsername();
        if (username != null && !"".equals(username)) {
            //查询密码
            String password = shiroService.getPasswordByUserName(username);
            if (password != null) {
                return new SimpleAuthenticationInfo(username, password, getName());
            }
        }
        return null;
    }
}

测试

准备相应的页面:略

动态配置过滤规则

在实际开发中,url和对应的访问权限可能需要从数据库中读取,我们可以定义一个工具类从数据库中读取访问权限并传递给Shiro。

package util;

import org.apache.shiro.config.Ini;
import org.apache.shiro.util.CollectionUtils;
import org.apache.shiro.web.config.IniFilterChainResolverFactory;
import org.springframework.beans.factory.FactoryBean;

import java.text.MessageFormat;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

public class MyChainDefinitions implements FactoryBean<Ini.Section> {
    public static final String PREMISSION_STRING = "perms[{0}]";
    public static final String ROLE_STRING = "roles[{0}]";
    private String filterChainDefinitions;

    public void setFilterChainDefinitions(String filterChainDefinitions) {
        this.filterChainDefinitions = filterChainDefinitions;
    }


    @Override
    public Ini.Section getObject() throws Exception {
        //urls可以从数据库查出来,此处省略代码,直接模拟几条数据
        Set<String> urls = new LinkedHashSet<>();
        urls.add("/dotest1.html");
        urls.add("/dotest2.html");
        //每个url对应的权限也可以从数据库中查出来,这里模拟几条数据
        Map<String, String[]> permsMap = new HashMap<>();
        permsMap.put("/dotest1.html", new String[]{"perm1", "admin"});
        permsMap.put("/dotest2.html", new String[]{"perm1"});

        //加载配置默认的过滤链
        Ini ini = new Ini();
        ini.load(filterChainDefinitions);
        Ini.Section section = ini.getSection(IniFilterChainResolverFactory.URLS);
        if (CollectionUtils.isEmpty(section)) {
            section = ini.getSection(Ini.DEFAULT_SECTION_NAME);
        }
        for (String url : urls) {
            String[] perms = permsMap.get(url);
            StringBuilder permFilters = new StringBuilder();
            for (int i = 0; i < perms.length; i++) {
                permFilters.append(perms[i]).append(",");
            }
            //去掉末尾的逗号
            String str = permFilters.substring(0, permFilters.length() - 1);
            //生成结果如:/dotest1.html = authc, perms[admin]
            section.put(url, MessageFormat.format(PREMISSION_STRING, str));
        }
        return section;
    }

    @Override
    public Class<?> getObjectType() {
        return this.getClass();
    }

    @Override
    public boolean isSingleton() {
        return false;
    }
}

注:section中是以Map存放的数据,所以放入相同的key,后放的会覆盖先放的数据。

spring-context.xml

<!--声明自定义规则-->
<bean id="myChainDefinitions" class="util.MyChainDefinitions">
    <!--静态的条件-->
    <property name="filterChainDefinitions">
        <value>
            /gologin.html = anon
            /login.html = anon
            /doadd.html = authc, perms[perm1,perm2]
            /doget.html = authc, perms[admin]
            /doupdate.html = authc, perms[perm1]
            /dodel.html = authc, perms[perm2]
            /logout.html=user
        </value>
    </property>
</bean>

将shiroFilter中的filterChainDefinitions替换掉:

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager"/>
    <property name="loginUrl" value="/gologin.html"/>
    <property name="successUrl" value="/index.html"/>
    <property name="unauthorizedUrl" value="/error.jsp"/>
    <!--定义过滤的规则-->
    <property name="filterChainDefinitionMap" ref="myChainDefinitions"/>
</bean>

可以访问/dotest1.html和/dotest2.html查看拦截效果。

重写过滤器

/doadd.html = authc,perms[perm1,perm2]

shiro默认的拦截是要满足所有的条件,但有时我们只要满足其中的一个,用于拥有perm1或perm2任何一个条件就可以访问/doadd.html。这时我们可以重写过滤器,将and变成or

package util;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;

public class MyShiroPermFilter extends AuthorizationFilter {
    @Override
    protected boolean isAccessAllowed
            (ServletRequest req, ServletResponse resp, Object mappedValue) 
                 throws Exception {
        Subject subject = getSubject(req, resp);
        String[] permsArray = (String[]) mappedValue;

        if (permsArray == null || permsArray.length == 0) { //没有权限限制
            return true;
        }
        for (int i = 0; i < permsArray.length; i++) {
            //如果是角色,就是subject.hasRole()
            //若当前用户是permsArray中的任何一个,则有权限访问
            if (subject.isPermitted(permsArray[i])) {
                return true;
            }
        }
        return false;
    }
}

spring-context.xml
在id=”shiroFilter”的bean中加入过滤拦截:

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager"/>
    <property name="loginUrl" value="/gologin.html"/>
    <property name="successUrl" value="/index.html"/>
    <property name="unauthorizedUrl" value="/error.jsp"/>
    <property name="filterChainDefinitionMap" ref="myChainDefinitions"/>
    <!--修改后的过滤规则,从and变成or-->
    <property name="filters">
        <map>
            <entry key="perms">
                <bean class="util.MyShiroPermFilter"/>
            </entry>
        </map>
    </property>
</bean>
rememberMe属性

rememberMe可以在浏览器中设置cookie,在spring配置中可以设置cookie的属性,如过期时间、cookie名字、加密的秘钥等:

<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
    <constructor-arg value="rememberMeShiro"/><!-- 浏览器中cookie的名字 -->
    <property name="httpOnly" value="true"/><!--document对象中就看不到cookie了-->
    <property name="maxAge" value="2592000"/><!-- 30天 -->
</bean>

<!-- rememberMe管理器 -->
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
    <!--秘钥要16位,24位或32位的Base64。这个解密后是1234567890abcdef-->
    <property name="cipherKey" value="#{T(org.apache.shiro.codec.Base64).decode('MTIzNDU2Nzg5MGFiY2RlZg==')}"/>
    <property name="cookie" ref="rememberMeCookie"/>
</bean>
<!--  在securityManager中加入rememberMe中加入配置: -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <property name="realm" ref="myShiroRealm"/>
    <!--加入rememberMe的设置-->
    <property name="rememberMeManager" ref="rememberMeManager"/>
</bean>

配置说明:
HttpOnly属性:
浏览器中通过document.cookie可以获取cookie属性,设置了HttpOnly=true,在脚本中就不能的到cookie了。可以避免cookie被盗用。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容

  • 这篇博客就来写一写之前学习shiro框架整合的时候,产生的一些问题,相信大家在学习的时候也有相应的一些疑惑。接下来...
    z77z阅读 11,649评论 8 15
  • github: https://github.com/pyygithub/shiro-web 什么是Shiro? ...
    匆匆岁月阅读 5,964评论 3 22
  • 早上没起,八点才起。。。。然后吃早饭,一碗粥,一个鸡蛋一个馒头,中午,一个馒头,菜用水冲掉油,一个鱿鱼串 我的瘦子...
    鲁春玲阅读 136评论 0 0
  • 天色正好 想和你说说话 却莫名的骗自己 旭日东升 该去写首诗 我把诗拼成一把折伞 撑开 替你抗过冬天 你嘲笑我的荒...
    云落听风阅读 190评论 0 1
  • 在哪一刻,你感受到了真正的贫富差距? 一堵墙的距离,大概十几米。 墙的左边是最底层的贫民窟,为生计起早贪黑;墙的右...
    腻腻丷阅读 19,172评论 35 226