Apache Shiro+JWT

一、概述

什么是Apache Shiro

Apache Shiro是一个功能强大且灵活的开源安全框架,可以清晰地处理身份验证,授权,企业会话管理和加密。

Apache Shiro的首要目标是易于使用和理解。安全有时可能非常复杂,甚至是痛苦的,但事实并非如此。框架应尽可能掩盖复杂性,并提供简洁直观的API,以简化开发人员确保其应用程序安全的工作。

以下是Apache Shiro可以做的一些事情:

验证用户以验证其身份
为用户执行访问控制,例如:
确定是否为用户分配了某个安全角色
确定是否允许用户执行某些操作
在任何环境中使用Session API,即使没有Web容器或EJB容器也是如此。
在身份验证,访问控制或会话生命周期内对事件做出反应。
聚合用户安全数据的1个或多个数据源,并将其全部显示为单个复合用户“视图”。
启用单点登录(SSO)功能
无需登录即可为用户关联启用“记住我”服务
......
以及更多 - 全部集成到一个易于使用的内聚API中。
Shiro尝试为所有应用程序环境实现这些目标 - 从最简单的命令行应用程序到最大的企业应用程序,而不会强制依赖其他第三方框架,容器或应用程序服务器。当然,该项目旨在尽可能地融入这些环境,但它可以在任何环境中开箱即用。

Apache Shiro功能

功能模块.png

四大核心模块

  • 身份验证:有时称为“登录”,这是证明用户是他们所说的人的行为。

  • 授权:访问控制的过程,即确定“谁”可以访问“什么”。

  • 会话管理:即使在非Web或EJB应用程序中,也可以管理特定于用户的会话。

  • 密码学:使用加密算法保持数据安全,同时仍然易于使用。

还有其他功能可以在不同的应用程序环境中支持和强化这些问题,尤其是:

Web支持:Shiro的Web支持API可帮助轻松保护Web应用程序。
缓存: 缓存是Apache Shiro API中的第一层公民,可确保安全操作保持快速高效。
并发: Apache Shiro支持具有并发功能的多线程应用程序。
测试: 存在测试支持以帮助您编写单元和集成测试,并确保您的代码按预期受到保护。
“运行方式”: 允许用户假定其他用户的身份(如果允许)的功能,有时在管理方案中很有用。
“记住我”: 记住用户在会话中的身份,因此他们只需要在必要时登录。

二、架构分析

1.Shiro的架构有3个主要概念:和SubjectSecurityManagerRealms
架构图.png
  • Subject
    官网的描述为当前的用户、第三方服务等,其实就是与集成了shiro的系统交互的访客,抽象为Subject。
    Subject实例必须绑定一个SecurityManager
  • SecurityManager
    SecurityManager是Shiro架构的核心,充当一种“伞形”对象,协调其内部安全组件,共同形成对象图。 我们只需要对其进行相应的配置即可
    当我们与Subject交互时,实际上工作的是幕后的SecurityManager,它可以完成任何Subject安全操作的繁重任务。
  • Realms
    Realms充当Shiro与应用程序安全数据之间的“桥梁”或“连接器”。当实际与安全相关数据(如用户帐户)进行交互以执行身份验证(登录)和授权(访问控制)时,Shiro会从为应用程序配置的一个或多个领域中查找许多这些内容。
    从这个意义上讲,Realm本质上是一个特定于安全性的DAO:它封装了数据源的连接细节,并根据需要使相关数据可用于Shiro。配置Shiro时,必须至少指定一个Realm用于身份验证和/或授权。所述SecurityManager可与多个境界被配置,但至少有一个是必需的。
    Shiro提供了开箱即用的Realms,可以连接到许多安全数据源(也称为目录),如LDAP,关系数据库(JDBC),文本配置源(如INI和属性文件等)。如果默认域不符合您的需要,您可以插入自己的Realm实现来表示自定义数据源。
    与其他内部组件一样,Shiro SecurityManager管理如何使用Realms获取要表示为Subject实例的安全性和身份数据。
