一篇看懂shiro安全框架

本篇框架使用:SpringBoot SpringMvc MyBatis shiro thymeleaf
我们先来了解一下shiro的基本知识

1.什么是Shiro权限框架
Apache Shiro是一个强大且易用的Java安全框架,执行身份认证、授权、加密和会话管理。使用Shiro的易于理解的API,可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
2.Shiro可以做哪些事情
●验证用户身份
●用户访问控制,比如用户是否被赋予了某个角色;是否允许访问某些资源
●在任何环境都可以使用SessionAPI,即使不是WEB项目或没有EJB容器
●事件响应(在身份验证,访问控制期间,或是session生命周期中)
●集成多种用户信息数据源
●SSO- 单点登陆
●Remember Me,记住我
●Shiro尝试在任何应用环境下实现这些功能,而不依赖其他框架、容器或应用服务器。
3.Shiro与Springsecurity区别
1.首先Shiro较之Spring Security, Shiro 在保持强大功能的同时,还在简单性和灵活性方面拥有巨大优势。
2.Shiro是一个强大而灵活的开源安全框架,能够非常清晰的处理认证、授权、加密、会话管理等功能。
3.密码加密

4.框架体系
Shiro 的整体框架大致如下图所示(图片来自互联网):

Authentication(认证), Authorization(授权), Session Management(会话管理), Cryptography(加密)代表Shiro应用安全的四大基石。

它们分别是:
Authentication(认证):用户身份识别,通常被称为用户“登录”。
Authorization(授权):访问控制。比如某个用户是否具有某个操作的使用权限。
Session Management(会话管理):特定于用户的会话管理,甚至在非web 应用程序。
Cryptography(加密):在对数据源使用加密算法加密的同时,保证易于使用。
除此之外,还有其他的功能来支持和加强这些不同应用环境下安全领域的关注点。
特别是对以下的功能支持:
Web支持:Shiro 提供的 web 支持 api ,可以很轻松的保护 web 应用程序的安全。
缓存:缓存是 Apache Shiro 保证安全操作快速、高效的重要手段。
并发:Apache Shiro 支持多线程应用程序的并发特性。
测试:支持单元测试和集成测试,确保代码和预想的一样安全。
“Run As”:这个功能允许用户在许可的前提下假设另一个用户的身份。
“Remember Me”:跨 session 记录用户的身份,只有在强制需要时才需要登录。
5.shiro主要流程
在概念层,Shiro 架构包含三个主要的理念:Subject, SecurityManager 和 Realm。下面的图展示了这些组件如何相互作用,我们将在下面依次对其进行描述。

1.Subject:当前用户,Subject 可以是一个人,但也可以是第三方服务、守护进程帐户、时钟守护任务或者其它-当前和软件交互的任何事件。
2. SecurityManager:管理所有Subject,SecurityManager 是Shiro 架构的核心,配合内部安全组件共同组成安全伞。
3.Realms:用于进行权限信息的验证,我们自己实现。Realm本质上是一个特定的安全DAO:它封装与数据源连接的细节,得到Shiro所需的相关的数据。在配置Shiro的时候,你必须指定至少一个Realm来实现认证(authentication)和/或授权( authorization)。

了解完shiro的基础知识,接下来我们看代码的实现

数据库表

user表

role表

代码目录结构

pom.xml(加入shiro依赖包)

 <dependencies>    <!--包含spirng Mvc ,tomcat的包-->    <dependency>      <groupId>org.springframework.boot</groupId>      <artifactId>spring-boot-starter-test</artifactId>      <scope>test</scope>    </dependency>      <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->    <dependency>      <groupId>org.projectlombok</groupId>      <artifactId>lombok</artifactId>      <version>1.18.10</version>      <scope>provided</scope>    </dependency>    <!-- 包含spirng Mvc ,tomcat的包包含requestMapping restController 等注解 -->    <dependency>      <groupId>org.springframework.boot</groupId>      <artifactId>spring-boot-starter-web</artifactId>    </dependency>    <!-- mybatis 与 spring boot 2.x的整合包 -->    <dependency>      <groupId>org.mybatis.spring.boot</groupId>      <artifactId>mybatis-spring-boot-starter</artifactId>      <version>2.0.1</version>    </dependency>    <!-- 连接mysql数据源 -->    <dependency>      <groupId>com.alibaba</groupId>      <artifactId>druid</artifactId>      <version>1.1.17</version>    </dependency>    <!-- mysql包 -->    <dependency>      <groupId>mysql</groupId>      <artifactId>mysql-connector-java</artifactId>    </dependency>    <dependency>      <groupId>org.apache.shiro</groupId>      <artifactId>shiro-spring</artifactId>      <version>1.4.0</version>    </dependency>      <dependency>      <groupId>commons-codec</groupId>      <artifactId>commons-codec</artifactId>    </dependency>      <dependency>      <groupId>org.apache.commons</groupId>      <artifactId>commons-lang3</artifactId>    </dependency>    <dependency>      <groupId>org.springframework.boot</groupId>      <artifactId>spring-boot-starter-thymeleaf</artifactId>    </dependency>  </dependencies>

