Interview preparation of project experience

2. What is REST

REST is not a "rest" word, but the acronym few words REpresentation State Transfer, a direct translation: the presentation layer state transition, this translation is not well understood. Internet to find a more popular argument is: URL locate resources with HTTP verbs (GET, POST, DELETE, PUSH, etc.) description of the operation

3. What is Restful

REST-based API is built Restful style.

4. Why Restful

(1). Before our team has been using JSP as the presentation layer, but this technique limits the efficiency of our development, we need to give the front group of static html pages converted to jsp page, and write some js code, and even some front-end code. This will severely restrict our development efficiency, do not let the back-end group focused on the development of business functions, we now have around more and more Internet companies began to implement end separation.

(2) In recent years with the development of mobile Internet, various types of client after another, Restful can provide services for PC, micro-channel (H5), IOS and Android through a unified interface, this interface does not require front-end style, It provides data only. Restful architecture is as follows:

 

 
 

For a practical example:

Foolish Old Man currency transactions such as user lists,

      PC sites in need this feature,

      Android App which also need this feature,

      IOS App which also need this function.

According to our current development model, we have to write two sets (PC and terminal APP) get the user to talk about the features list. That is required are written in two places connected database configuration information, query the database, we can imagine is a waste of time and experience, but safety performance is poor (if you configure the database connection in Android written inside), RestfulAPI can a better solution to this problem: (the following interface does not exist, just an example)

Foolish Old Man currency transaction inquiries PC: http: //******.yugyg.com/user/get YgfMoneyDeal from = PC?

Foolish Old Man currency trading inquiry Andrews: http: //******.yugyg.com/user/getYgfMoneyDeal from = android?

愚公币交易查询 IOS: http://******.yugyg.com/user/getYgfMoneyDeal?from=ios

带来好处是只要写一次接口就可以供3个地方同时使用,这样不仅更加安全,快捷,最主要是分工更快速了。一个人专门写接口,另外一个人只需要知道如何调用就可以了,完全不需要知道是如何实现的。

5. 如何设计Restful风格的API

RestfulAPI就是由后台(SERVER端)来提供接口,前端来调用。前端调用API向后台发起HTTP请求,后台响应请求将处理结果反馈给前端。也就是说Restful 是典型的基于HTTP的协议。那么RESTful API有哪些特征呢?

(1).Resource资源,首先是弄清楚资源的概念。资源就是网络上的一个实体、一段文本、一张图片或者一首歌曲。资源总是要通过一种载体来反应它的内容。文本可以用TXT,也可以用HTML或者XML、图片可以用JPG格式或者PNG格式,JSON是现在最常用的资源表现形式。

(2).统一接口。Restful风格的数据元操作CRUD(create,read,update,delete)分别对应HTTP方法:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源,这样就统一了数据操作的接口。

(3).HTTP状态码,在REST中都有特定的意义:200,201,202,204,400,401,403,500。比如401表示用户身份认证失败,403表示你验证身份通过了,但这个资源你不能操作。

(4). 无状态。所谓无状态即所有的资源都可以URI定位,而且这个定位与其他资源无关,也不会因为其他资源的变化而变化。

Restful 是典型的基于HTTP的协议,HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为“一次连接”。前面一次请求与后面一次请求没有必然的联系,所以是无状态的。

TCP/IP要建立一个连接,需要经过三次握手,可以简单的理解为:

①.客户端发起连接请求,等待服务器响应 

②.服务器接收到请求,确认客户端发起的包,并多返回一个包

③.客户端接收服务器发过来的包,并且回复给服务器确认包,至此三次握手完成,连接建立

握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。所以TCP/IP是有状态的。

有状态和无状态的区别,举个例子说明一下,例如要查询员工工资的步骤为:

第一步:登录系统。

第二步:进入查询工资的页面。

第三步:搜索该员工。

第四步:点击姓名查看工资。

这样的操作流程就是有状态的,查询工资的每一个步骤都依赖于前一个步骤,只要前置操作不成功,后续操作就无法执行。

如果输入一个URL就可以得到指定员工的工资,则这种情况就是无状态的,因为获取工资不依赖于其他资源或状态,且这种情况下,员工工资是一个资源,由一个URL与之对应可以通过HTTP中的GET方法得到资源,这就是典型的Restful风格。

(5).将API的版本号放入URL。GET:http://www.xxx.com/v1/friend/123。或者将版本号放在HTTP头信息中,我个人觉得版本号能够较好的控制缓存问题,推荐放在HTTP头信息中。

(6).过滤信息。如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。下面是一些常见的参数:

?limit=10:指定返回记录的数量。

?current_page=2&showCount=10:指定第几页,以及每页的记录数。

?search_type=1:指定筛选条件。

(7). 规范返回的数据。为了保障前后端的数据交互的顺畅,建议规范数据的返回,并采用固定的数据格式封装。

