WEB后台--基于Token的WEB后台登陆认证机制(转)

WEB后台--基于Token的WEB后台登陆认证机制(并讲解其余认证机制以及cookie和session机制)

继续这一个系列,基于Token的WEB后台登陆认证机制(并讲解cookie和session机制)。每一个后端不得不解决的认证问题。

本系列:

(一)J2EE项目系列(三)–Spring Data JPA+Spring+SpringMVC+Maven快速开发(1)项目架构

(二) J2EE项目系列(三)–Spring Data JPA+Spring+SpringMVC+Maven快速开发(2)多个第三方服务端接入之云旺IM

(三) Java-解决实现JPA的hibernate自动建表的编码问题


文章结构:(1)JWT(一种基于 token 的认证方案)介绍并介绍其余几大认证机制;(2)cookie和session机制;(3)Token机制相对于Cookie机制的好处;(4)JWT的Java实现;


1、JWT(一种基于 token 的认证方案)介绍:

(1)概述:JWT就是一种Token的编码算法,服务器端负责根据一个密码和算法生成Token,而后发给客户端,客户端只负责后面每次请求都在HTTP header里面带上这个Token,服务器负责验证这个Token是否是合法的,有没有过时等,并能够解析出subject和claim里面的数据。

(2)相关问题:

1.为何用JWT?

JWT只经过算法实现对Token合法性的验证,不依赖数据库,Memcached的等存储系统,所以能够作到跨服务器验证,只要密钥和算法相同,不一样服务器程序生成的Token能够互相验证。

2.JWT Token不须要持久化在任何NoSQL中,否则背离其算法验证的初心

3.在退出登陆时怎样实现JWT Token失效呢?

退出登陆, 只要客户端端把Token丢弃就能够了,服务器端不须要废弃Token。

4.怎样保持客户端长时间保持登陆状态?

服务器端提供刷新Token的接口, 客户端负责按必定的逻辑刷新服务器Token。

5.服务器端是否应该从JWT中取出userid用于业务查询?

REST API是无状态的,意味着服务器端每次请求都是独立的,即不依赖之前请求的结果,所以也不该该依赖JWT token作业务查询, 应该在请求报文中单独加个userid 字段。

为了作用户水平越权的检查,能够在业务层判断传入的userid和从JWT token中解析出的userid是否一致, 有些业务可能会容许查不一样用户的数据。


(3)其余几大认证机制:

1.HTTP Basic Auth

HTTP Basic Auth简单点说明就是每次请求API时都提供用户的username和password,简言之,Basic Auth是配合RESTful API 使用的最简单的认证方式,只需提供用户名密码便可,但因为有把用户名密码暴露给第三方客户端的风险,在生产环境下被使用的愈来愈少。所以,在开发对外开放的RESTful API时,尽可能避免采用HTTP Basic Auth

2.OAuth(开放受权)

