转:30分钟了解Springboot整合Shiro
前言:06年7月的某日,不才创作了一篇题为《30分钟学会如何使用Shiro》的文章。不在意之间居然斩获了22万的阅读量,许多人因此加了我的联系方式咨询源码工程,只可惜当时并没有专门保留。2年后的今天在机缘巧合之下,我又重拾此话题。希望能带给小伙伴们在Springboot下如何使用Shiro,当然若各位感兴趣我还希望之后能创作一些与它有关的更加深入的知识。作为一个知识分享型博主,我希望能够帮助大家尽快上手。因此我尽可能去除了与整合无关的干扰因素,方便大家只要按照文章的思路就一定能有所收获。
项目结构截图:
项目在结构上没有任何特殊之处,基本就是MVC的传统结构重点需要关注的是3个Entity类、2个Controller类和1个Config类。
首先,提供pom的完整文档结构:
- <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>
- <groupId>com.learnhow.springboot</groupId>
- <artifactId>web</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- <packaging>jar</packaging>
- <name>web</name>
- <url>http://maven.apache.org</url>
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>2.0.4.RELEASE</version>
- <relativePath /> <!-- lookup parent from repository -->
- </parent>
- <properties>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- <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-test</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-jpa</artifactId>
- </dependency>
- <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>org.springframework.boot</groupId>
- <artifactId>spring-boot-devtools</artifactId>
- <optional>true</optional>
- </dependency>
- </dependencies>
- <build>
- <plugins>
- <plugin>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-maven-plugin</artifactId>
- <configuration>
- <fork>true</fork>
- </configuration>
- </plugin>
- </plugins>
- </build>
- </project>
其次,创建数据库和表结构。由于我们采用jpa作为数据库持久层框架,因此我们将建表的任务交给框架自动完成,我们只需要在entity中写清楚对应关系即可。
在yml文件中配置自动建表,并且写好entity后,执行项目,jpa会执行建表语句。
- CREATE DATABASE enceladus; // enceladus是数据库的名称
application.yml
- server:
- port: 8088
- spring:
- application:
- name: shiro
- datasource:
- url: jdbc:mysql://192.168.31.37:3306/enceladus
- username: root
- password: 12345678
- driver-class-name: com.mysql.jdbc.Driver
- jpa:
- database: mysql
- showSql: true
- hibernate:
- ddlAuto: update
- properties:
- hibernate:
- dialect: org.hibernate.dialect.MySQL5Dialect
- format_sql: true
最基础的Shiro配置至少需要三张主表分别代表用户(user)、角色(role)、权限(permission),用户和角色,角色与权限之间都是ManyToMany的对应关系,不熟悉实体对应关系的小伙伴可以先去熟悉一下Hibernate。
User.java
- package com.learnhow.springboot.web.entity;
- import java.io.Serializable;
- import java.util.List;
- import javax.persistence.Entity;
- import javax.persistence.FetchType;
- import javax.persistence.GeneratedValue;
- import javax.persistence.Id;
- import javax.persistence.JoinColumn;
- import javax.persistence.JoinTable;
- import javax.persistence.ManyToMany;
- import javax.persistence.Table;
- /**
- * 用户表
- */
- @Entity
- @Table(name = "user_t")
- public class User implements Serializable {
- private static final long serialVersionUID = -3320971805590503443L;
- @Id
- @GeneratedValue
- private long id;
- private String username;
- private String password;
- private String salt;
- @ManyToMany(fetch = FetchType.EAGER)
- @JoinTable(name = "user_role_t", joinColumns = { @JoinColumn(name = "uid") }, inverseJoinColumns = {
- @JoinColumn(name = "rid") })
- private List<SysRole> roles;
- public long getId() {
- return id;
- }
- public void setId(long id) {
- this.id = id;
- }
- public String getUsername() {
- return username;
- }
- public void setUsername(String username) {
- this.username = username;
- }
- public String getPassword() {
- return password;
- }
- public void setPassword(String password) {
- this.password = password;
- }
- public String getSalt() {
- return salt;
- }
- public void setSalt(String salt) {
- this.salt = salt;
- }
- public List<SysRole> getRoles() {
- return roles;
- }
- public void setRoles(List<SysRole> roles) {
- this.roles = roles;
- }
- public String getCredentialsSalt() {
- return username + salt + salt;
- }
- @Override
- public String toString() {
- return "User [id=" + id + ", username=" + username + "]";
- }
- }
User
SysRole.java
- package com.learnhow.springboot.web.entity;
- import java.io.Serializable;
- import java.util.List;
- import javax.persistence.Entity;
- import javax.persistence.FetchType;
- import javax.persistence.GeneratedValue;
- import javax.persistence.Id;
- import javax.persistence.JoinColumn;
- import javax.persistence.JoinTable;
- import javax.persistence.ManyToMany;
- import javax.persistence.Table;
- /**
- * 角色表
- */
- @Entity
- @Table(name = "role_t")
- public class SysRole implements Serializable {
- private static final long serialVersionUID = -8687790154329829056L;
- @Id
- @GeneratedValue
- private Integer id;
- private String role;
- @ManyToMany(fetch = FetchType.EAGER)
- @JoinTable(name = "role_permission_t", joinColumns = { @JoinColumn(name = "rid") }, inverseJoinColumns = {
- @JoinColumn(name = "pid") })
- private List<SysPermission> permissions;
- @ManyToMany
- @JoinTable(name = "user_role_t", joinColumns = { @JoinColumn(name = "rid") }, inverseJoinColumns = {
- @JoinColumn(name = "uid") })
- private List<User> users;
- public Integer getId() {
- return id;
- }
- public void setId(Integer id) {
- this.id = id;
- }
- public String getRole() {
- return role;
- }
- public void setRole(String role) {
- this.role = role;
- }
- public List<SysPermission> getPermissions() {
- return permissions;
- }
- public void setPermissions(List<SysPermission> permissions) {
- this.permissions = permissions;
- }
- public List<User> getUsers() {
- return users;
- }
- public void setUsers(List<User> users) {
- this.users = users;
- }
- }
UserRole
SysPermission.java
- package com.learnhow.springboot.web.entity;
- import java.io.Serializable;
- import java.util.List;
- import javax.persistence.Entity;
- import javax.persistence.GeneratedValue;
- import javax.persistence.Id;
- import javax.persistence.JoinColumn;
- import javax.persistence.JoinTable;
- import javax.persistence.ManyToMany;
- import javax.persistence.Table;
- /**
- * 权限表
- */
- @Entity
- @Table(name = "permission_t")
- public class SysPermission implements Serializable {
- private static final long serialVersionUID = 353629772108330570L;
- @Id
- @GeneratedValue
- private Integer id;
- private String name;
- @ManyToMany
- @JoinTable(name = "role_permission_t", joinColumns = { @JoinColumn(name = "pid") }, inverseJoinColumns = {
- @JoinColumn(name = "rid") })
- private List<SysRole> roles;
- public Integer getId() {
- return id;
- }
- public void setId(Integer id) {
- this.id = id;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public List<SysRole> getRoles() {
- return roles;
- }
- public void setRoles(List<SysRole> roles) {
- this.roles = roles;
- }
- }
SysPermission
在注明对应关系以后,jpa会帮助我们创建3张实体表和2张中间表:
最后我们还需要初始化一些基础数据:
- INSERT INTO `permission_t` VALUES (1, 'Retrieve');
- INSERT INTO `permission_t` VALUES (2, 'Create');
- INSERT INTO `permission_t` VALUES (3, 'Update');
- INSERT INTO `permission_t` VALUES (4, 'Delete');
- INSERT INTO `role_t` VALUES (1, 'guest');
- INSERT INTO `role_t` VALUES (2, 'user');
- INSERT INTO `role_t` VALUES (3, 'admin');
- INSERT INTO `role_permission_t` VALUES (1, 1);
- INSERT INTO `role_permission_t` VALUES (1, 2);
- INSERT INTO `role_permission_t` VALUES (2, 2);
- INSERT INTO `role_permission_t` VALUES (3, 2);
- INSERT INTO `role_permission_t` VALUES (1, 3);
- INSERT INTO `role_permission_t` VALUES (2, 3);
- INSERT INTO `role_permission_t` VALUES (3, 3);
- INSERT INTO `role_permission_t` VALUES (4, 3);
至此,前期的准备工作已经完成。下面为了让Shiro能够在项目中生效我们需要通过代码的方式提供配置信息。Shiro的安全管理提供了两个层面的控制:(1)用户认证:需要用户通过登陆证明你是你自己。(2)权限控制:在证明了你是你自己的基础上系统为当前用户赋予权限。后者我们已经在数据库中完成了大部分配置。
用户认证的常规手段就是登陆认证,在目前的情况下我们认为只有用户自己知道登陆密码。不过Shiro为我们做的更多,它还提供了一套能够很方便我们使用的密码散列算法。因为普通的散列技巧可以很容易的通过暴力手段破解,我们可以在散列的过程中加入一定的算法复杂度(增加散列次数与Salt)从而解决这样的问题。
- import org.apache.shiro.crypto.RandomNumberGenerator;
- import org.apache.shiro.crypto.SecureRandomNumberGenerator;
- import org.apache.shiro.crypto.hash.SimpleHash;
- import org.apache.shiro.util.ByteSource;
- import com.learnhow.springboot.web.entity.User;
- public class PasswordHelper {
- private RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
- public static final String ALGORITHM_NAME = "md5"; // 基础散列算法
- public static final int HASH_ITERATIONS = 2; // 自定义散列次数
- public void encryptPassword(User user) {
- // 随机字符串作为salt因子,实际参与运算的salt我们还引入其它干扰因子
- user.setSalt(randomNumberGenerator.nextBytes().toHex());
- String newPassword = new SimpleHash(ALGORITHM_NAME, user.getPassword(),
- ByteSource.Util.bytes(user.getCredentialsSalt()), HASH_ITERATIONS).toHex();
- user.setPassword(newPassword);
- }
- }
这个类帮助我们解决用户注册的密码散列问题,当然我们还需要使用同样的算法来保证在登陆的时候密码能够被散列成相同的字符串。如果两次散列的结果不同系统就无法完成密码比对,因此在计算散列因子的时候我们不能引入变量,例如我们可以将username作为salt因子加入散列算法,但是不能选择password或datetime,具体原因各位请手动测试。
另外为了帮助Shiro能够正确为当前登陆用户做认证和赋权,我们需要实现自定义的Realm。具体来说就是实现doGetAuthenticationInfo和doGetAuthorizationInfo,这两个方法前者负责登陆认证后者负责提供一个权限信息。
- 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.apache.shiro.util.ByteSource;
- import org.springframework.beans.factory.annotation.Autowired;
- import com.learnhow.springboot.web.entity.SysPermission;
- import com.learnhow.springboot.web.entity.SysRole;
- import com.learnhow.springboot.web.entity.User;
- import com.learnhow.springboot.web.service.UserService;
- public class EnceladusShiroRealm extends AuthorizingRealm {
- @Autowired
- private UserService userService;
- @Override
- protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
- SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
- String username = (String) principals.getPrimaryPrincipal();
- User user = userService.findUserByName(username);
- for (SysRole role : user.getRoles()) {
- authorizationInfo.addRole(role.getRole());
- for (SysPermission permission : role.getPermissions()) {
- authorizationInfo.addStringPermission(permission.getName());
- }
- }
- return authorizationInfo;
- }
- @Override
- protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
- String username = (String) token.getPrincipal();
- User user = userService.findUserByName(username);
- if (user == null) {
- return null;
- }
- SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(),
- ByteSource.Util.bytes(user.getCredentialsSalt()), getName());
- return authenticationInfo;
- }
- }
还记得前面我们说过,认证的时候我们需要提供相同的散列算法吗?可是在上面的代码里,我们并未提供。那么Shiro是怎么做的呢?AuthorizingRealm是一个抽象类,我们会在另外的配置文件里向它提供基础算法与散列次数这两个变量。
- import java.util.HashMap;
- import java.util.Map;
- import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
- import org.apache.shiro.mgt.SecurityManager;
- import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
- import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- @Configuration
- public class ShiroConfig {
- @Bean
- public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
- ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
- shiroFilterFactoryBean.setSecurityManager(securityManager);
- Map<String, String> filterChainDefinitionMap = new HashMap<String, String>();
- shiroFilterFactoryBean.setLoginUrl("/login");
- shiroFilterFactoryBean.setUnauthorizedUrl("/unauthc");
- shiroFilterFactoryBean.setSuccessUrl("/home/index");
- filterChainDefinitionMap.put("/*", "anon");
- filterChainDefinitionMap.put("/authc/index", "authc");
- filterChainDefinitionMap.put("/authc/admin", "roles[admin]");
- filterChainDefinitionMap.put("/authc/renewable", "perms[Create,Update]");
- filterChainDefinitionMap.put("/authc/removable", "perms[Delete]");
- shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
- return shiroFilterFactoryBean;
- }
- @Bean
- public HashedCredentialsMatcher hashedCredentialsMatcher() {
- HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
- hashedCredentialsMatcher.setHashAlgorithmName(PasswordHelper.ALGORITHM_NAME); // 散列算法
- hashedCredentialsMatcher.setHashIterations(PasswordHelper.HASH_ITERATIONS); // 散列次数
- return hashedCredentialsMatcher;
- }
- @Bean
- public EnceladusShiroRealm shiroRealm() {
- EnceladusShiroRealm shiroRealm = new EnceladusShiroRealm();
- shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); // 原来在这里
- return shiroRealm;
- }
- @Bean
- public SecurityManager securityManager() {
- DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
- securityManager.setRealm(shiroRealm());
- return securityManager;
- }
- @Bean
- public PasswordHelper passwordHelper() {
- return new PasswordHelper();
- }
- }
接下来,我们将目光集中到上文的shirFilter方法中。Shiro通过一系列filter来控制访问权限,并在它的内部为我们预先定义了多个过滤器,我们可以直接通过字符串配置这些过滤器。
常用的过滤器如下:
authc:所有已登陆用户可访问
roles:有指定角色的用户可访问,通过[ ]指定具体角色,这里的角色名称与数据库中配置一致
perms:有指定权限的用户可访问,通过[ ]指定具体权限,这里的权限名称与数据库中配置一致
anon:所有用户可访问,通常作为指定页面的静态资源时使用
为了测试方便我们不引入页面配置直接通过rest方式访问
不受权限控制访问的地址
- 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.beans.factory.annotation.Autowired;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestParam;
- import org.springframework.web.bind.annotation.RestController;
- import com.learnhow.springboot.web.PasswordHelper;
- import com.learnhow.springboot.web.entity.User;
- import com.learnhow.springboot.web.service.UserService;
- @RestController
- @RequestMapping
- public class HomeController {
- @Autowired
- private UserService userService;
- @Autowired
- private PasswordHelper passwordHelper;
- @GetMapping("login")
- public Object login() {
- return "Here is Login page";
- }
- @GetMapping("unauthc")
- public Object unauthc() {
- return "Here is Unauthc page";
- }
- @GetMapping("doLogin")
- public Object doLogin(@RequestParam String username, @RequestParam String password) {
- UsernamePasswordToken token = new UsernamePasswordToken(username, password);
- Subject subject = SecurityUtils.getSubject();
- try {
- subject.login(token);
- } catch (IncorrectCredentialsException ice) {
- return "password error!";
- } catch (UnknownAccountException uae) {
- return "username error!";
- }
- User user = userService.findUserByName(username);
- subject.getSession().setAttribute("user", user);
- return "SUCCESS";
- }
- @GetMapping("register")
- public Object register(@RequestParam String username, @RequestParam String password) {
- User user = new User();
- user.setUsername(username);
- user.setPassword(password);
- passwordHelper.encryptPassword(user);
- userService.saveUser(user);
- return "SUCCESS";
- }
- }
需要指定权限可以访问的地址
- import org.apache.shiro.SecurityUtils;
- import org.apache.shiro.subject.Subject;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RestController;
- import com.learnhow.springboot.web.entity.User;
- @RestController
- @RequestMapping("authc")
- public class AuthcController {
- @GetMapping("index")
- public Object index() {
- Subject subject = SecurityUtils.getSubject();
- User user = (User) subject.getSession().getAttribute("user");
- return user.toString();
- }
- @GetMapping("admin")
- public Object admin() {
- return "Welcome Admin";
- }
- // delete
- @GetMapping("removable")
- public Object removable() {
- return "removable";
- }
- // insert & update
- @GetMapping("renewable")
- public Object renewable() {
- return "renewable";
- }
- }
详情请参考完整代码:
https://gitee.com/jingxin168/jingxin.git
上手调试:注册用户
转:30分钟了解Springboot整合Shiro的更多相关文章
- 30分钟了解Springboot整合Shiro
项目结构截图: 项目在结构上没有任何特殊之处,基本就是MVC的传统结构重点需要关注的是3个Entity类.2个Controller类和1个Config类. 首先,提供pom的完整文档结构: <p ...
- 30分钟学会如何使用Shiro
本篇内容大多总结自张开涛的<跟我学Shiro>原文地址:http://jinnianshilongnian.iteye.com/blog/2018936 我并没有全部看完,只是选择了一部分 ...
- SpringBoot整合Shiro实现权限控制,验证码
本文介绍 SpringBoot 整合 shiro,相对于 Spring Security 而言,shiro 更加简单,没有那么复杂. 目前我的需求是一个博客系统,有用户和管理员两种角色.一个用户可能有 ...
- SpringBoot整合Shiro权限框架实战
什么是ACL和RBAC ACL Access Control list:访问控制列表 优点:简单易用,开发便捷 缺点:用户和权限直接挂钩,导致在授予时的复杂性,比较分散,不便于管理 例子:常见的文件系 ...
- SpringBoot系列十二:SpringBoot整合 Shiro
声明:本文来源于MLDN培训视频的课堂笔记,写在这里只是为了方便查阅. 1.概念:SpringBoot 整合 Shiro 2.具体内容 Shiro 是现在最为流行的权限认证开发框架,与它起名的只有最初 ...
- SpringBoot 整合 Shiro 密码登录与邮件验证码登录(多 Realm 认证)
导入依赖(pom.xml) <!--整合Shiro安全框架--> <dependency> <groupId>org.apache.shiro</group ...
- 补习系列(6)- springboot 整合 shiro 一指禅
目标 了解ApacheShiro是什么,能做什么: 通过QuickStart 代码领会 Shiro的关键概念: 能基于SpringBoot 整合Shiro 实现URL安全访问: 掌握基于注解的方法,以 ...
- SpringBoot整合Shiro实现基于角色的权限访问控制(RBAC)系统简单设计从零搭建
SpringBoot整合Shiro实现基于角色的权限访问控制(RBAC)系统简单设计从零搭建 技术栈 : SpringBoot + shiro + jpa + freemark ,因为篇幅原因,这里只 ...
- springboot整合Shiro功能案例
Shiro 核心功能案例讲解 基于SpringBoot 有源码 从实战中学习Shiro的用法.本章使用SpringBoot快速搭建项目.整合SiteMesh框架布局页面.整合Shiro框架实现用身份认 ...
随机推荐
- Windows API-----top level window
原文地址: http://blog.163.com/cumt_xl/blog/static/19071504420136911838683/ Q: What is a top-level window ...
- TextView的跑马灯效果(AS开发实战第二章学习笔记)
TextView的跑马灯效果跑马灯用到的属性与方法说明singleLine 指定文本是否单行显示ellipsize 指定文本超出范围后的省略方式focusable 指定是否获得焦点,跑马灯效果要求设置 ...
- springlog记录
在servlet.xml加入 <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-aut ...
- pt-variable-advisor(percona toolkit)
pt-variable-advisor是一款分析参数,并且给出参数设置建议的一款PT工具,基本语法 pt-variable-advisor [OPTIONS] [DSN] 如下我们可以获取本地参数的一 ...
- SVN升级到1.8后 Upgrade working copy
SVN升级到1.8后没法用了,不能提交,提示说要SVN Upgrade working copy, 但是半天在根目录和.svn所在文件夹上面右键都没有找到这个菜单. 坑爹的…… 最后找到解决办法是:重 ...
- linux(centos)设置tomcat开机启动
方法一: linux 下tomcat开机自启动修改Tomcat/bin/startup.sh 为: export JAVA_HOME=/usr/java/j2sdk1.4.2_08 export CL ...
- c# 知识学习
1.C#基础知识梳理系列 2.详解C#委托,事件与回调函数 3.C#制作Windows service
- python接口自动化4-绕过验证码登录(cookie) (转载)
前言 有些登录的接口会有验证码:短信验证码,图形验证码等,这种登录的话验证码参数可以从后台获取的(或者查数据库最直接). 获取不到也没关系,可以通过添加cookie的方式绕过验证码. 一.抓登录coo ...
- laravel 使用EasyWechat 3分钟完成微信支付(以APP支付为例)
上一篇写了支付宝支付,然后这段时间我又把微信支付给接上了,作为萌新的我还是很有成就感的,哈哈~~好了,该写正事了. 第一步:创建应用及配配置 首先到微信的官方平台注册应用https://pay.we ...
- Jmeter(一)工具的简单介绍(z)
一.JMeter介绍 Apache JMeter是100%纯JAVA桌面应用程序,被设计为用于测试客户端/服务端结构的软件(例如web应用程序).它可以用来测试静态和动态资源的性能,例如:静态文件,J ...