{

 "msg":"uri_not_found",

 "code":10001,

 "request":"GET/v2/goods/1227"

}

6. 采用Restful风格的API可以方便测试

SpringMVC项目可以通过整合springfox和swagger-ui来构建API接口文档,整合过程可以参考:

https://my.oschina.net/wangmengjun/blog/907679

后台通过规范的写法,通过IP+端口号+项目名/swagger-ui.html来访问生成的API接口文档:

 

 
 

通过swagger-ui与项目的结合,可以提高前后端配合效率,对接口的管理更加方便,提高开发效率。

7. REST API版本控制

随着我们需求的变更,功能的迭代,API的更改是不可避免的。当一个API修改时,可能会新增一个参数,也有可能会修改返回的数据类型,也有可能删除某个函数……

REST不提供任何特定的版本控制指南,但更常用的方法可以分为3种:

(1).URL版本控制

使用URI是最直接的方法,尽管它违背了URI应该引用唯一资源的原则。当版本更新时,还可以保障客户端不会受到影响,如:

http://******.yugyg.com/v1

http://******v1.yugyg.com

版本不需要是数字的,也不需要使用“v[x]”语法指定。替代方案包括日期、项目名称、季节或其他标识符,这些标识符对于开发api的团队来说足够有意义,并且随着版本的变化也足够灵活。

(2). 使用自定义请求头进行版本控制。

自定义头(例如,Accept-version)允许您在版本之间保留uri,尽管它实际上是现有的Accept头实现的内容协商行为的副本,如:

$.ajax({

    headers: {

        Accept-version:v1

    },

    type: "get",

    success: function (data) {

    }

});

(3). 版本使用Accept标头

内容协商可以让您保留一组干净的url,但是您仍然需要处理在某些地方服务不同版本内容的复杂性。这个负担通常会向上转移到您的API接口上,API接口负责确定要发送哪个版本的资源。最终结果往往是一个更复杂的API,因为客户端在请求资源之前必须知道要指定哪个头,如:

Accept:application/ com.yugyg.v1 + json

Accept:application

/ json com.yugyg + = 1.0版本

在实际开发中,API永远不会是完全稳定的。因此,如何管理这种变化很重要。

Shiro的优点

简单的身份认证, 支持多种数据源
对角色的简单的授权, 支持细粒度的授权(方法级)
支持一级缓存,以提升应用程序的性能;
内置的基于 POJO 企业会话管理, 适用于 Web 以及非 Web 的环境
非常简单的加密 API
不跟任何的框架或者容器捆绑, 可以独立运行

Shiro 架构 核心组件:
Authenticator:管理登陆登出
Autorizer:授权器赋予主体有那些权限
session Manager:shiro自己实现session管理器
session DAO:提供了session的增删改插
Cache Manager:缓冲管理器
Raelms:和数据库交互的桥梁

shiro认证过程
创建SecurityManager -> 主体提交认证 -> SecurityManager认证 -> Authenticator认证 -> Realm验证

shiro 授权过程
创建SecurityManager ->主体授权 -> securityManager授权 -> Authorizer授权 ->Realm获取权限数据

 

简述
Apache Shiro是java的一个安全框架,Shiro可以帮助我们完成认证、授权、加密、会话管理、与Web集成、缓存等。而且Shiro的API也比较简单,这里我们就不进行过多的赘述,想要详细了解Shiro的,推荐看开涛的博客(点这里)

在Shiro的强大权限管理的基础上,我们实现单点登录就容易了很多,结合我上篇博客所讲的JSON Web Token(推荐先看这篇博客)就可以完成单点登录系统。

实现过程
在使用Shiro实现登录的时候,将登录成功的信息包括Token信息返回给前端,前端在请求后台时,将Token信息存入请求头中。配置自定义拦截器,拦截所有URL请求,取出请求头信息中的Token信息,对Token信息进行验证,对于redis中存在的登录时生成的Token信息,如果Token信息正确,则确认该用户已经登录,否则拒绝请求,返回401错误。

1.引入所需jar包
<!--json-web-token-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
</dependency>

2.登录认证
要实现单点登录功能,首先要完成的就是登录功能,这里我们使用Shiro的认证来完成登录。

2.1 spring-shiro的配置文件
<!--配置SecurityManager-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realms">
<list>
<ref bean="shiroRealm"/>
</list>
</property>
</bean>

<!--配置Realm-->
<!--直接配置实现了org.apache.shiro.realm.Realm接口的bean-->
<bean id="shiroRealm" class="com.why.authority.realms.ShiroRealm">
<!-- 凭证匹配器:配置登录验证所使用的加密算法-->
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="sha-512"/>
<property name="hashIterations" value="1024"/>
</bean>
</property>
</bean>

