プロジェクト経験のインタビューの準備

2. RESTとは何か

RESTは「休む」という言葉はありませんが、頭字語のいくつかの単語表現状態転送、直接翻訳:プレゼンテーション層の状態遷移、この翻訳はよく理解されていません。より人気の引数を見つけるためにインターネットがある:URLは、HTTP動詞(など、PUSH、POSTは、DELETE、GET)動作の説明とリソースを検索します

3.どのような安らかです

RESTベースのAPIは、RESTfulなスタイルを構築されています。

4.なぜRESTfulな

(1)。我々のチームは、プレゼンテーション層としてJSPを使用していたが、この技術は、我々の開発の効率を制限する前に、我々は静的なHTMLページの前群を与える必要があるページをJSPに変換され、いくつかのJSコードを書き、さらにいくつかのフロントエンドコード。これがひどく、私たちの開発効率を制限するバックエンドのグループは、ビジネス機能の開発に焦点を当ててはいけない、私たちは今、周りの持っているより多くのインターネット企業は、エンド・分離を実施し始めました。

(2)モバイルインターネットの発展と近年では、別の後のクライアントの様々な種類、PCのためのサービスを提供することができRESTfulな、マイクロチャネル(H5)、統一されたインタフェースを介して、IOSとAndroid、フロントエンドのスタイルを必要としない、このインタフェース、これは、データのみを提供します。次のように安らかなアーキテクチャは次のとおりです。

 

 
 

実用的な例:

フーリッシュオールドマン、ユーザーリストなどの通貨取引、

      必要この機能でPCサイト、

      また、この機能を必要とするAndroidアプリ、

      また、この機能を必要とするIOSアプリケーション。

私たちの現在の開発モデルによれば、我々は二組(PCと端末APP)の機能一覧について話をするユーザーの取得を記述する必要があります。それはすることができ、データベースを照会し、必要がデータベース構成情報を接続する2つの場所で書かれている、私たちが想像することができます(あなたが内部書かれたアンドロイドでデータベース接続を設定している場合)、時間と経験の無駄ですが、安全性能が悪く、RestfulAPIこの問題へのよりよい解決策:(以下のインタフェースが存在しない、単なる一例)

フーリッシュオールドマン通貨取引のお問い合わせのPCます。http://******.yugyg.com/user/get YgfMoneyDeal = PCから?

フーリッシュオールドマン為替取引のお問い合わせアンドリュースます:http://******.yugyg.com/user/getYgfMoneyDeal =アンドロイドから?

愚公币交易查询 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;
}

 

おすすめ

転載: www.cnblogs.com/Treesir/p/11753671.html