搜索词>>fetch 耗时0.0020
  • ajax和axios、fetch 区别

    1.jQuery ajax$.ajax({ type: 'POST', url: url, data: data, dataType: dataType, success: function () {},1.jQuery ajax$.ajax({ type: 'POST', url: url, data: data, dataType: dataType, success: function () {}, error: function () {} });传统 Ajax 指的是 XMLHttpRequest(XHR), 最早出现的发送后端请求技术,隶属于原始js中,核心使用XMLHttpRequest对象,多个请求之间如果有先后关系的话,就会出现回调地狱。JQuery ajax 是对原生XHR的封装,除此以外还增添了对JSONP的支持。经过多年的更新维护,真的已经是非常的方便了,优点无需多言;如果是硬要举出几个缺点,那可能只有:1.本身是针对MVC的编程,不符合现在前端MVVM的浪潮2.基于原生的XHR开发,XHR本身的架构不清晰。3.JQuery整个项目太大,单纯使用ajax却要引入整个JQuery非常的不合理(采取个性化打包的方案又不能享受CDN服务)4.不符合关注分离(Separation of Concerns)的原则5.配置和调用方式非常混乱,而且基于事件的异步模型不友好。PS:MVVM(Model-View-ViewModel), 源自于经典的 Model–View–Controller(MVC)模式。MVVM 的出现促进了 GUI 前端开发与后端业务逻辑的分离,极大地提高了前端开发效率。MVVM 的核心是 ViewModel 层,它就像是一个中转站(value converter),负责转换 Model 中的数据对象来让数据变得更容易管理和使用,该层向上与视图层进行双向数据绑定,向下与 Model 层通过接口请求进行数据交互,起呈上启下作用。View 层展现的不是 Model 层的数据,而是 ViewModel 的数据,由 ViewModel 负责与 Model 层交互,这就完全解耦了 View 层和 Model 层,这个解耦是至关重要的,它是前后端分离方案实施的最重要一环。如下图所示:​2.axiosaxios({ method: 'post', url: '/user/12345', data: { firstName: 'Fred', lastName: 'Flintstone' } }) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); });Vue2.0之后,尤雨溪推荐大家用axios替换JQuery ajax,想必让axios进入了很多人的目光中。axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,本质上也是对原生XHR的封装,只不过它是Promise的实现版本,符合最新的ES规范,它本身具有以下特征:1.从浏览器中创建 XMLHttpRequest2.支持 Promise API3.客户端支持防止CSRF4.提供了一些并发请求的接口(重要,方便了很多的操作)5.从 node.js 创建 http 请求6.拦截请求和响应7.转换请求和响应数据8.取消请求9.自动转换JSON数据PS:防止CSRF:就是让你的每个请求都带一个从cookie中拿到的key, 根据浏览器同源策略,假冒的网站是拿不到你cookie中得key的,这样,后台就可以轻松辨别出这个请求是否是用户在假冒网站上的误导输入,从而采取正确的策略。3.fetchtry { let response = await fetch(url); let data = response.json(); console.log(data); } catch(e) { console.log("Oops, error", e); }fetch号称是AJAX的替代品,是在ES6出现的,使用了ES6中的promise对象。Fetch是基于promise设计的。Fetch的代码结构比起ajax简单多了,参数有点像jQuery ajax。但是,一定记住fetch不是ajax的进一步封装,而是原生js,没有使用XMLHttpRequest对象。fetch的优点:1.符合关注分离,没有将输入、输出和用事件来跟踪的状态混杂在一个对象里2.更好更方便的写法坦白说,上面的理由对我来说完全没有什么说服力,因为不管是Jquery还是Axios都已经帮我们把xhr封装的足够好,使用起来也足够方便,为什么我们还要花费大力气去学习fetch?我认为fetch的优势主要优势就是:1.语法简洁,更加语义化 2.基于标准 Promise 实现,支持 async/await 3.同构方便,使用 [isomorphic-fetch](https://github.com/matthew-andrews/isomorphic-fetch) 4.更加底层,提供的API丰富(request, response) 5.脱离了XHR,是ES规范里新的实现方式最近在使用fetch的时候,也遇到了不少的问题:fetch是一个低层次的API,你可以把它考虑成原生的XHR,所以使用起来并不是那么舒服,需要进行封装。例如:1)fetch只对网络请求报错,对400,500都当做成功的请求,服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject。 2)fetch默认不会带cookie,需要添加配置项: fetch(url, {credentials: 'include'}) 3)fetch不支持abort,不支持超时控制,使用setTimeout及Promise.reject的实现的超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费 4)fetch没有办法原生监测请求的进度,而XHR可以总结axios既提供了并发的封装,也没有fetch的各种问题,而且体积也较小,当之无愧现在最应该选用的请求的方式。
  • spring boot Jersey基于角色的安全控制使用JAX-RS的注解

    Spring Boot 基于角色的安全控制使用JAX-RS的注解,spring boot,Jersey<h2>一、摘要说明</h2>     学习使用spring boot和Jersey框架来创建JAX-RS 2.0 REST APIs,并且使用JAX-RS注解来添加基于角色的安全控制。例如注解<code>@PermitAll</code>, <code>@RolesAllowed</code> 或者 <code>@DenyAll。</code><br />    <br />     本博客学习纲要: <ul> <li>项目结构    </li> <li>创建REST api</li> <li>使用jax-rs注释的安全REST api</li> <li>使用jax-rs容器请求过滤器编写安全过滤器</li> <li>一个例子演示</li> </ul> <h2>二、项目结构</h2> 本教程中创建的应用程序的项目结构如下:<br /> <img alt="项目结构" class="img-thumbnail" src="/assist/images/blog/1d2312c3dc4d48f79fb04f5094b9a000.png" /> <h2>三、创建 REST APIs</h2> 1.去 Spring Initializr网站,创建一个spring boot项目并且添加 Jersey (JAX-RS) 依赖<br /> <img alt="创建项目" class="img-thumbnail" src="/assist/images/blog/27643e1e52c3445ca3c603d6184b69bb.png" />2.导入项目到eclipse中<br /> 以zip文件的格式生成项目。在你电脑的某个地方把它取出来。将该项目作为“Existing maven application”导入eclipse。<br /> <br /> 3.检查maven的依赖<br /> 检查maven的依赖文件中必须有<strong>spring-boot-starter-jersey</strong> <pre> <code class="language-xml"><dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jersey</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies></code></pre> 4.创建 REST APIs<br /> 现在创建一些jax-rs资源,我们将访问测试阶段。我已经创建了UserResource类。<br /> <br /> <strong>UserResource.java</strong> <pre> <code class="language-java"> import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.Response; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlRootElement; @XmlAccessorType(XmlAccessType.NONE) @XmlRootElement(name = "users") @Path("/users") public class UserResource { private static Map<Integer, User> DB = new HashMap<>(); @GET @Produces("application/json") public Users getAllUsers() { Users users = new Users(); users.setUsers(new ArrayList<>(DB.values())); return users; } @POST @Consumes("application/json") public Response createUser(User user) throws URISyntaxException { if(user.getFirstName() == null || user.getLastName() == null) { return Response.status(400).entity("Please provide all mandatory inputs").build(); } user.setId(DB.values().size()+1); user.setUri("/user-management/"+user.getId()); DB.put(user.getId(), user); return Response.status(201).contentLocation(new URI(user.getUri())).build(); } @GET @Path("/{id}") @Produces("application/json") public Response getUserById(@PathParam("id") int id) throws URISyntaxException { User user = DB.get(id); if(user == null) { return Response.status(404).build(); } return Response .status(200) .entity(user) .contentLocation(new URI("/user-management/"+id)).build(); } @PUT @Path("/{id}") @Consumes("application/json") @Produces("application/json") public Response updateUser(@PathParam("id") int id, User user) throws URISyntaxException { User temp = DB.get(id); if(user == null) { return Response.status(404).build(); } temp.setFirstName(user.getFirstName()); temp.setLastName(user.getLastName()); DB.put(temp.getId(), temp); return Response.status(200).entity(temp).build(); } @DELETE @Path("/{id}") public Response deleteUser(@PathParam("id") int id) throws URISyntaxException { User user = DB.get(id); if(user != null) { DB.remove(user.getId()); return Response.status(200).build(); } return Response.status(404).build(); } static { User user1 = new User(); user1.setId(1); user1.setFirstName("John"); user1.setLastName("Wick"); user1.setUri("/user-management/1"); User user2 = new User(); user2.setId(2); user2.setFirstName("Harry"); user2.setLastName("Potter"); user2.setUri("/user-management/2"); DB.put(user1.getId(), user1); DB.put(user2.getId(), user2); } }</code></pre> <strong>Users.java</strong> <pre> <code class="language-java"> import java.util.ArrayList; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; @XmlAccessorType(XmlAccessType.NONE) @XmlRootElement(name = "users") public class Users { @XmlElement(name="user") private ArrayList<User> users; public ArrayList<User> getUsers() { return users; } public void setUsers(ArrayList<User> users) { this.users = users; } }</code></pre> <strong>User.java</strong> <pre> <code class="language-java"> import java.io.Serializable; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; @XmlAccessorType(XmlAccessType.NONE) @XmlRootElement(name = "user") public class User implements Serializable { private static final long serialVersionUID = 1L; @XmlAttribute(name = "id") private int id; @XmlAttribute(name="uri") private String uri; @XmlElement(name = "firstName") private String firstName; @XmlElement(name = "lastName") private String lastName; // Getters and Setters }</code></pre> 5.配置Jersey<br /> 现在我们有了一个jax-rs资源,我们希望从spring启动应用程序中访问它,其中包括Jersey依赖项。让我们将这个资源注册为泽西资源。 <pre> <code class="language-java">import org.glassfish.jersey.server.ResourceConfig; import org.springframework.stereotype.Component; @Component public class JerseyConfig extends ResourceConfig { public JerseyConfig() { register(SecurityFilter.class); register(UserResource.class); } }</code></pre>   <ul> <li>查看@component注释。它使这个类可以注册,而spring引导自动扫描源文件夹中的java类。</li> <li>资源econfig提供了简化jax-rs组件注册的高级功能。</li> <li>SecurityFilter类是实际的auth细节处理器,我们将在本教程后面看到。</li> </ul> <br /> 使用SpringBootServletInitializer扩展spring应用程序 <pre> <code class="language-java">import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.support.SpringBootServletInitializer; @SpringBootApplication public class JerseydemoApplication extends SpringBootServletInitializer { public static void main(String[] args) { new JerseydemoApplication().configure(new SpringApplicationBuilder(JerseydemoApplication.class)).run(args); } }</code></pre> <h2>四、使用JAX-RS Annotations创建安全的REST APIs</h2> 现在,当我们的api准备好时,我们将开始保护它们。让我们使用jax-rs注释对api进行注释,基于它们所需的访问级别和允许访问它们的用户角色。 <pre> <code class="language-java">@XmlAccessorType(XmlAccessType.NONE) @XmlRootElement(name = "users") @Path("/users") public class UserResource { private static Map<Integer, User> DB = new HashMap<>(); @GET @PermitAll @Produces("application/json") public Users getAllUsers() { Users users = new Users(); users.setUsers(new ArrayList<>(DB.values())); return users; } @POST @Consumes("application/json") @RolesAllowed("ADMIN") public Response createUser(User user) throws URISyntaxException { if(user.getFirstName() == null || user.getLastName() == null) { return Response.status(400).entity("Please provide all mandatory inputs").build(); } user.setId(DB.values().size()+1); user.setUri("/user-management/"+user.getId()); DB.put(user.getId(), user); return Response.status(201).contentLocation(new URI(user.getUri())).build(); } @GET @Path("/{id}") @Produces("application/json") @PermitAll public Response getUserById(@PathParam("id") int id) throws URISyntaxException { User user = DB.get(id); if(user == null) { return Response.status(404).build(); } return Response .status(200) .entity(user) .contentLocation(new URI("/user-management/"+id)).build(); } @PUT @Path("/{id}") @Consumes("application/json") @Produces("application/json") @RolesAllowed("ADMIN") public Response updateUser(@PathParam("id") int id, User user) throws URISyntaxException { User temp = DB.get(id); if(user == null) { return Response.status(404).build(); } temp.setFirstName(user.getFirstName()); temp.setLastName(user.getLastName()); DB.put(temp.getId(), temp); return Response.status(200).entity(temp).build(); } @DELETE @Path("/{id}") @RolesAllowed("ADMIN") public Response deleteUser(@PathParam("id") int id) throws URISyntaxException { User user = DB.get(id); if(user != null) { DB.remove(user.getId()); return Response.status(200).build(); } return Response.status(404).build(); } static { User user1 = new User(); user1.setId(1); user1.setFirstName("John"); user1.setLastName("Wick"); user1.setUri("/user-management/1"); User user2 = new User(); user2.setId(2); user2.setFirstName("Harry"); user2.setLastName("Potter"); user2.setUri("/user-management/2"); DB.put(user1.getId(), user1); DB.put(user2.getId(), user2); } }</code></pre> 你可以看到注解 角色注解@RolesAllowed <h2>五、使用JAX-RS ContainerRequestFilter来编写security filter</h2>   现在是编写我们的安全过滤器的时候了,它将检查传入的请求,获取授权信息(在本例中是基本身份验证),然后将匹配用户名和密码,最后它将通过它的角色来验证用户的访问级别。如果一切都匹配,API将被访问,否则用户将获得访问被拒绝的响应。 <pre> <code class="language-java">import java.lang.reflect.Method; import java.util.Arrays; import java.util.Base64; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.StringTokenizer; import javax.annotation.security.DenyAll; import javax.annotation.security.PermitAll; import javax.annotation.security.RolesAllowed; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ResourceInfo; import javax.ws.rs.core.Context; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import javax.ws.rs.ext.Provider; /** * This filter verify the access permissions for a user based on * user name and password provided in request * */ @Provider public class SecurityFilter implements javax.ws.rs.container.ContainerRequestFilter { private static final String AUTHORIZATION_PROPERTY = "Authorization"; private static final String AUTHENTICATION_SCHEME = "Basic"; private static final Response ACCESS_DENIED = Response.status(Response.Status.UNAUTHORIZED).build(); private static final Response ACCESS_FORBIDDEN = Response.status(Response.Status.FORBIDDEN).build(); private static final Response SERVER_ERROR = Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); @Context private ResourceInfo resourceInfo; @Override public void filter(ContainerRequestContext requestContext) { Method method = resourceInfo.getResourceMethod(); //Access allowed for all if( ! method.isAnnotationPresent(PermitAll.class)) { //Access denied for all if(method.isAnnotationPresent(DenyAll.class)) { requestContext.abortWith(ACCESS_FORBIDDEN); return; } //Get request headers final MultivaluedMap<String, String> headers = requestContext.getHeaders(); //Fetch authorization header final List<String> authorization = headers.get(AUTHORIZATION_PROPERTY); //If no authorization information present; block access if(authorization == null || authorization.isEmpty()) { requestContext.abortWith(ACCESS_DENIED); return; } //Get encoded username and password final String encodedUserPassword = authorization.get(0).replaceFirst(AUTHENTICATION_SCHEME + " ", ""); //Decode username and password String usernameAndPassword = null; try { usernameAndPassword = new String(Base64.getDecoder().decode(encodedUserPassword)); } catch (Exception e) { requestContext.abortWith(SERVER_ERROR); return; } //Split username and password tokens final StringTokenizer tokenizer = new StringTokenizer(usernameAndPassword, ":"); final String username = tokenizer.nextToken(); final String password = tokenizer.nextToken(); //Verifying Username and password if(!(username.equalsIgnoreCase("admin") && password.equalsIgnoreCase("password"))){ requestContext.abortWith(ACCESS_DENIED); return; } //Verify user access if(method.isAnnotationPresent(RolesAllowed.class)) { RolesAllowed rolesAnnotation = method.getAnnotation(RolesAllowed.class); Set<String> rolesSet = new HashSet<String>(Arrays.asList(rolesAnnotation.value())); //Is user valid? if( ! isUserAllowed(username, password, rolesSet)) { requestContext.abortWith(ACCESS_DENIED); return; } } } } private boolean isUserAllowed(final String username, final String password, final Set<String> rolesSet) { boolean isAllowed = false; //Step 1. Fetch password from database and match with password in argument //If both match then get the defined role for user from database and continue; else return isAllowed [false] //Access the database and do this part yourself //String userRole = userMgr.getUserRole(username); String userRole = "ADMIN"; //Step 2. Verify user role if(rolesSet.contains(userRole)) { isAllowed = true; } return isAllowed; } }</code></pre>   <h2>六、例子演示</h2> 运行这个项目作为Spring引导应用程序。现在测试rest资源。<br /> <strong>访问 GET /users resource<br /> <img alt="例子演示" class="img-thumbnail" src="/assist/images/blog/9d1d46f7425a422b9cb44730e13285fd.png" /><br /> 使用POST方式并且没有认证信息去访问/users 资源接口</strong><br /> 可以看到返回的状态 status code 401.<br /> <img alt="401" class="img-thumbnail" src="/assist/images/blog/90e9626483b4473fa79578ab2fa6b1e6.png" /><br /> <strong>使用POST方式并且添加认证信息去访问/users 资源接口</strong><br /> 使用<a href="http://www.leftso.com/tools/url.html" rel="external nofollow" target="_blank">此链接</a>生成base64编码的用户名和密码组合,以传递到授权头。<br /> <img alt="成功访问user" class="img-thumbnail" src="/assist/images/blog/5cf84a5bba9c4178be76a13e5e2e7716.png" /><br />  
  • 使用OAuth2安全的Spring security REST API(含demo代码下载)

    使用OAuth2安全的Spring REST API,Secure Spring REST API using OAuth2(含demo代码下载)<p>    让我们<code>Spring REST API</code>使用<code>OAuth2</code>这个时间来保护我们,这是一个简单的指南,显示使用REST API来保护REST API所需的内容<code>Spring OAuth2</code>。我们的用例符合<code>Resource-owner Password Grant</code>OAUth2规范的流程。我们将使用两个不同的客户端(Postman和<code>Spring RestTemplate</code>基于Java 的应用程序)访问我们的OAuth2受保护的REST资源。</p> <p>    如果您已经熟悉OAuth2概念,您可能希望跳过该理论,并直接跳转到代码中。一如以往,在本文末尾附件中可以找到完整的代码。让我们开始吧。<br />  </p> <h3>    什么是OAuth2</h3> <p>    OAuth2是一个标准化的授权协议/框架。根据官方OAuth2规范:</p> <p>    OAuth 2.0授权框架使得第三方应用程序可以通过协调资源所有者和HTTP服务之间的批准交互来代替资源所有者来<u>获取</u>对HTTP服务的<u>有限访问权限</u>,也可以允许第三方应用程序以自己的身份获得访问权限。</p> <p>    Google,Facebook等大型玩家已经在使用自己的OAuth2实现了很长一段时间。企业也正在朝OAuth2采纳方向发展。</p> <p>我发现OAuth2规范比较简单。然而,如果你想开始更快,可以在这里找到一篇关于OAuth2基础知识的优秀文章,从而深入了解OAUth2理论概念。</p> <p>Spring Security OAuth项目提供了使用Spring开发符合OAuth2标准的实现所需的所有API。官方的Spring安全oauth项目提供了一个实施OAuth2的综合示例。这个帖子的代码示例灵感来自这个例子本身。这篇文章的目的是为了保护我们的REST API,只需使用所需的最低限度的功能。</p> <p>至少你应该知道OAuth2中的四个关键概念:<br />  </p> <h3>OAuth2角色</h3> <p>OAuth2定义了四个角色:</p> <ul> <li><strong><code>resource owner</code>:</strong><br /> 可能是你 能够授予访问受保护资源的实体。当资源所有者是个人时,它被称为最终用户。</li> <li><strong><code>resource server</code>:</strong><br /> 托管受保护资源的服务器,能够使用访问令牌接受和响应受保护的资源请求。</li> <li><strong><code>client</code>:</strong><br /> 代表资源所有者及其授权的应用程序生成受保护的资源请求。它可能是一个移动应用程序,要求您访问您的Facebook订阅源,REST客户端尝试访问REST API,一个网站[Stackoverflow例如]使用Facebook帐户提供备用登录选项。</li> <li><strong><code>authorization server</code>:</strong><br /> 服务器在成功验证资源所有者并获得授权后,向客户端发出访问令牌。</li> </ul> <p>在我们的示例中,我们的REST API只能通过资源服务器进行访问,这将需要存在请求的访问令牌<br />  </p> <h3>2. OAuth2授权授权类型</h3> <p>授权授权是表示资源所有者授权(访问其受保护的资源)的凭据,由客户端使用以获取访问令牌。规范定义了四种授权类型:</p> <ul> <li><code>authorization code</code></li> <li><code>implicit</code></li> <li><code>resource owner password credentials</code></li> <li><code>client credentials</code></li> </ul> <p>我们将使用<code>resource owner password credentials</code>授权类型。原因很简单,我们没有实现将我们重定向到登录页面的视图。仅客户端[Postman或RestTemplate为基础的Java客户端例如]拥有资源所有者的凭据,并且他们将这些凭证以及客户机凭证提供给授权服务器,以便最终接收访问令牌[和可选刷新令牌],然后使用该令牌实际访问资源。</p> <p>一个常见的例子是<code>GMail app</code>您的智能手机上的[客户端],您需要您的凭据并使用它们进行连接<code>GMail servers</code>。它还显示“密码凭证授予”最适合当客户端和服务器与信任在同一个公司时,您不想向第三方提供凭据。</p> <h3>OAuth2令牌</h3> <p>令牌是实现特定的随机字符串,由授权服务器生成,并在客户端请求时发出。</p> <ul> <li><code>Access Token</code> :发送每个请求,通常有效期很短的一段时间[一小时例如]</li> <li><code>Refresh Token</code> :主要用于获取新的访问令牌,不发送每个请求,通常比访问令牌寿命更长。</li> </ul> <strong>HTTPS上的一个词</strong>:对于任何类型的安全实现,从基本身份验证到完整的OAuth2实现<strong><code>HTTPS</code></strong>都是必须的。没有HTTPS,无论您的实现是什么,安全性都将受到威胁。 <h3>OAuth2访问令牌范围</h3> <p>客户端可以使用范围[想要访问此用户的Facebook帐户的Feed和照片]来查询具有特定访问权限的资源,授权服务器又返回显示实际授予客户端访问权限的范围[资源所有者只允许Feed访问,没有照片例如]。</p> <hr />我们进入代码 <p>我们来实现使用Spring Security实现OAuth的必要构建块,以便访问我们的REST资源。</p> <h3>资源服务器</h3> <p>资源服务器托管客户端感兴趣的资源[我们的REST API]。资源位于<code>/user/</code>。<code>@EnableResourceServer</code>注释,应用于OAuth2资源服务器,启用使用传入OAuth2令牌对请求进行身份验证的Spring Security过滤器。类<code>ResourceServerConfigurerAdapter</code>实现<code>ResourceServerConfigurer</code>提供了调整由OAuth2安全保护的访问规则和路径的方法。</p> <pre> <code class="language-java">package com.websystique.springmvc.security;   import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler;   @Configuration @EnableResourceServer public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {       private static final String RESOURCE_ID = "my_rest_api";           @Override     public void configure(ResourceServerSecurityConfigurer resources) {         resources.resourceId(RESOURCE_ID).stateless(false);     }       @Override     public void configure(HttpSecurity http) throws Exception {         http.         anonymous().disable()         .requestMatchers().antMatchers("/user/**")         .and().authorizeRequests()         .antMatchers("/user/**").access("hasRole('ADMIN')")         .and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());     }   }</code></pre> <h3><br /> 授权服务器是一个负责验证凭据的人员,如果凭据确定,提供令牌[刷新令牌以及访问令牌]。它还包含有关注册的客户端和可能的访问范围和授权类型的信息。令牌存储用于存储令牌。我们将使用内存中的令牌存储。<code>@EnableAuthorizationServer</code>在当前应用程序上下文中启用授权服务器(即AuthorizationEndpoint和TokenEndpoint)。类<code>AuthorizationServerConfigurerAdapter</code>实现<code>AuthorizationServerConfigurer</code>,它提供了配置授权服务器的所有必要方法。2.授权服务器</h3> <pre> <code class="language-java"> package com.websystique.springmvc.security;   import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; import org.springframework.security.oauth2.provider.approval.UserApprovalHandler; import org.springframework.security.oauth2.provider.token.TokenStore;   @Configuration @EnableAuthorizationServer public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {       private static String REALM="MY_OAUTH_REALM";           @Autowired     private TokenStore tokenStore;       @Autowired     private UserApprovalHandler userApprovalHandler;       @Autowired     @Qualifier("authenticationManagerBean")     private AuthenticationManager authenticationManager;       @Override     public void configure(ClientDetailsServiceConfigurer clients) throws Exception {           clients.inMemory()             .withClient("my-trusted-client")             .authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")             .authorities("ROLE_CLIENT", "ROLE_TRUSTED_CLIENT")             .scopes("read", "write", "trust")             .secret("secret")             .accessTokenValiditySeconds(120).//Access token is only valid for 2 minutes.             refreshTokenValiditySeconds(600);//Refresh token is only valid for 10 minutes.     }       @Override     public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {         endpoints.tokenStore(tokenStore).userApprovalHandler(userApprovalHandler)                 .authenticationManager(authenticationManager);     }       @Override     public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {         oauthServer.realm(REALM+"/client");     }   }</code></pre> <p><br /> <br /> 向客户端注册客户端“我的信任客户端”和密码“秘密”以及允许的角色和范围。以上配置</p> <ul> <li>指定任何生成的访问令牌只有120秒有效</li> <li>指定任何生成的刷新令牌只有600秒有效</li> </ul> <h3>3.安全配置</h3> <p>粘在一起 端点<code>/oauth/token</code>用于请求令牌[访问或刷新]。资源所有者[bill,bob]在这里配置。</p> <pre> <code class="language-java">package com.websystique.springmvc.security;   import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.approval.ApprovalStore; import org.springframework.security.oauth2.provider.approval.TokenApprovalStore; import org.springframework.security.oauth2.provider.approval.TokenStoreUserApprovalHandler; import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;   @Configuration @EnableWebSecurity public class OAuth2SecurityConfiguration extends WebSecurityConfigurerAdapter {       @Autowired     private ClientDetailsService clientDetailsService;           @Autowired     public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {         auth.inMemoryAuthentication()         .withUser("bill").password("abc123").roles("ADMIN").and()         .withUser("bob").password("abc123").roles("USER");     }       @Override     protected void configure(HttpSecurity http) throws Exception {         http         .csrf().disable()         .anonymous().disable()         .authorizeRequests()         .antMatchers("/oauth/token").permitAll();     }       @Override     @Bean     public AuthenticationManager authenticationManagerBean() throws Exception {         return super.authenticationManagerBean();     }         @Bean     public TokenStore tokenStore() {         return new InMemoryTokenStore();     }       @Bean     @Autowired     public TokenStoreUserApprovalHandler userApprovalHandler(TokenStore tokenStore){         TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler();         handler.setTokenStore(tokenStore);         handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientDetailsService));         handler.setClientDetailsService(clientDetailsService);         return handler;     }           @Bean     @Autowired     public ApprovalStore approvalStore(TokenStore tokenStore) throws Exception {         TokenApprovalStore store = new TokenApprovalStore();         store.setTokenStore(tokenStore);         return store;     }       }</code></pre> <p>另外,启用全局方法安全性,如果我们想要使用它,它将激活@PreFilter,@PostFilter,@PreAuthorize @PostAuthorize注释。</p> <pre> <code class="language-java"> package com.websystique.springmvc.security;   import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; import org.springframework.security.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler;   @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true) public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {     @Autowired     private OAuth2SecurityConfiguration securityConfig;       @Override     protected MethodSecurityExpressionHandler createExpressionHandler() {         return new OAuth2MethodSecurityExpressionHandler();     } }</code></pre> <h3>4.终点及其目的</h3> <ul> <li>尝试访问资源[REST API],无需任何授权[将当然失败]。<br /> <code>GET http://localhost:8080/SpringSecurityOAuth2Example/user/</code></li> <li>问令牌[接入+刷新]使用<strong>HTTP POST</strong>上<code>/oauth/token</code>,与grant_type =密码和资源所有者凭证REQ-PARAMS。另外,在授权头中发送客户端凭据。 <p> </p> <p><code>POST http://localhost:8080/SpringSecurityOAuth2Example/oauth/token?grant_type=password&username=bill&password=abc123</code></p> </li> <li>要求通过有效的刷新令牌新的访问令牌,使用<strong>HTTP POST</strong>上<code>/oauth/token</code>,与grant_type = refresh_token,并发送实际的刷新令牌。另外,在授权头中发送客户端凭据。 <p> </p> <p><code>POST http://localhost:8080/SpringSecurityOAuth2Example/oauth/token?grant_type=refresh_token&refresh_token=094b7d23-973f-4cc1-83ad-8ffd43de1845</code></p> </li> <li>通过使用<code>access_token</code>具有请求的查询参数提供访问令牌来访问资源。<br /> <code>GET http://localhost:8080/SpringSecurityOAuth2Example/user/?access_token=3525d0e4-d881-49e7-9f91-bcfd18259109</code></li> </ul> <h3>5.休息API</h3> <p>我在大部分帖子中使用的简单的Spring REST API。</p> <pre> <code class="language-java"> package com.websystique.springmvc.controller;    import java.util.List;    import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.util.UriComponentsBuilder;    import com.websystique.springmvc.model.User; import com.websystique.springmvc.service.UserService;    @RestController public class HelloWorldRestController {        @Autowired     UserService userService;  //Service which will do all data retrieval/manipulation work               //-------------------Retrieve All Users--------------------------------------------------------            @RequestMapping(value = "/user/", method = RequestMethod.GET)     public ResponseEntity<List<User>> listAllUsers() {         List<User> users = userService.findAllUsers();         if(users.isEmpty()){             return new ResponseEntity<List<User>>(HttpStatus.NO_CONTENT);//You many decide to return HttpStatus.NOT_FOUND         }         return new ResponseEntity<List<User>>(users, HttpStatus.OK);     }           //-------------------Retrieve Single User--------------------------------------------------------            @RequestMapping(value = "/user/{id}", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_VALUE,MediaType.APPLICATION_XML_VALUE})     public ResponseEntity<User> getUser(@PathVariable("id") long id) {         System.out.println("Fetching User with id " + id);         User user = userService.findById(id);         if (user == null) {             System.out.println("User with id " + id + " not found");             return new ResponseEntity<User>(HttpStatus.NOT_FOUND);         }         return new ResponseEntity<User>(user, HttpStatus.OK);     }                      //-------------------Create a User--------------------------------------------------------            @RequestMapping(value = "/user/", method = RequestMethod.POST)     public ResponseEntity<Void> createUser(@RequestBody User user, UriComponentsBuilder ucBuilder) {         System.out.println("Creating User " + user.getName());            if (userService.isUserExist(user)) {             System.out.println("A User with name " + user.getName() + " already exist");             return new ResponseEntity<Void>(HttpStatus.CONFLICT);         }            userService.saveUser(user);            HttpHeaders headers = new HttpHeaders();         headers.setLocation(ucBuilder.path("/user/{id}").buildAndExpand(user.getId()).toUri());         return new ResponseEntity<Void>(headers, HttpStatus.CREATED);     }               //------------------- Update a User --------------------------------------------------------            @RequestMapping(value = "/user/{id}", method = RequestMethod.PUT)     public ResponseEntity<User> updateUser(@PathVariable("id") long id, @RequestBody User user) {         System.out.println("Updating User " + id);                    User currentUser = userService.findById(id);                    if (currentUser==null) {             System.out.println("User with id " + id + " not found");             return new ResponseEntity<User>(HttpStatus.NOT_FOUND);         }            currentUser.setName(user.getName());         currentUser.setAge(user.getAge());         currentUser.setSalary(user.getSalary());                    userService.updateUser(currentUser);         return new ResponseEntity<User>(currentUser, HttpStatus.OK);     }        //------------------- Delete a User --------------------------------------------------------            @RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)     public ResponseEntity<User> deleteUser(@PathVariable("id") long id) {         System.out.println("Fetching & Deleting User with id " + id);            User user = userService.findById(id);         if (user == null) {             System.out.println("Unable to delete. User with id " + id + " not found");             return new ResponseEntity<User>(HttpStatus.NOT_FOUND);         }            userService.deleteUserById(id);         return new ResponseEntity<User>(HttpStatus.NO_CONTENT);     }               //------------------- Delete All Users --------------------------------------------------------            @RequestMapping(value = "/user/", method = RequestMethod.DELETE)     public ResponseEntity<User> deleteAllUsers() {         System.out.println("Deleting All Users");            userService.deleteAllUsers();         return new ResponseEntity<User>(HttpStatus.NO_CONTENT);     }    }</code></pre> <h3><br /> 运行应用程序</h3> <p>运行它并使用两个不同的客户端进行测试。<br /> <br />  </p> <h4>客户端1:Postman</h4> <p>尝试访问一个没有任何信息的资源,将获得一个401。</p> <p><img alt="SpringOAuth2_img1" class="img-thumbnail" src="/assist/images/blog/bbd70c98-9e02-46e9-b4b7-c4bdfadb1d93.png" /></p> <p>让我们得到令牌。首先添加一个<strong>客户端凭证</strong> [my-trusted-client / secret] 的授权头文件。</p> <p><img alt="SpringOAuth2_img2" class="img-thumbnail" src="/assist/images/blog/9c19299c-5b53-4dfb-97f7-30b81963ac1f.png" /></p> <p>单击更新请求,验证标题标签中的标题。</p> <p><img alt="SpringOAuth2_img3" class="img-thumbnail" src="/assist/images/blog/e404605e-f273-476d-a582-0147146e96d9.png" /></p> <p>发送POST请求时,您将收到包含响应<code>access-token</code>以及<code>refresh-token</code>。</p> <p><img alt="SpringOAuth2_img4" class="img-thumbnail" src="/assist/images/blog/a0e2e85e-0a1b-40f8-86e4-3c9f1c062276.png" /></p> <p>将这些令牌保存在某个地方,您将需要它们。现在,您可以使用此访问令牌(有效期为2分钟)来访问资源。</p> <p><img alt="SpringOAuth2_img5" class="img-thumbnail" src="/assist/images/blog/77e1a43f-c866-4b33-b1f0-f1c7d5232f89.png" /></p> <p>2分钟后,access-token将过期,您的进一步资源请求将失败。</p> <p><img alt="SpringOAuth2_img6" class="img-thumbnail" src="/assist/images/blog/dc75aaec-9ce3-4fe0-8bb7-c98e3e8c6054.png" /></p> <p>我们需要一个新的访问令牌。通过刷新令牌来触发一个帖子,以获得全新的访问令牌。</p> <p><img alt="SpringOAuth2_img7" class="img-thumbnail" src="/assist/images/blog/34a5980f-0d49-416a-adf4-fe4182056648.png" /></p> <p>使用这个新的访问令牌访问资源。</p> <p><img alt="SpringOAuth2_img8" class="img-thumbnail" src="/assist/images/blog/4e0b3771-92ed-438a-bc0f-15de7bc6fcf1.png" /></p> <p>刷新令牌也过期[10分钟]。之后,您将看到刷新请求失败。</p> <p><img alt="SpringOAuth2_img9" class="img-thumbnail" src="/assist/images/blog/c8760e84-cdfa-4760-ad3e-0ff07768c117.png" /></p> <p>这意味着您需要请求新的刷新+访问令牌,如步骤2中所示。</p> <h4>客户端2:基于RestTemplate的java应用程序</h4> <p>方法<strong>sendTokenRequest</strong>用于实际获取令牌。然后,我们收到的访问令牌将被用于每个请求。如果需要,您可以在下面的示例中轻松实现刷新令牌流。</p> <pre> <code class="language-java">package com.websystique.springmvc; import java.net.URI; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import org.apache.commons.codec.binary.Base64; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.util.Assert; import org.springframework.web.client.RestTemplate; import com.websystique.springmvc.model.AuthTokenInfo; import com.websystique.springmvc.model.User; public class SpringRestClient { public static final String REST_SERVICE_URI = "http://localhost:8080/SpringSecurityOAuth2Example"; public static final String AUTH_SERVER_URI = "http://localhost:8080/SpringSecurityOAuth2Example/oauth/token"; public static final String QPM_PASSWORD_GRANT = "?grant_type=password&username=bill&password=abc123"; public static final String QPM_ACCESS_TOKEN = "?access_token="; /* * Prepare HTTP Headers. */ private static HttpHeaders getHeaders(){ HttpHeaders headers = new HttpHeaders(); headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); return headers; } /* * Add HTTP Authorization header, using Basic-Authentication to send client-credentials. */ private static HttpHeaders getHeadersWithClientCredentials(){ String plainClientCredentials="my-trusted-client:secret"; String base64ClientCredentials = new String(Base64.encodeBase64(plainClientCredentials.getBytes())); HttpHeaders headers = getHeaders(); headers.add("Authorization", "Basic " + base64ClientCredentials); return headers; } /* * Send a POST request [on /oauth/token] to get an access-token, which will then be send with each request. */ @SuppressWarnings({ "unchecked"}) private static AuthTokenInfo sendTokenRequest(){ RestTemplate restTemplate = new RestTemplate(); HttpEntity<String> request = new HttpEntity<String>(getHeadersWithClientCredentials()); ResponseEntity<Object> response = restTemplate.exchange(AUTH_SERVER_URI+QPM_PASSWORD_GRANT, HttpMethod.POST, request, Object.class); LinkedHashMap<String, Object> map = (LinkedHashMap<String, Object>)response.getBody(); AuthTokenInfo tokenInfo = null; if(map!=null){ tokenInfo = new AuthTokenInfo(); tokenInfo.setAccess_token((String)map.get("access_token")); tokenInfo.setToken_type((String)map.get("token_type")); tokenInfo.setRefresh_token((String)map.get("refresh_token")); tokenInfo.setExpires_in((int)map.get("expires_in")); tokenInfo.setScope((String)map.get("scope")); System.out.println(tokenInfo); //System.out.println("access_token ="+map.get("access_token")+", token_type="+map.get("token_type")+", refresh_token="+map.get("refresh_token") //+", expires_in="+map.get("expires_in")+", scope="+map.get("scope"));; }else{ System.out.println("No user exist----------"); } return tokenInfo; } /* * Send a GET request to get list of all users. */ @SuppressWarnings({ "unchecked", "rawtypes" }) private static void listAllUsers(AuthTokenInfo tokenInfo){ Assert.notNull(tokenInfo, "Authenticate first please......"); System.out.println("\nTesting listAllUsers API-----------"); RestTemplate restTemplate = new RestTemplate(); HttpEntity<String> request = new HttpEntity<String>(getHeaders()); ResponseEntity<List> response = restTemplate.exchange(REST_SERVICE_URI+"/user/"+QPM_ACCESS_TOKEN+tokenInfo.getAccess_token(), HttpMethod.GET, request, List.class); List<LinkedHashMap<String, Object>> usersMap = (List<LinkedHashMap<String, Object>>)response.getBody(); if(usersMap!=null){ for(LinkedHashMap<String, Object> map : usersMap){ System.out.println("User : id="+map.get("id")+", Name="+map.get("name")+", Age="+map.get("age")+", Salary="+map.get("salary"));; } }else{ System.out.println("No user exist----------"); } } /* * Send a GET request to get a specific user. */ private static void getUser(AuthTokenInfo tokenInfo){ Assert.notNull(tokenInfo, "Authenticate first please......"); System.out.println("\nTesting getUser API----------"); RestTemplate restTemplate = new RestTemplate(); HttpEntity<String> request = new HttpEntity<String>(getHeaders()); ResponseEntity<User> response = restTemplate.exchange(REST_SERVICE_URI+"/user/1"+QPM_ACCESS_TOKEN+tokenInfo.getAccess_token(), HttpMethod.GET, request, User.class); User user = response.getBody(); System.out.println(user); } /* * Send a POST request to create a new user. */ private static void createUser(AuthTokenInfo tokenInfo) { Assert.notNull(tokenInfo, "Authenticate first please......"); System.out.println("\nTesting create User API----------"); RestTemplate restTemplate = new RestTemplate(); User user = new User(0,"Sarah",51,134); HttpEntity<Object> request = new HttpEntity<Object>(user, getHeaders()); URI uri = restTemplate.postForLocation(REST_SERVICE_URI+"/user/"+QPM_ACCESS_TOKEN+tokenInfo.getAccess_token(), request, User.class); System.out.println("Location : "+uri.toASCIIString()); } /* * Send a PUT request to update an existing user. */ private static void updateUser(AuthTokenInfo tokenInfo) { Assert.notNull(tokenInfo, "Authenticate first please......"); System.out.println("\nTesting update User API----------"); RestTemplate restTemplate = new RestTemplate(); User user = new User(1,"Tomy",33, 70000); HttpEntity<Object> request = new HttpEntity<Object>(user, getHeaders()); ResponseEntity<User> response = restTemplate.exchange(REST_SERVICE_URI+"/user/1"+QPM_ACCESS_TOKEN+tokenInfo.getAccess_token(), HttpMethod.PUT, request, User.class); System.out.println(response.getBody()); } /* * Send a DELETE request to delete a specific user. */ private static void deleteUser(AuthTokenInfo tokenInfo) { Assert.notNull(tokenInfo, "Authenticate first please......"); System.out.println("\nTesting delete User API----------"); RestTemplate restTemplate = new RestTemplate(); HttpEntity<String> request = new HttpEntity<String>(getHeaders()); restTemplate.exchange(REST_SERVICE_URI+"/user/3"+QPM_ACCESS_TOKEN+tokenInfo.getAccess_token(), HttpMethod.DELETE, request, User.class); } /* * Send a DELETE request to delete all users. */ private static void deleteAllUsers(AuthTokenInfo tokenInfo) { Assert.notNull(tokenInfo, "Authenticate first please......"); System.out.println("\nTesting all delete Users API----------"); RestTemplate restTemplate = new RestTemplate(); HttpEntity<String> request = new HttpEntity<String>(getHeaders()); restTemplate.exchange(REST_SERVICE_URI+"/user/"+QPM_ACCESS_TOKEN+tokenInfo.getAccess_token(), HttpMethod.DELETE, request, User.class); } public static void main(String args[]){ AuthTokenInfo tokenInfo = sendTokenRequest(); listAllUsers(tokenInfo); getUser(tokenInfo); createUser(tokenInfo); listAllUsers(tokenInfo); updateUser(tokenInfo); listAllUsers(tokenInfo); deleteUser(tokenInfo); listAllUsers(tokenInfo); deleteAllUsers(tokenInfo); listAllUsers(tokenInfo); } }</code></pre> <br /> 以上代码将产生以下输出: <pre> <code>AuthTokenInfo [access_token=fceed386-5923-4bf8-b193-1d76f95da4c4, token_type=bearer, refresh_token=29d28ee2-9d09-483f-a2d6-7f93e7a31667, expires_in=71, scope=read write trust] Testing listAllUsers API----------- User : id=1, Name=Sam, Age=30, Salary=70000.0 User : id=2, Name=Tom, Age=40, Salary=50000.0 User : id=3, Name=Jerome, Age=45, Salary=30000.0 User : id=4, Name=Silvia, Age=50, Salary=40000.0 Testing getUser API---------- User [id=1, name=Sam, age=30, salary=70000.0] Testing create User API---------- Location : http://localhost:8080/SpringSecurityOAuth2Example/user/5 Testing listAllUsers API----------- User : id=1, Name=Sam, Age=30, Salary=70000.0 User : id=2, Name=Tom, Age=40, Salary=50000.0 User : id=3, Name=Jerome, Age=45, Salary=30000.0 User : id=4, Name=Silvia, Age=50, Salary=40000.0 User : id=5, Name=Sarah, Age=51, Salary=134.0 Testing update User API---------- User [id=1, name=Tomy, age=33, salary=70000.0] Testing listAllUsers API----------- User : id=1, Name=Tomy, Age=33, Salary=70000.0 User : id=2, Name=Tom, Age=40, Salary=50000.0 User : id=3, Name=Jerome, Age=45, Salary=30000.0 User : id=4, Name=Silvia, Age=50, Salary=40000.0 User : id=5, Name=Sarah, Age=51, Salary=134.0 Testing delete User API---------- Testing listAllUsers API----------- User : id=1, Name=Tomy, Age=33, Salary=70000.0 User : id=2, Name=Tom, Age=40, Salary=50000.0 User : id=4, Name=Silvia, Age=50, Salary=40000.0 User : id=5, Name=Sarah, Age=51, Salary=134.0 Testing all delete Users API---------- Testing listAllUsers API----------- No user exist----------</code></pre>   <h3>项目结构</h3> <img alt="10" class="img-thumbnail" src="/assist/images/blog/aab904e5-276f-4fac-8bd4-3762adb1a28c.png" /> <h3>pom.xml</h3> <pre> <code class="language-xml"><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.websystique.springmvc</groupId> <artifactId>SpringSecurityOAuth2Example</artifactId> <version>1.0.0</version> <packaging>war</packaging> <name>SpringSecurityOAuth2Example</name> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <springframework.version>4.3.1.RELEASE</springframework.version> <springsecurity.version>4.1.1.RELEASE</springsecurity.version> <springsecurityoauth2.version>2.0.10.RELEASE</springsecurityoauth2.version> <jackson.library>2.7.5</jackson.library> </properties> <dependencies> <!-- Spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${springframework.version}</version> </dependency> <!-- Spring Security --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>${springsecurity.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>${springsecurity.version}</version> </dependency> <!-- Spring Security OAuth2--> <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> <version>${springsecurityoauth2.version}</version> </dependency> <!-- Jackson libraries --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson.library}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> <version>${jackson.library}</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.2</version> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.4</version> <configuration> <warSourceDirectory>src/main/webapp</warSourceDirectory> <warName>SpringSecurityOAuth2Example</warName> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> <finalName>SpringSecurityOAuth2Example</finalName> </build> </project></code></pre> <h4><strong><a href="http://websystique.com/?smd_process_download=1&download_id=2926" rel="external nofollow" target="_blank"><em><u>下载源代码</u></em></a></strong></h4> <br />