2.详细架构
image.png
  • Subjectorg.apache.shiro.subject.Subject
    当前与软件交互的实体(用户,第三方服务,cron作业等)的特定于安全性的“视图”。

  • SecurityManagerorg.apache.shiro.mgt.SecurityManager
    如上所述,这SecurityManager是Shiro建筑的核心。它主要是一个“伞形”对象,协调其托管组件,以确保它们一起平稳运行。它还管理Shiro对每个应用程序用户的视图,因此它知道如何对每个用户执行安全操作。

  • 认证器org.apache.shiro.authc.Authenticator
    Authenticator是,负责执行和反应以验证(注册)用户企图的组件。当用户尝试登录时,该逻辑由执行Authenticator。该Authenticator知道如何与一个或多个协调Realms存储有关用户/帐户信息。从这些数据中获取的数据Realms用于验证用户的身份,以保证用户确实是他们所说的人。

  • AuthenticationStrategyorg.apache.shiro.authc.pam.AuthenticationStrategy
    如果Realm配置了多个,AuthenticationStrategy则将协调领域以确定身份验证尝试成功或失败的条件(例如,如果一个领域成功但其他领域失败尝试是否成功?必须所有领域成功吗?只有第一个?)。

  • Authorizerorg.apache.shiro.authz.Authorizer
    Authorizer是部件负责确定用户在该应用程序的访问控制。这种机制最终会说明是否允许用户做某事。与此类似Authenticator,它Authorizer也知道如何协调多个后端数据源以访问角色和权限信息。在Authorizer使用该信息来确定到底是否允许用户执行特定的操作。

  • SessionManagerorg.apache.shiro.session.mgt.SessionManager
    SessionManager知道如何创建和管理用户Session生命周期,提供在所有环境中的用户强大的会话体验。这是安全框架领域的一项独特功能 - 即使没有可用的Web / Servlet或EJB容器,Shiro也能够在任何环境中本地管理用户Sessions。默认情况下,Shiro将使用现有的会话机制(例如Servlet容器),但如果没有,例如在独立应用程序或非Web环境中,它将使用其内置的企业会话管理提供相同的编程经验。的SessionDAO存在允许任何数据源被用来坚持的会议。

  • SessionDAOorg.apache.shiro.session.mgt.eis.SessionDAO
    SessionDAO执行Session代表的持久性(CRUD)操作SessionManager。这允许将任何数据存储插入会话管理基础结构。

  • CacheManagerorg.apache.shiro.cache.CacheManager
    CacheManager创建和管理Cache其他四郎组件使用实例的生命周期。由于Shiro可以访问许多后端数据源以进行身份​​验证,授权和会话管理,因此缓存一直是框架中的一流架构功能,可在使用这些数据源时提高性能。任何现代开源和/或企业缓存产品都可以插入Shiro,以提供快速有效的用户体验。

  • Cryptographyorg.apache.shiro.crypto.*
    密码学是企业安全框架的自然补充。Shiro的crypto软件包包含易于使用和理解的密码密码,哈希(aka摘要)和不同编解码器实现的表示。该软件包中的所有类都经过精心设计,易于使用且易于理解。使用Java本机加密支持的任何人都知道它可能是一个具有挑战性的驯服动物。Shiro的加密API简化了复杂的Java机制,使密码学易于用于普通的凡人。

  • Realmorg.apache.shiro.realm.Realm
    如上所述,Realms充当Shiro与应用程序安全数据之间的“桥接”或“连接器”。当实际与安全相关数据(如用户帐户)进行交互以执行身份验证(登录)和授权(访问控制)时,Shiro会从为应用程序配置的一个或多个领域中查找许多这些内容。您可以根据Realms需要配置任意数量(通常每个数据源一个),Shiro将根据需要进行身份验证和授权协调。

总结:Subject相当于shiro的门面(前台),负责对外交互,实际的验证授权等需要由SecurityManager决定,而SecurityManager做出决定需要经过数据验证,那么数据由Realm来提供,到此就把shiro的框架流程串起来了。

二、shiro整合spring

由于我们在开发中,基本上都是结合spring使用shiro,所以这里略过了官网的入门案例

Shiro一直支持Spring Web应用程序。在Web应用程序中,所有可通过Shiro访问的Web请求都必须通过主Shiro过滤器。此过滤器本身非常强大,允许基于任何URL路径表达式执行临时自定义过滤器链。
以下是如何在基于Spring的Web应用程序中配置Shiro:

1、采用xml方式配置(不推荐使用)

在web.xml中配置shiro过滤器

<!-- 过滤器的名称和你在applicationContext.xml中配置的bean注入必须一致 -->
<!--见applicationContext.xml中 <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> -->
<filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <init-param>
        <param-name>targetFilterLifecycle</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>

...

<!--在所有其他过滤器之前定义此过滤器,/ *捕获所有请求,确保过滤了您希望Shiro访问的任何请求。 -->
<filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

在applicationContext.xml中配置

<!-- id必须和web.xml中的filter名称匹配-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager"/>
    <!-- override these for application-specific URLs if you like:
   <!--指定跳转到的登录页面-->
    <property name="loginUrl" value="/login.jsp"/>
  <!--指定跳转到的成功页面-->
    <property name="successUrl" value="/home.jsp"/>
  <!--指定跳转到的无权限页面-->
    <property name="unauthorizedUrl" value="/unauthorized.jsp"/> -->
    <!-- The 'filters' property is not necessary since any declared javax.servlet.Filter bean  -->
    <!-- defined will be automatically acquired and available via its beanName in chain        -->
    <!-- definitions, but you can perform instance overrides or name aliases here if you like: -->
    <!-- <property name="filters">
        <util:map>
            <entry key="anAlias" value-ref="someFilter"/>
        </util:map>
    </property> -->
<!--根据需要添加过滤器到过滤器链中-->
    <property name="filterChainDefinitions">
        <value>
            # some example chain definitions:
            /admin/** = authc, roles[admin]
            /docs/** = authc, perms[document:read]
            /** = authc
            # more URL-to-FilterChain definitions here
        </value>
    </property>
</bean>

<!-- Define any javax.servlet.Filter beans you want anywhere in this application context.   -->
<!-- They will automatically be acquired by the 'shiroFilter' bean above and made available -->
<!-- to the 'filterChainDefinitions' property.  Or you can manually/explicitly add them     -->
<!-- to the shiroFilter's 'filters' Map if desired. See its JavaDoc for more details.       -->
<!--除了shiro提供的默认过滤器,也可以自定义过滤器-->
<bean id="someFilter" class="..."/>
<bean id="anotherFilter" class="..."> ... </bean>
...

<!--配置securityManager-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <!-- Single realm app.  If you have multiple realms, use the 'realms' property instead. -->
    <property name="realm" ref="myRealm"/>
    <!-- By default the servlet container sessions will be used.  Uncomment this line
         to use shiro's native sessions (see the JavaDoc for more): -->
    <!-- <property name="sessionMode" value="native"/> -->
</bean>
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

<!-- Define the Shiro Realm implementation you want to use to connect to your back-end -->
<!--配置自定义的realm,用户用户信息的比对-->
<!-- security datasource: -->
<bean id="myRealm" class="...">
    ...
</bean>

开启shiro注解
在请求接口中,也许我们会通过shiro的注解进行安全认证,(例如@RequiresRoles, @RequiresPermissions等等)
方法很简单,我们只需要在applicationContext.xml中添加如下配置,但注意,此时必须保证lifecycleBeanPostProcessor进行了配置

<!-- Enable Shiro Annotations for Spring-configured beans.  Only run after -->
<!-- the lifecycleBeanProcessor has run: -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/>
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
    <property name="securityManager" ref="securityManager"/>
</bean>

2.采用注解的方式进行配置(推荐使用)

shiro的配置

2.1 首先自定义Realm
/**
 * 认证
 *
 */
@Component
public class MyRealm extends AuthorizingRealm {
  
    /**
     * 授权(验证权限时调用)
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        User user = (User)principals.getPrimaryPrincipal();
        Long userId = user.getUserId();

        //用户权限列表
        Set<String> permsSet = shiroService.getUserPermissions(userId);

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setStringPermissions(permsSet);
        return info;
    }

    /**
     * 认证(登录时调用)
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
     UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken)token;
        String username = usernamePasswordToken.getUsername();
       char[] password = usernamePasswordToken.getPassword();
      //根据用户名和密码去验证用户
        ...
      //验证通过后
        User user = getUser();

        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password,username);
        return info;
    }
}

2.2 shiro的配置类
/**
 * Shiro配置
 *
 */
@Configuration
public class ShiroConfig {