是一个开放的受权标准,容许用户让第三方应用访问该用户在某一web服务上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。
OAuth容许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据。每个令牌受权一个特定的第三方系统(例如,视频编辑网站)在特定的时段(例如,接下来的2小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth让用户能够受权第三方网站访问他们存储在另外服务提供者的某些特定信息,而非全部内容。

这里写图片描述

这种基于OAuth的认证机制适用于我的消费者类的互联网产品,如社交类APP等应用,可是不太适合拥有自有认证权限管理的企业应用;

3.Cookie Auth

Cookie认证机制就是为一次请求认证在服务端建立一个Session对象,同时在客户端的浏览器端建立了一个Cookie对象;经过客户端带上来Cookie对象来与服务器端的session对象匹配来实现状态管理的。默认的,当咱们关闭浏览器的时候,cookie会被删除。但能够经过修改cookie 的expire time使cookie在必定时间内有效;

2、cookie和session机制:

(1)概述:

Cookie和Session是为了在无状态的HTTP协议之上维护会话状态,使得服务器能够知道当前是和哪一个客户在打交道。

Session是在服务端保存的一个数据结构,用来跟踪用户的状态,这个数据能够保存在集群、数据库、文件中;

Cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现Session的一种方式。

由于HTTP协议是无状态的,即每次用户请求到达服务器时,HTTP服务器并不知道这个用户是谁、是否登陆过等。如今的服务器之因此知道咱们是否已经登陆,是由于服务器在登陆时设置了浏览器的Cookie!Session则是借由Cookie而实现的更高层的服务器与浏览器之间的会话。

(2)cookie实现机制:

Cookie是由客户端保存的小型文本文件,其内容为一系列的键值对。 Cookie是由HTTP服务器设置的,保存在浏览器中, 在用户访问其余页面时,会在HTTP请求中附上该服务器以前设置的Cookie。

Cookie的传递流程

1.浏览器向某个URL发起HTTP请求(能够是任何请求,好比GET一个页面、POST一个登陆表单等)
2.对应的服务器收到该HTTP请求,并计算应当返回给浏览器的HTTP响应。(HTTP响应包括请求头和请求体两部分)
3.在响应头加入Set-Cookie字段,它的值是要设置的Cookie。
4.浏览器收到来自服务器的HTTP响应。
5.浏览器在响应头中发现Set-Cookie字段,就会将该字段的值保存在内存或者硬盘中。(Set-Cookie字段的值能够是不少项Cookie,每一项均可以指定过时时间Expires。 默认的过时时间是用户关闭浏览器时。)
6.浏览器下次给该服务器发送HTTP请求时, 会将服务器设置的Cookie附加在HTTP请求的头字段Cookie中。(浏览器能够存储多个域名下的Cookie,但只发送当前请求的域名曾经指定的Cookie, 这个域名也能够在Set-Cookie字段中指定)。)
7.服务器收到这个HTTP请求,发现请求头中有Cookie字段, 便知道以前就和这个用户打过交道了.
8.过时的Cookie会被浏览器删除。

总之,服务器经过Set-Cookie响应头字段来指示浏览器保存Cookie, 浏览器经过Cookie请求头字段来告诉服务器以前的状态。 Cookie中包含若干个键值对,每一个键值对能够设置过时时间。

Cookie提供了一种手段使得HTTP请求能够附加当前状态, 现今的网站也是靠Cookie来标识用户的登陆状态的:

1.用户提交用户名和密码的表单,这一般是一个POST HTTP请求。
2.服务器验证用户名与密码,若是合法则返回200(OK)并设置Set-Cookie为authed=true。
3.浏览器存储该Cookie。
4.浏览器发送请求时,设置Cookie字段为authed=true。
5.服务器收到第二次请求,从Cookie字段得知该用户已经登陆。 按照已登陆用户的权限来处理这次请求。

问题是什么??风险是什么??

咱们知道能够发送HTTP请求的不仅是浏览器,不少HTTP客户端软件(包括curl、Node.js)均可以发送任意的HTTP请求,能够设置任何头字段。 假如咱们直接设置Cookie字段为authed=true并发送该HTTP请求, 服务器岂不是被欺骗了?这种攻击很是容易,Cookie是能够被篡改的

服务器能够为每一个Cookie项生成签名,因为用户篡改Cookie后没法生成对应的签名, 服务器即可以得知用户对Cookie进行了篡改。

例子:一个简单的校验过程:

1.在服务器中配置一个鲜为人知的字符串(咱们叫它Secret),好比:x$sfz32。
2.当服务器须要设置Cookie时(好比authed=false),不只设置authed的值为false, 在值的后面进一步设置一个签名,最终设置的Cookie是authed=false|6hTiBl7lVpd1P。
3.签名6hTiBl7lVpd1P是这样生成的:Hash(‘x$sfz32’+’true’)。 要设置的值与Secret相加再取哈希。
5.用户在发送HTTP请求时,篡改了authed值,设置头字段Cookie: authed=true|???。 由于用户不知道Secret,没法生成签名,只能随便填一个。
6.服务器收到HTTP请求,发现Cookie: authed=true|???。服务器开始进行校验: Hash(‘true’+’x$sfz32’),便会发现用户提供的签名不正确。

经过给Cookie添加签名,使得服务器得以知道Cookie被篡改。可是!!仍是有风险!

由于Cookie是明文传输的, 只要服务器设置过一次authed=true|xxxx我不就知道true的签名是xxxx了么, 之后就能够用这个签名来欺骗服务器了。所以Cookie中最好不要放敏感数据。 通常来说Cookie中只会放一个Session Id,而Session存储在服务器端。