配置类application.properties(配置连接mysql数据库,mybatis,thymeleaf配置)

server.port=8080#数据库连接spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTCspring.datasource.driver-class-name=com.mysql.jdbc.Driverspring.datasource.username=rootspring.datasource.password=123456spring.datasource.type=com.alibaba.druid.pool.DruidDataSource#mybatis配置mybatis.type-aliases-package=com.demo.shiro.entitymybatis.mapper-locations=classpath:mapper/*.xml# thymeleaf页面模板配置spring.thymeleaf.prefix= classpath:/templates/spring.thymeleaf.suffix=.ftl

项目启动类ShiroApplication(对mapper进行扫包)

package com.demo.shiro;import org.mybatis.spring.annotation.MapperScan;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication@MapperScan(basePackages = "com.demo.shiro.mapper")public class ShiroApplication {        public static void main(String[] args) {        SpringApplication.run(ShiroApplication.class, args);    }    }

实体类RoleMapper

/******************************************************************************* * Package: com.demo.shiro.entity * Type:    RoleEntity * Date:    2020/12/5 11:06 * * Copyright (c) 2020 HUANENG GUICHENG TRUST CORP.,LTD All Rights Reserved. * * You may not use this file except in compliance with the License. *******************************************************************************/package com.demo.shiro.entity;import lombok.Data;import lombok.Getter;import lombok.Setter;/** * 角色实体 * * @author Yujiaqi * @date 2020/12/5 11:06 */@Data@Getter@Setterpublic class RoleEntity {    private Integer id;    private String role;    private String permissionurl;    public RoleEntity(){};        public RoleEntity(Integer id, String role, String permissionurl) {        this.id = id;        this.role = role;        this.permissionurl = permissionurl;    }}

实体类UserMapper