    @Bean("securityManager")
    public SecurityManager securityManager(MyRealm myRealm, SessionManager sessionManager) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm);
        securityManager.setSessionManager(sessionManager);

        return securityManager;
    }

    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);

      //放行一些不用权限验证的路径
        Map<String, String> filterMap = new LinkedHashMap<>();
        filterMap.put("/webjars/**", "anon");
        filterMap.put("/druid/**", "anon");
        filterMap.put("/api/**", "anon");
        filterMap.put("/sys/login", "anon");
        filterMap.put("/**/*.css", "anon");
        filterMap.put("/**/*.js", "anon");
        filterMap.put("/**/*.html", "anon");
        filterMap.put("/img/**", "anon");
        filterMap.put("/fonts/**", "anon");
        filterMap.put("/plugins/**", "anon");
        filterMap.put("/swagger/**", "anon");
        filterMap.put("/favicon.ico", "anon");
        filterMap.put("/captcha.jpg", "anon");
        filterMap.put("/", "anon");
        filterMap.put("/**", "oauth2");
        shiroFilter.setFilterChainDefinitionMap(filterMap);

        return shiroFilter;
    }
  
    @Bean("lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }


    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

}

配置过滤器,将上面写的过滤器加入到容器中

package cn.environmental.config;

import cn.expand.filter.CorsFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.DelegatingFilterProxy;

import cn.environmental.common.xss.XssFilter;

import javax.servlet.DispatcherType;

/**
 * Filter配置
 *
 */
@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean shiroFilterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        //注意shiroFilter和shiroConfig中匹配
        registration.setFilter(new DelegatingFilterProxy("shiroFilter"));
        //该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理
        registration.addInitParameter("targetFilterLifecycle", "true");
        registration.setEnabled(true);
        registration.setOrder(Integer.MAX_VALUE - 1);
        registration.addUrlPatterns("/*");
        return registration;
    }

  
}

三、Shiro整合JWT

JWT的介绍不在这里进行说明了,可以自己查阅相关资料
引入JWT后的过程如下:

3.1 在用户登录的成功的时候为其生成token,并返回,用户访问其他接口时需要携带token!

①引入jjwt依赖

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
</dependency>

②生成token

public class JwtTest {

    /****
     * 创建Jwt令牌
     */
    @Test
    public void testCreateJwt(){
        JwtBuilder builder= Jwts.builder()
                .setId("888")             //设置唯一编号
                .setSubject("小白")       //设置主题  可以是JSON数据
                //.addClaims(xxx)  自定义载荷信息
                .setIssuedAt(new Date())  //设置签发日期
                //.setExpiration(date)//用于设置过期时间 ,参数为Date类型数据
                .signWith(SignatureAlgorithm.HS256,"秘钥");//设置签名 使用HS256算法,并设置SecretKey(字符串)
        //构建 并返回一个字符串
        System.out.println( builder.compact() );
//eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NjIwNjIyODd9.RBLpZ79USMplQyfJCZFD2muHV_KLks7M1ZsjTu6Aez4

    }
}

③解析token

/***
 * 解析Jwt令牌数据
 */
@Test
public void testParseJwt(){
    String compactJwt="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NjIwNjI5MjUsImV4cCI6MTU2MjA2MjkyNX0._vs4METaPkCza52LuN0-2NGGWIIO7v51xt40DHY1U1Q";
    Claims claims = Jwts.parser().
            setSigningKey("秘钥").
            parseClaimsJws(compactJwt).
            getBody();
    System.out.println(claims);
}
3.2 引入token后需要在上面的shiroFilter中加入一个自定义的过滤器来专门验证token

/**
 * token过滤器
 */
public class OAuth2Filter extends AuthenticatingFilter {

    @Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
        //获取请求token
        String token = getRequestToken((HttpServletRequest) request);

        if(StringUtils.isBlank(token)){
            return null;
        }