(3)session的实现机制:

1.概述:Session 是存储在服务器端的,避免了在客户端Cookie中存储敏感数据。 Session 能够存储在HTTP服务器的内存中,也能够存在内存数据库(如redis)中, 对于重量级的应用甚至能够存储在数据库中。

例子:存储在redis中的Session为例,考察如何验证用户登陆状态的问题。

1.用户提交包含用户名和密码的表单,发送HTTP请求。

2.服务器验证用户发来的用户名密码。

3.若是正确则把当前用户名(一般是用户对象)存储到redis中,并生成它在redis中的ID。

这个ID称为Session ID,经过Session ID能够从Redis中取出对应的用户对象, 敏感数据(好比authed=true)都存储在这个用户对象中。

4.设置Cookie为sessionId=xxxxxx|checksum并发送HTTP响应, 仍然为每一项Cookie都设置签名。

5.用户收到HTTP响应后,便看不到任何敏感数据了。在此后的请求中发送该Cookie给服务器。

6.服务器收到此后的HTTP请求后,发现Cookie中有SessionID,进行放篡改验证。

7.若是经过了验证,根据该ID从Redis中取出对应的用户对象, 查看该对象的状态并继续执行业务逻辑。

实现上述过程,在Web应用中能够直接得到当前用户。 至关于在HTTP协议之上,经过Cookie实现了持久的会话。这个会话便称为Session。


3、Token认证机制相对于Cookie等机制的好处:

1. 支持跨域访问: Cookie是不容许垮域访问的,这一点对Token机制是不存在的,前提是传输的用户认证信息经过HTTP头传输。(垮域访问:两个域名之间不能跨过域名来发送请求或者请求数据)

2.无状态(也称:服务端可扩展行):Token机制在服务端不须要存储session信息,由于Token 自身包含了全部登陆用户的信息,只须要在客户端的cookie或本地介质存储状态信息.

3.更适用CDN: 能够经过内容分发网络请求你服务端的全部资料(如:javascript,HTML,图片等),而你的服务端只要提供API便可.

4.去耦: 不须要绑定到一个特定的身份验证方案。Token能够在任何地方生成,只要在你的API被调用的时候,你能够进行Token生成调用便可.

5.更适用于移动应用: 当你的客户端是一个原平生台(iOS, Android,Windows 8等)时,Cookie是不被支持的(你须要经过Cookie容器进行处理),这时采用Token认证机制就会简单得多。

6. CSRF:由于再也不依赖于Cookie,因此你就不须要考虑对CSRF(跨站请求伪造)的防范。

7.性能: 一次网络往返时间(经过数据库查询session信息)总比作一次HMACSHA256计算 的Token验证和解析要费时得多.

8.不须要为登陆页面作特殊处理: 若是你使用Protractor 作功能测试的时候,再也不须要为登陆页面作特殊处理.

9.基于标准化:你的API能够采用标准化的 JSON Web Token (JWT). 这个标准已经存在多个后端库(.NET, Ruby, Java,Python, PHP)和多家公司的支持(如:Firebase,Google, Microsoft).


4、JWT的Java实现:

概述:一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。这里咱们只使用简单的载荷,并将JSON对象进行base64编码获得token

过程:登陆为例子

第一次认证:第一次登陆,用户从浏览器输入用户名/密码,提交后到服务器的登陆处理的Action层(controller)
Login Action调用认证服务进行用户名密码认证,若是认证经过,Login Action层调用用户信息服务获取用户信息(包括完整的用户信息及对应权限信息);
返回用户信息后,Login Action从配置文件再通过工具类处理获取Token签名生成的秘钥信息,进行Token的生成
生成Token的过程当中能够调用第三方的JWT Lib生成签名后的JWT数据;
完成JWT数据签名后,将其设置到COOKIE对象中,并重定向到首页,完成登陆过程;

请求认证

使用:基于Token的认证机制会在每一次请求中都带上完成签名的Token信息,这个Token信息可能在COOKIE中,也可能在HTTP的Authorization头中;

注意:

客户端(APP客户端或浏览器)经过GET或POST请求访问资源(页面或调用API);
认证服务做为一个Middleware HOOK 对请求进行拦截,首先在cookie中查找Token信息,若是没有找到,则在HTTP Authorization Head中查找;
若是找到Token信息,则根据配置文件中的签名加密秘钥,调用JWT Lib对Token信息进行解密和解码;
完成解码并验证签名经过后,对Token中的exp、nbf、aud等信息进行验证;
所有经过后,根据获取的用户的角色权限信息,进行对请求的资源的权限逻辑判断;
若是权限逻辑判断经过则经过Response对象返回;不然则返回HTTP 401;

(1)使用JWT的包:maven导入

<!--JSON WEB TOKEN --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.6.0</version> </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

(2)一个生成token的工具类:

public class JavaWebToken { private static Logger log = Logger.getLogger(JavaWebToken.class); private static Key getKeyInstance() { // return MacProvider.generateKey(); //We will sign our JavaWebToken with our ApiKey secret SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary("APP"); Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName()); return signingKey; } public static String createJavaWebToken(Map<String, Object> claims) { return Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS256, getKeyInstance()).compact(); } public static Map<String, Object> verifyJavaWebToken(String jwt) { try { Map<String, Object> jwtClaims = Jwts.parser().setSigningKey(getKeyInstance()).parseClaimsJws(jwt).getBody(); return jwtClaims; } catch (Exception e) { log.error("json web token verify failed"); return null; } } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

(3)一个从request拿去session,而且解密session获得token获得用户id的类

public class AuthUtil { private static Map<String, Object> getClientLoginInfo(HttpServletRequest request) throws Exception { Map<String, Object> r = new HashMap<>(); String sessionId = request.getHeader("sessionId"); if (sessionId != null) { r = decodeSession(sessionId); return r; } throw new Exception("session解析错误"); } public static Long getUserId(HttpServletRequest request) throws Exception { return Long.valueOf((Integer)getClientLoginInfo(request).get("userId")); } /** * session解密 */ public static Map<String, Object> decodeSession(String sessionId) { try { return verifyJavaWebToken(sessionId); } catch (Exception e) { System.err.println(""); return null; } } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

使用例子:

登陆的时候把信息放进session,存到map里,再交由JWT获得token保存起来

这里写图片描述

//登陆 @RequestMapping(value = "/login", method = {RequestMethod.GET, RequestMethod.POST}, produces = "text/html;charset=UTF-8") public String login(String account) { User user = userService.login(account); DTO dto = new DTO(); if (user == null) { dto.code = "-1"; dto.msg = "Have not registered"; } else { //把用户登陆信息放进Session Map<String, Object> loginInfo = new HashMap<>(); loginInfo.put("userId", user.getId()); String sessionId = JavaWebToken.createJavaWebToken(loginInfo); System.out.println("sessionID"+sessionId); dto.data = sessionId; } return JSON.toJSONString(dto); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

用户登陆之后,其余的用户性知道的操做就可使用token进行了,安全快捷方便:

这里写图片描述

//修改昵称 @RequestMapping(value = "/updateName", method = {RequestMethod.GET, RequestMethod.POST}) public String updateName(HttpServletRequest request, String name) { DTO dto = new DTO(); try { //从session拿到token,再解密获得userid Long userId = AuthUtil.getUserId(request); boolean userIsExist = userService.updateName(userId, name); if (userIsExist == false) { dto.code = "-1"; dto.msg = "Have not updateAvatar"; } } catch (Exception e) { e.printStackTrace(); } return JSON.toJSONString(dto); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

这就是token机制的登陆认证功能简单实现了,安全机制等,之后会有博客补充。


源码下载:WEB后台–基于Token的WEB后台登陆认证机制(并讲解其余认证机制以及cookie和session机制)

好了,WEB后台–基于Token的WEB后台登陆认证机制(并讲解其余认证机制以及cookie和session机制)讲完了。本博客是这个系列的第四篇,讲的是一个简单的登陆机制实现。另外,这个系列还有一些我在外包项目过程当中作的优化,虽然核心算法跟安全处理不能够公布,但我会尽快出完给你们,分享经验给你们。欢迎在下面指出错误,共同窗习!!你的点赞是对我最好的支持!!

更多内容,能够访问JackFrost的博客

相关文章
相关标签/搜索