<!-- shiro拦截器-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/user/index"/>
<!--配置哪些页面需要受保护
1. anon 可以被匿名访问
2. authc 必须认证(登录)后才可能访问的页面
3. logout 登出
-->

<!--自定义filters,该拦截器即为实现单点登录的拦截器-->
<property name="filters">
<map>
<entry key="acf">
<bean class="com.why.authority.filter.AccessingControlFilter"/>
</entry>
</map>
</property>
<property name="filterChainDefinitions">
<value>
/user/index=anon
/user/login=anon
/user/content= acf
/** = acf
</value>
</property>
</bean>

2.2 登录方法
Controller:

@RequestMapping(value = {"/login"}, method = RequestMethod.POST)
@ResponseBody
public WhyResult content(@RequestParam("usercode") String usercode, @RequestParam("password") String password) {

String userInfoKey = "aum:user:" + usercode;
String tokenKey = "aum:token:"+usercode;

try {
if(StringUtils.isBlank(usercode) || StringUtils.isBlank(password)){
throw new UnknownAccountException();
}
//1. 执行登录
//把用户名和密码封装为UsernamePasswordToken对象
UsernamePasswordToken token = new UsernamePasswordToken(usercode, password);
SecurityUtils.getSubject().login(token);

//2.获取用户信息userEntity,redis中不存在则存入redis
UserEntity userEntity = new UserEntity();
//2.1 从redis中获取或从数据库中获取
String strUserInfo = JedisCacheUtil.get(userInfoKey);
if (!StringUtils.isBlank(strUserInfo)) {
userEntity = JacksonJsonUntil.jsonToPojo(strUserInfo, UserEntity.class);
} else {
userEntity = addUserInfoToRedis(usercode, userInfoKey);
}
//3.生成Token信息并保存到redis
LoginEntity loginEntity = addTokenToRedis(userEntity,tokenKey);
return WhyResult.build(200,"登录成功!",loginEntity);
//所有认证异常的父类
} catch (AuthenticationException e) {
logger.error("登录失败!",e);
return WhyResult.build(401,"用户名或密码错误!");
}
}

自定义Realm

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

//1.把AuthenticationToken转换为UsernamePasswordToken
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;

//2.从UsernamePasswordToken中获取userCode
String userCode = usernamePasswordToken.getUsername();
String userInfoKey = "aum:user:" + userCode;
UserEntity userEntity;
//3.获取用户信息userEntity
//3.1 从redis中获取
String strUserInfo;
try {
strUserInfo = JedisCacheUtil.get(userInfoKey);
if (!StringUtils.isBlank(strUserInfo)) {
userEntity = JacksonJsonUntil.jsonToPojo(strUserInfo, UserEntity.class);
} else {
userEntity = addUserAndGetUser(userCode, userInfoKey);
}
} catch (Exception e) {
userEntity = addUserAndGetUser(userCode, userInfoKey);
}
//6.根据用户的情况,来构建AuthenticationInfo对象并返回
String credentials = userEntity.getPassword();
//使用ByteSource.Util.bytes()来计算盐值
ByteSource credentialsSalt = ByteSource.Util.bytes(userCode);

return new SimpleAuthenticationInfo(userEntity, credentials, credentialsSalt, getName());

}

3. 自定义拦截器
该拦截器是在spring-shiro.xml文件中配置的自定义拦截器,原理就是拦截每个请求,验证URL请求头信息中的Token信息是否过期,是否被篡改。

@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
//是否验证通过
boolean bool = false;
try {
HttpServletRequest req = WebUtils.toHttp(servletRequest);
String firstLoginToken = req.getParameter("token");

//从token中获得信息
Claims claims = TokenUtil.getClaims(firstLoginToken);
String userCode = claims.getSubject();
String userId = claims.getId();

String redisLoginKey = "aum:token:" + userCode;
String redisToken = JedisCacheUtil.get(redisLoginKey);
if(!StringUtils.isBlank(redisToken)){
String[] arrayRedisToken = redisToken.split("@");
//将用户传过来的token和redis中的做对比,若一样,认为已经登录
if (arrayRedisToken[0].equals(firstLoginToken)) {
//比较这次访问与登录的时间间隔有多少分钟,如果大于5分钟,则更新redis中的上次访问时间信息,将过期时间从新设定为30分钟
long diffMin = TokenUtil.CompareTime(arrayRedisToken[1]);
if (diffMin >= 5) {
String currentAccessTime = PasswordUtil.base64Encoede(String.valueOf(System.currentTimeMillis()));
//更新redis中的token登录信息
JedisCacheUtil.set(redisLoginKey, arrayRedisToken[0] + "@" + currentAccessTime, 30 * 60);
}
bool=true;
}
}
} catch (Exception e) {
return bool;
}
return bool;
}

 

Guess you like

Origin www.cnblogs.com/Treesir/p/11753671.html