/******************************************************************************* * Package: com.demo.shiro.entity * Type:    UserEntity * Date:    2020/12/5 11:06 * * Copyright (c) 2020 HUANENG GUICHENG TRUST CORP.,LTD All Rights Reserved. * * You may not use this file except in compliance with the License. *******************************************************************************/package com.demo.shiro.entity;import jdk.nashorn.internal.objects.annotations.Constructor;import lombok.Data;import lombok.Getter;import lombok.Setter;/** * 用户实体 * * @author Yujiaqi * @date 2020/12/5 11:06 */@Data@Getter@Setterpublic class UserEntity {    private int id;    private String userName;    private String realName;    private String passWord;    private String role;    public UserEntity(){}        public UserEntity(int id, String userName, String realName, String passWord, String role) {        this.id = id;        this.userName = userName;        this.realName = realName;        this.passWord = passWord;        this.role = role;    }}
RoleMapper类
/******************************************************************************* * Package: com.demo.shiro.mapper * Type:    RoleMapper * Date:    2020/12/5 11:06 * * Copyright (c) 2020 HUANENG GUICHENG TRUST CORP.,LTD All Rights Reserved. * * You may not use this file except in compliance with the License. *******************************************************************************/package com.demo.shiro.mapper;import com.demo.shiro.entity.RoleEntity;import org.springframework.stereotype.Repository;import java.util.List;/** * TODO your comment * * @author Yujiaqi * @date 2020/12/5 11:06 */@Repositorypublic interface RoleMapper {    /**     * 查询所有角色表信息     * @return     */    List<RoleEntity> selectRoleAll();}
UserMapper类
/******************************************************************************* * Package: com.demo.shiro.mapper * Type:    UserMapper * Date:    2020/12/5 11:06 * * Copyright (c) 2020 HUANENG GUICHENG TRUST CORP.,LTD All Rights Reserved. * * You may not use this file except in compliance with the License. *******************************************************************************/package com.demo.shiro.mapper;import com.demo.shiro.entity.UserEntity;import org.apache.ibatis.annotations.Param;/** * 根据用户名查询数据 * * @author Yujiaqi * @date 2020/12/5 11:06 */public interface UserMapper {    UserEntity findByUsername(@Param(value = "userName") String userName);}
RoleMapper.xml
<?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.demo.shiro.mapper.RoleMapper">    <select id="selectRoleAll" resultType="com.demo.shiro.entity.RoleEntity">    SELECT      *    FROM        test.ROLE  </select></mapper>
UserMapper.xml
<?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.demo.shiro.mapper.UserMapper">    <select id="findByUsername" resultType="com.demo.shiro.entity.UserEntity">    SELECT        u.id,        u.user_name AS userName,        u.real_name AS realName,        u.PASSWORD,        u.role    FROM        test.USER u    WHERE        u.user_name = #{userName}  </select></mapper>
ShiroConfig配置类(项目启动时加载,配置页面登录拦截,登录权限)
/******************************************************************************* * Package: com.demo.shiro.config * Type:    ShiroConfig * Date:    2020/12/5 10:44 * * Copyright (c) 2020 HUANENG GUICHENG TRUST CORP.,LTD All Rights Reserved. * * You may not use this file except in compliance with the License. *******************************************************************************/package com.demo.shiro.config;import com.demo.shiro.entity.RoleEntity;import com.demo.shiro.mapper.RoleMapper;import org.apache.shiro.mgt.SecurityManager;import org.apache.shiro.spring.web.ShiroFilterFactoryBean;import org.apache.shiro.web.mgt.DefaultWebSecurityManager;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.util.HashMap;import java.util.List;/** * ShiroConfig * * @author Yujiaqi * @date 2020/12/5 10:44 */@Configurationpublic class ShiroConfig {        @Autowired    private MayiktRealm mayiktRealm;    @Autowired    private RoleMapper roleMapper;    /**     * @Configuration实际上就是@Component,将类注入到spring容器中      */        /**     * 配置shiroFilter     * @param securityManager     * @return     */    @Bean    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();        //设定登录页面        shiroFilterFactoryBean.setLoginUrl("/login");        shiroFilterFactoryBean.setSuccessUrl("/home");        HashMap<String, String> map = new HashMap<>();        //不需要登录,就可以访问接口        map.put("/mayiktOpenApi","anon");        //需要要登录才可以访问该接口,采用弹框的形式(authcBasic),表单模式(authc)        //要跳转到home页面的用户必须经过登录        map.put("/home","authc");//        //设定order接口只能order角色访问//        map.put("/order/*","roles[order]");//        //设定member接口只能member角色访问//        map.put("/member/*","roles[member]");        //通过数据库设计接口访问权限        List<RoleEntity> roleEntities = roleMapper.selectRoleAll();        roleEntities.forEach(t -> {            map.put(t.getPermissionurl(),"roles[" t.getRole() "]");        });        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");        shiroFilterFactoryBean.setSecurityManager(securityManager);        return shiroFilterFactoryBean;    }        @Bean    public SecurityManager securityManager(){        //将自定义Realm加进来        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();        securityManager.setRealm(mayiktRealm);        return securityManager;    }}
MayiktRealm类 需要继承AuthorizingRealm类
/******************************************************************************* * Package: com.demo.shiro.config * Type:    MayiktRealm * Date:    2020/12/5 10:59 * * Copyright (c) 2020 HUANENG GUICHENG TRUST CORP.,LTD All Rights Reserved. * * You may not use this file except in compliance with the License. *******************************************************************************/package com.demo.shiro.config;import com.alibaba.druid.util.StringUtils;import com.demo.shiro.entity.UserEntity;import com.demo.shiro.mapper.UserMapper;import org.apache.shiro.authc.AuthenticationException;import org.apache.shiro.authc.AuthenticationInfo;import org.apache.shiro.authc.AuthenticationToken;import org.apache.shiro.authc.SimpleAuthenticationInfo;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.stereotype.Component;import java.util.Arrays;import java.util.HashSet;/** * 配置拦截器 * * @author Yujiaqi * @date 2020/12/5 10:59 */@Componentpublic class MayiktRealm extends AuthorizingRealm {        @Autowired    private UserMapper userMapper;        /**     * 为当前登录成功的用户授予权限和分配角色。     * 在用户登录成功后,每次请求后台接口都需要先访问此方法,进行权限的验证     * @param principal     * @return     */    @Override    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {        String username = (String)principal.getPrimaryPrincipal();        UserEntity byUsername = userMapper.findByUsername(username);        if (byUsername == null){            return null;        }        String role = byUsername.getRole();        if (StringUtils.isEmpty(role)){            return null;        }        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();        //设置权限        HashSet<String> roles = new HashSet<>();        String[] split = role.split(",");        Arrays.stream(split).forEach(t ->{            roles.add(t);        });        //设置我们的权限        simpleAuthorizationInfo.setRoles(roles);        return simpleAuthorizationInfo;    }        /**     * 用来验证当前登录的用户,获取认证信息。     * @param authenticationToken     * @return     * @throws AuthenticationException     */    @Override    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)            throws AuthenticationException {        String userName = (String) authenticationToken.getPrincipal();        if (userName == null){            return null;        }        UserEntity byUsername = userMapper.findByUsername(userName);        if(byUsername == null){            return null;        }        //使用数据库db的密码验证用户输入的密码        //这里传入的password(这里是从数据库获取的)和token(filter中登录时生成的)中的password做对比,如果相同就允许登录,不相同就抛出异常。        SimpleAuthenticationInfo simpleAuthorizationInfo = new SimpleAuthenticationInfo(userName, byUsername.getPassWord(), byUsername.getRealName());        return simpleAuthorizationInfo;    }}
MD5Util类(进行密码加密)
/******************************************************************************* * Package: com.demo.shiro.config * Type:    MD5Util * Date:    2020/12/5 11:05 * * Copyright (c) 2020 HUANENG GUICHENG TRUST CORP.,LTD All Rights Reserved. * * You may not use this file except in compliance with the License. *******************************************************************************/package com.demo.shiro.config;import java.security.MessageDigest;/** * 用于密码加密 * * @author Yujiaqi * @date 2020/12/5 11:05 */public class MD5Util {        private static final String SALT = "yujiaqi";        public static String encode(String password) {        password = password   SALT;        MessageDigest md5 = null;        try {            md5 = MessageDigest.getInstance("MD5");        } catch (Exception e) {            throw new RuntimeException(e);        }        char[] charArray = password.toCharArray();        byte[] byteArray = new byte[charArray.length];        for (int i = 0; i < charArray.length; i  )            byteArray[i] = (byte) charArray[i];        byte[] md5Bytes = md5. digest( byteArray);        StringBuffer hexValue = new StringBuffer();        for (int i = 0; i < md5Bytes. length; i  ) {            int val = ((int) md5Bytes[i]) & 0xff;            if(val<16){                hexValue . append("0");            }            hexValue.append(Integer. toHexString(val));        }        return hexValue. toString();    }        public static void main(String[] args) {        System.out.println(encode("123456"));        //密码只能加密,不能解密,只能通过暴力破解    }}
IndexController登录入口类
/******************************************************************************* * Package: com.demo.shiro.controller * Type:    IndexController * Date:    2020/12/5 10:49 * * Copyright (c) 2020 HUANENG GUICHENG TRUST CORP.,LTD All Rights Reserved. * * You may not use this file except in compliance with the License. *******************************************************************************/package com.demo.shiro.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;/** * IndexController * * @author Yujiaqi * @date 2020/12/5 10:49 */@Controllerpublic class IndexController {        @RequestMapping("/home")    public String home(){        return "home";    }        @ResponseBody    @RequestMapping("/mayiktOpenApi")    public String mayikt(){        return "不需要任何登录和权限都可以访问到";    }        @ResponseBody    @RequestMapping("/unauthorized")    public String unauthorized(){        return "你的角色暂时没有该权限,无法访问该接口";    }    }
LoginController类
/******************************************************************************* * Package: com.demo.shiro.controller * Type:    LoginController * Date:    2020/12/5 13:54 * * Copyright (c) 2020 HUANENG GUICHENG TRUST CORP.,LTD All Rights Reserved. * * You may not use this file except in compliance with the License. *******************************************************************************/package com.demo.shiro.controller;import com.demo.shiro.config.MD5Util;import com.demo.shiro.entity.UserEntity;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.IncorrectCredentialsException;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.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import javax.servlet.http.HttpServletRequest;/** * TODO your comment * * @author Yujiaqi * @date 2020/12/5 13:54 */@Controllerpublic class LoginController {        @RequestMapping("/login")    public String login(){        return "login";    }        @RequestMapping("userLogin")    public String userLogin(UserEntity userEntity, Model model){        // 获取主题用户        Subject subject = SecurityUtils.getSubject();        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userEntity.getUserName(),                MD5Util.encode(userEntity.getPassWord()));        try{            //登录就会走MayiktRealm.doGetAuthenticationInfo方法验证用户名密码            subject.login(usernamePasswordToken);            model.addAttribute("userName" , userEntity.getUserName());            return "home";        }catch (IncorrectCredentialsException e){            e.printStackTrace();            model.addAttribute("error","账号密码错误!");            return "login";        }            }        }
MemberController类
/******************************************************************************* * Package: com.demo.shiro.controller * Type:    MemberController * Date:    2020/12/5 11:06 * * Copyright (c) 2020 HUANENG GUICHENG TRUST CORP.,LTD All Rights Reserved. * * You may not use this file except in compliance with the License. *******************************************************************************/package com.demo.shiro.controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/** * TODO your comment * * @author Yujiaqi * @date 2020/12/5 11:06 */@RestController@RequestMapping("/member")public class MemberController {        @RequestMapping("getMember")    public String getMember(){        return "getMember";    }        @RequestMapping("insertMember")    public String insertMember(){        return "insertMember";    }}
OrderController类
/******************************************************************************* * Package: com.demo.shiro.controller * Type:    OrderController * Date:    2020/12/5 11:06 * * Copyright (c) 2020 HUANENG GUICHENG TRUST CORP.,LTD All Rights Reserved. * * You may not use this file except in compliance with the License. *******************************************************************************/package com.demo.shiro.controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/** * TODO your comment * * @author Yujiaqi * @date 2020/12/5 11:06 */@RestController@RequestMapping("/order")public class OrderController {    @RequestMapping("insertOrder")    public String insertOrder(){        return "insertOrder";    }        @RequestMapping("updateOrder")    public String updateOrder(){        return "updateOrder";    }        @RequestMapping("deleteOrder")    public String deleteOrder(){        return "deleteOrder";    }}

前端页面

home.ftl

<html xmlns:th="http://www.thymeleaf.org"><head>    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>    <title>Insert title here</title></head><body>    <h1>恭喜您进入到管理系统<span th:text="${userName}"></span></h1>    <h3><a href="/order/insertOrder">insertOrder</a></h3>    <h3><a href="/order/updateOrder">updateOrder</a></h3>    <h3><a href="/order/deleteOrder">deleteOrder</a></h3>    <h3><a href="/member/insertMember">insertMember</a></h3>    <h3><a href=" /member/getMember" >getMember</a></h3></body></html>

login.ftl

<html xmlns:th="http://www.thymeleaf.org"><head>    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>    <title>Insert title here</title></head><body><h1>每特教育--权限控制登陆系统</h1><form action="/userLogin" method="post">    <span>用户名称:</span><input type= "text" name="userName"/><br>    <span>用户密码:</span><input type="password" name= "passWord"/><br>    <input type="submit" value="登陆" ></form>    <span style="color: red" th:text="${error}"></span></body></html>
ShiroConfig类为启动时加载

我们访问127.0.0.1:8080/mayiktOpenApi可以直接访问到,因为在ShiroConfig类配置了map.put("/mayiktOpenApi","anon"); anon不需要登录,就可以访问接口

我们访问127.0.0.1:8080/home就会进入到127.0.0.1:8080/login登录页面,因为我们进行了拦截在ShiroConfig类配置了map.put("/home","authc");//authc需要要进行登录验证才可以访问到接口

我们输入错误的用户名密码进行登录:会进入到doGetAuthenticationInfo方法用来验证当前登录的用户,获取认证信息。用户名密码正确登录成功,否则登录失败,并且后台会报错

下面我们输入正确的用户名密码(登录成功)

我们登录的是yujiaqi_admin用户,他的权限为member,order,意思是我们可以访问/member/*接口也可以访问/order/*接口

接下来我们点击insertOrder(访问成功),会调用doGetAuthorizationInfo方法为当前登录成功的用户授予权限和分配角色。 在用户登录成功后,每次请求后台接口都需要先访问此方法,进行权限的验证

点击insertMember(访问成功)

其他访问类似

接下来我们登录yujiaqi_order用户,他的权限order说明mayikt_order用户,只能访问/order/*接口

我们点击insertOrder(访问成功)

点击insertMember(访问失败,没有权限)

到此我们就完成了shiro安全框架的整合,可以进行登录拦截,也可以给用户进行权限的分配

博主也是初学shiro框架,后续还会有Shiro整合Jwt实现前后端分离,Shiro整合0auth2实现权限认证正在学习,希望留下你的小心心

想当年我们也是在道上混的,只不过后来改邪归正了

来源:https://www.icode9.com/content-4-777501.html

(0)

相关推荐

  • spring boot 整合 shiro 权限框架

    spring boot 整合 shiro 权限框架

  • 你了解JSON吗?

    写目录 1. 什么是 JSON 2. JSON 语法规则 3. JSON 与 JS 对象的关系 4. JSON 和 JS 对象互转 5. 使用Jackson数据交互 1.导入依赖 2.配置web.xm ...

  • 让一无所知的你 一篇看懂公路车变速

    作者: 高端单车知识 2018-03-14 10:14:55 33301 评论:1 关于公路车变速,BikeXe已经有多篇介绍,今天这篇囊括市场上主流的三种变速:Shimano.SRAM和Campag ...

  • 抓涨停难在第二天抽身而退,仅此这篇看懂学会抓涨停能全身而退!

    几乎抓涨停是每个投资者心目中的梦想,幻想每天能够抓到一只涨停股,那么奔上小康生活指日可待.然而,抓涨停板并不难,股市超过2000只股票,网上也那么多实用的抓涨停板绝技,难就难在如何第二天抽身而退. 凡 ...

  • 很多人都在买的「临期食品」是什么?食品过期了还能吃吗?一篇看懂

    原价 88 的瑞*莲巧克力,现价 29 元 原价 126 的进口曲奇饼干,现价 12.6 元 -- 因为临近保质期限,这些食品被低折扣售卖,这类食品被叫做「临期食品」. 图片来源:豆瓣网友@星星 在很 ...

  • 混淆不清的戈壁料与戈壁玉,一篇看懂!

    说到戈壁料与戈壁玉,估计有不少朋友会问,戈壁料与戈壁玉是不是同一种东西吗?事实上两者是不同的,现在浅谈一下两者之间的区别. 二者区别 戈壁料 戈壁玉 在玉石中,除了戈壁玉,还有一种戈壁料,很多人将二者 ...

  • 一篇看懂,非暴力沟通·亲子营(升级版)报名指南

    学习期间,老师每天会指定章节,如P8-P15.阅读打卡 为确保一致性,学习方便.尽可能提前准备纸质书籍(电子书或其他工具页码不一致) (三)  怎样学习? A"术":方法理论的学习 ...

  • 一文看懂免税店研究框架

    海南板块成了面霸集中营. 很多人不解,海南自由贸易港的方案那么给力,海南概念股怎么还是走成了这个样子? 老读者此时应该能脱口而出,因为, 买预期,卖现实. 5月份海南板块整体上涨11%,跑赢大盘12% ...

  • 一篇看懂古代所有屋顶!魔性动图一目了然!

    耳朵里的博物馆 和娃去博物馆,不懂找我- 963篇原创内容 公众号 你好呀,我是朵朵! 中国古建筑是个神奇的存在 模样纷繁复杂 大家都很熟悉 但是好像又都很陌生 每当看到一个不认识的屋顶 你会说这是- ...

  • 一篇看懂为什么禁烟令不合理

    图:Tatsurokiuchi 公共场合禁烟问题,这几年一聊就炸.昨天文章里我说过不想再谈这个话题,因为说了很多次,懂的人嫌啰嗦,不懂的说再多也没用.但更多人还是更关心为什么说餐厅是否禁烟应该餐厅老板 ...

  • 最全入门级科普!北美超市烘焙食材一篇看懂!

    看着别人疫情在家,自己做蛋糕,做甜品 自己也有一颗蠢蠢欲动的心 但是超市里琳琅满目的商品,搞得人晕头转向. 光面粉就有三四五六种,真的有点劝退 我狠下决心, 把这些复杂的种类都搞明白了, 一篇告诉你到 ...