        return new OAuth2Token(token);
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        if(((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())){
            return true;
        }

        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        //获取请求token,如果token不存在,直接返回401
        String token = getRequestToken((HttpServletRequest) request);
        if(StringUtils.isBlank(token)){
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
            httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());

            String json = new Gson().toJson(R.error(HttpStatus.SC_UNAUTHORIZED, "invalid token"));

            httpResponse.getWriter().print(json);

            return false;
        }

        return executeLogin(request, response);
    }

    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setContentType("application/json;charset=utf-8");
        httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
        httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());
        try {
            //处理登录失败的异常
            Throwable throwable = e.getCause() == null ? e : e.getCause();
            R r = R.error(HttpStatus.SC_UNAUTHORIZED, throwable.getMessage());

            String json = new Gson().toJson(r);
            httpResponse.getWriter().print(json);
        } catch (IOException e1) {

        }

        return false;
    }

    /**
     * 获取请求的token
     */
    private String getRequestToken(HttpServletRequest httpRequest){
        //从header中获取token
        String token = httpRequest.getHeader("token");

        //如果header中不存在token,则从参数中获取token
        if(StringUtils.isBlank(token)){
            token = httpRequest.getParameter("token");
        }

        return token;
    }


}
public class OAuth2Token implements AuthenticationToken {
    private String token;

    public OAuth2Token(String token){
        this.token = token;
    }

    @Override
    public String getPrincipal() {
        return token;
    }

    @Override
    public Object getCredentials() {
        return token;
    }
}

创建完验证token的过滤器后需要在shiroConfig中添加

@Configuration
public class ShiroConfig {

    @Bean("securityManager")
    public SecurityManager securityManager(OAuth2Realm oAuth2Realm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(oAuth2Realm);
        securityManager.setRememberMeManager(null);
        return securityManager;
    }

    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);

        //token过滤验证
        Map<String, Filter> filters = new HashMap<>();
        filters.put("oauth2", new OAuth2Filter());
        shiroFilter.setFilters(filters);

        Map<String, String> filterMap = new LinkedHashMap<>();
        filterMap.put("/webjars/**", "anon");
        filterMap.put("/druid/**", "anon");
        filterMap.put("/app/**", "anon");
        filterMap.put("/sys/login", "anon");
        filterMap.put("/swagger/**", "anon");
        filterMap.put("/v2/api-docs", "anon");
        filterMap.put("/swagger-ui.html", "anon");
        filterMap.put("/swagger-resources/**", "anon");
        filterMap.put("/captcha.jpg", "anon");
        filterMap.put("/aaa.txt", "anon");
        filterMap.put("/**", "oauth2");
        shiroFilter.setFilterChainDefinitionMap(filterMap);

        return shiroFilter;
    }

    @Bean("lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

}

token的过滤器的优先级比较高,这时候需要验证权限的接口就会先判断token是否有效了,需要将上面的Myrealm的认证方法改一下

  /**
     * 认证(登录时调用)
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String accessToken = (String) token.getPrincipal();

        //验证token的代码(略)
          ...
        //token失效提醒用户(略)
        //return
       
        //查询用户信息
        User user = queryUser(user.getUserId());

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

推荐阅读更多精彩内容

  • 目录 Apache Shiro架构详解... 1 1、高层视图... 2 2、详细架构... 4 3、Shrio设...
    尘_竹阅读 3,286评论 0 4
  • Apache Shiro Apache Shiro 是一个强大而灵活的开源安全框架,它干净利落地处理身份认证,授权...
    罗志贇阅读 3,214评论 1 49
  • 构建一个互联网应用,权限校验管理是很重要的安全措施,这其中主要包含: 认证 - 用户身份识别,即登录 授权 - 访...
    zhuke阅读 3,487评论 0 30
  • 构建第一个Apache Shiro应用 如果您是Apache Shiro的新手,这个简短的教程将向您展示如何设置基...
    尘_竹阅读 1,658评论 0 3
  • 现在是2019年4月21日。 忙碌的双休日就那么“唰”的一下过去了。现在的龙龙很平静,内心就像小池塘一样,波澜不惊...
    陌上风起阅读 245评论 0 0