一:Session跨域

所谓Session跨域就是摒弃了系统(Tomcat)提供的Session(jsessionid是和servlet绑定的httpsession的唯一标记),而使用自定义的类似Session的机制来保存客户端数据的一种解决方案。如使用cookie跨域共享:通过设置cookie的domain来实现cookie的跨域传递。在cookie中传递一个自定义的session_id。这个session_id是客户端的唯一标记。将这个标记作为key,将客户端需要保存的数据作为value,在服务端进行保存(数据库保存或NoSQL保存)。这种机制就是Session的跨域解决。

跨域: 客户端请求的时候,请求的服务器,不是同一个IP,端口,域名,主机名的时候,都称为跨域。

域:在应用模型,一个完整的,有独立访问路径的功能集合称为一个域。如:百度称为一个应用或系统。百度下有若干的域,如:搜索引擎(www.baidu.com),百度贴吧(tie.baidu.com),百度知道(zhidao.baidu.com),百度地图(map.baidu.com)等。域信息,有时也称为多级域名。域的划分: 以IP,端口,域名,主机名为标准,实现划分。

new Cookie("", "") //新建cookie
request.getCookies() -> cookie[] // 迭代找到需要使用的cookie
response.addCookie() //向浏览器添加cokie
cookie.setDomain() // 为cookie设定有效域范围。
cookie.setPath() //为cookie设定有效URI范围。

二:Spring Session跨域

1:概念

spring-session技术是spring提供的用于处理集群会话共享的解决方案。spring-session技术是将用户session数据保存到三方存储容器中,如:mysql,redis等。Spring-session技术是解决同域名下的多服务器集群session共享问题的。不能解决跨域session共享问题。

使用: 配置一个Spring提供的Filter,实现数据的拦截保存,并转换为spring-session需要的会话对象。必须提供一个数据库的表格信息(由spring-session提供,找spring-session-jdbc.jar/org/springframework/session/jdbc/*.sql,根据具体的数据库找对应的SQL文件,做表格的创建)。

spring-session表:保存客户端session对象的表格。

spring-session-attributes表:保存客户端session中的attributes属性数据的表格。

spring-session框架,是结合Servlet技术中的HTTPSession完成的会话共享机制。在代码中是直接操作HttpSession对象的。

2:主要配置信息

pom.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>hhxy</groupId><artifactId>sso-spring-session</artifactId><version>1.0</version><packaging>war</packaging><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.0.6.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.0.6.RELEASE</version></dependency><dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-jdbc</artifactId><version>2.0.3.RELEASE</version></dependency><dependency><groupId>javax.servlet</groupId><artifactId>jstl</artifactId><version>1.2</version></dependency><dependency><groupId>javax.servlet</groupId><artifactId>servlet-api</artifactId><version>2.5</version><scope>provided</scope></dependency><dependency><groupId>javax.servlet.jsp</groupId><artifactId>jsp-api</artifactId><version>2.2</version><scope>provided</scope></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.39</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency></dependencies><build><pluginManagement><plugins><!-- 配置Tomcat插件 --><plugin><groupId>org.apache.tomcat.maven</groupId><artifactId>tomcat7-maven-plugin</artifactId><version>2.2</version></plugin></plugins></pluginManagement><plugins><plugin><groupId>org.apache.tomcat.maven</groupId><artifactId>tomcat7-maven-plugin</artifactId><configuration><port>80</port><path>/</path></configuration></plugin></plugins></build>
</project>

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://java.sun.com/xml/ns/javaee"xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"id="WebApp_ID" version="2.5"><display-name>sso-cross-domain</display-name><welcome-file-list><welcome-file>index.html</welcome-file><welcome-file>index.htm</welcome-file><welcome-file>index.jsp</welcome-file><welcome-file>default.html</welcome-file><welcome-file>default.htm</welcome-file><welcome-file>default.jsp</welcome-file></welcome-file-list><filter><filter-name>springSessionRepositoryFilter</filter-name><filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class></filter><filter-mapping><filter-name>springSessionRepositoryFilter</filter-name><url-pattern>/*</url-pattern><dispatcher>REQUEST</dispatcher><dispatcher>ERROR</dispatcher></filter-mapping><filter><filter-name>charSetFilter</filter-name><filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class><init-param><param-name>encoding</param-name><param-value>UTF-8</param-value></init-param></filter><filter-mapping><filter-name>charSetFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping><servlet><servlet-name>mvc</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:applicationContext-mvc.xml</param-value></init-param><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>mvc</servlet-name><url-pattern>/</url-pattern></servlet-mapping>
</web-app>

applicationContext-mvc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><context:component-scan base-package="hhxy.controller" /><!-- 为SpringMVC配置注解驱动 --><mvc:annotation-driven /><!-- 为Spring基础容器开启注解配置信息 --><context:annotation-config /><!-- 就是用于提供HttpSession数据持久化操作的Bean对象。对象定义后,可以实现数据库相关操作配置,自动的实现HttpSession数据的持久化操作(CRUD)--><beanclass="org.springframework.session.jdbc.config.annotation.web.http.JdbcHttpSessionConfiguration" /><bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"><property name="url" value="jdbc:mysql://localhost:3306/testspringsession?useUnicode=true&amp;characterEncoding=UTF8"></property><property name="username" value="root"></property><property name="password" value="root"></property><property name="driverClassName" value="com.mysql.jdbc.Driver"></property></bean><!-- 事务管理器。为JdbcHttpSessionConfiguration提供的事务管理器。 --><beanclass="org.springframework.jdbc.datasource.DataSourceTransactionManager"><constructor-arg ref="dataSource" /></bean></beans>

3:执行流程

三:Nginx Session跨域

做反向代理服务器,可以为反向代理的服务器集群做集群管理和负载均衡。

正向代理: 对客户端已知,对服务端透明的代理应用,称为正向代理。如:翻墙软件。

反向代理: 对服务端已知,对客户端透明的代理应用,称为反向代理。

如:nginx中的ip_hash技术能够将某个ip的请求定向到同一台后端,这样一来这个ip下的某个客户端和某个后端就能建立起稳固的session,ip_hash是在upstream配置中定义的,具体如下:

upstream nginx.example.com
{server 127.0.0.1:8080 weight 1;server 127.0.0.1:808 weight 2;ip_hash;
}
server
{listen 80;location /{proxy_passhttp://nginx.example.com;proxy_set_header Host  $http_host;proxy_set_header Cookie $http_cookie;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;client_max_body_size  100m;}
}

四:Token机制

1:传统身份认证

HTTP 是一种没有状态的协议,也就是它并不知道是谁是访问应用。这里我们把用户看成是客户端,客户端使用用户名还有密码通过了身份验证,不过下回这个客户端再发送请求时候,还得再验证一下。解决的方法就是,当用户请求登录的时候,如果没有问题,我们在服务端生成一条记录,这个记录里可以说明一下登录的用户是谁,然后把这条记录的 ID 号发送给客户端,客户端收到以后把这个 ID 号存储在 Cookie 里,下次这个用户再向服务端发送请求的时候,可以带着这个 Cookie ,这样服务端会验证一个这个 Cookie 里的信息,看看能不能在服务端这里找到对应的记录,如果可以,说明用户已经通过了身份验证,就把用户请求的数据返回给客户端。上面说的就是 Session,我们需要在服务端存储为登录的用户生成的 Session ,这些 Session 可能会存储在内存,磁盘,或者数据库里。我们可能需要在服务端定期的去清理过期的 Session 。

这种认证中出现的问题是:

Session:每次认证用户发起请求时,服务器需要去创建一个记录来存储信息。当越来越多的用户发请求时,内存的开销也会不断增加。

可扩展性:在服务端的内存中使用Session存储登录信息,伴随而来的是可扩展性问题。

CORS(跨域资源共享):当我们需要让数据跨多台移动设备上使用时,跨域资源的共享会是一个让人头疼的问题。在使用Ajax抓取另一个域的资源,就可以会出现禁止请求的情况。

CSRF(跨站请求伪造):用户在访问银行网站时,他们很容易受到跨站请求伪造的攻击,并且能够被利用其访问其他的网站。

在这些问题中,可扩展性是最突出的。因此我们有必要去寻求一种更有行之有效的方法。

2:Token身份认证

使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录。大概的流程是这样的:客户端使用用户名、密码请求登录,服务端收到请求,去验证用户名、密码,验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端,客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 、Session Storage里客户端每次向服务端请求资源的时候需要带着服务端签发的 Token,服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据。

使用Token验证的优势:

无状态、可扩展:在客户端存储的Tokens是无状态的,并且能够被扩展。基于这种无状态和不存储Session信息,负载负载均衡器能够将用户信息从一个服务传到其他服务器上。

安全性:请求中发送token而不再是发送cookie能够防止CSRF(跨站请求伪造)。即使在客户端使用cookie存储token,cookie也仅仅是一个存储机制而不是用于认证。不将信息存储在Session中,让我们少了对session操作。

五、    JSON Web Token(JWT)机制

JWT是一种紧凑且自包含的,用于在多方传递JSON对象的技术。传递的数据可以使用数字签名增加其安全行。可以使用HMAC加密算法或RSA公钥/私钥加密方式。
紧凑:数据小,可以通过URL,POST参数,请求头发送。且数据小代表传输速度快。
自包含:使用payload数据块记录用户必要且不隐私的数据,可以有效的减少数据库访问次数,提高代码性能。
JWT一般用于处理用户身份验证或数据信息交换。
用户身份验证:一旦用户登录,每个后续请求都将包含JWT,允许用户访问该令牌允许的路由,服务和资源。单点登录是当今广泛使用JWT的一项功能,因为它的开销很小,并且能够轻松地跨不同域使用。
数据信息交换:JWT是一种非常方便的多方传递数据的载体,因为其可以使用数据签名来保证数据的有效性和安全性。

1:JWT数据结构

JWT的数据结构是 : A.B.C。 由字符点‘.’来分隔三部分数据。

A - header 头信息

B - payload (有效荷载?)

C - Signature 签名

1.1:header

数据结构: {“alg”: “加密算法名称”, “typ” : “JWT”}

alg是加密算法定义内容,如:HMAC SHA256 或 RSA

typ是token类型,这里固定为JWT。

1.2:payload

在payload数据块中一般用于记录实体(通常为用户信息)或其他数据的。主要分为三个部分,分别是:已注册信息(registered claims),公开数据(public claims),私有数据(private claims)。

payload中常用信息有:iss(发行者),exp(到期时间),sub(主题),aud(受众)等。前面列举的都是已注册信息。

公开数据部分一般都会在JWT注册表中增加定义。避免和已注册信息冲突。公开数据和私有数据可以由程序员任意定义。

注意:即使JWT有签名加密机制,但是payload内容都是明文记录,除非记录的是加密数据,否则不排除泄露隐私数据的可能。不推荐在payload中记录任何敏感数据。

1.3:Signature

签名信息。这是一个由开发者提供的信息。是服务器验证的传递的数据是否有效安全的标准。在生成JWT最终数据的之前。先使用header中定义的加密算法,将header和payload进行加密,并使用点进行连接。如:加密后的head.加密后的payload。再使用相同的加密算法,对加密后的数据和签名信息进行加密。得到最终结果。

2:JWT执行流程

六:基于JWT机制的单点登录

1:核心代码

1.1:前端index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="js/jquery.min.js"></script>
<script type="text/javascript">function login(){var username = $("#username").val();var password = $("#password").val();var params = "username="+username+"&password="+password;$.ajax({'url' : '${pageContext.request.contextPath }/login','data' : params,'success' : function(data){if(data.code == 200){var token = data.token;// web storage的查看 - 在浏览器的开发者面板中的application中查看。// local storage - 本地存储的数据。 长期有效的。// session storage - 会话存储的数据。 一次会话有效。var localStorage = window.localStorage; // 浏览器提供的存储空间。 根据key-value存储数据。localStorage.token = token;}else{alert(data.msg);}}});
}function setHeader(xhr){ // XmlHttpRequestxhr.setRequestHeader("Authorization",window.localStorage.token);
}function testLocalStorage(){$.ajax({'url' : '${pageContext.request.contextPath}/testAll','success' : function(data){if(data.code == 200){window.localStorage.token = data.token;alert(data.data);}else{alert(data.msg);}},'beforeSend' : setHeader});
}</script>
</head>
<body ><center><table><caption>登录测试</caption><tr><td style="text-align: right; padding-right: 5px">登录名:</td><td style="text-align: left; padding-left: 5px"><input type="text" name="username" id="username"/></td></tr><tr><td style="text-align: right; padding-right: 5px">密码:</td><td style="text-align: left; padding-left: 5px"><input type="text" name="password" id="password"/></td></tr><tr><td style="text-align: right; padding-right: 5px" colspan="2"><input type="button" value="登录" onclick="login();" /></td></tr></table></center><input type="button" value="testLocalStorage" onclick="testLocalStorage();"/>
</body>
</html>

1.2:后端核心代码

package hhxy.sso.commons;import java.util.Date;import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;/*** JWT工具*/
public class JWTUtils {// 服务器的key。用于做加解密的key数据。 如果可以使用客户端生成的key。当前定义的常亮可以不使用。private static final String JWT_SECERT = "test_jwt_secert" ;private static final ObjectMapper MAPPER = new ObjectMapper();public static final int JWT_ERRCODE_EXPIRE = 1005;//Token过期public static final int JWT_ERRCODE_FAIL = 1006;//验证不通过public static SecretKey generalKey() {try {// byte[] encodedKey = Base64.decode(JWT_SECERT); // 不管哪种方式最终得到一个byte[]类型的key就行byte[] encodedKey = JWT_SECERT.getBytes("UTF-8");SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");return key;} catch (Exception e) {e.printStackTrace();return null;}}/*** 签发JWT,创建token的方法。* @param id  jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。* @param iss jwt签发者* @param subject jwt所面向的用户。payload中记录的public claims。当前环境中就是用户的登录名。* @param ttlMillis 有效期,单位毫秒* @return token, token是一次性的。是为一个用户的有效登录周期准备的一个token。用户退出或超时,token失效。* @throws Exception*/public static String createJWT(String id,String iss, String subject, long ttlMillis) {// 加密算法SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;// 当前时间。long nowMillis = System.currentTimeMillis();// 当前时间的日期对象。Date now = new Date(nowMillis);SecretKey secretKey = generalKey();// 创建JWT的构建器。 就是使用指定的信息和加密算法,生成Token的工具。JwtBuilder builder = Jwts.builder().setId(id)  // 设置身份标志。就是一个客户端的唯一标记。 如:可以使用用户的主键,客户端的IP,服务器生成的随机数据。.setIssuer(iss).setSubject(subject).setIssuedAt(now) // token生成的时间。.signWith(signatureAlgorithm, secretKey); // 设定密匙和算法if (ttlMillis >= 0) { long expMillis = nowMillis + ttlMillis;Date expDate = new Date(expMillis); // token的失效时间。builder.setExpiration(expDate);}return builder.compact(); // 生成token}/*** 验证JWT* @param jwtStr* @return*/public static JWTResult validateJWT(String jwtStr) {JWTResult checkResult = new JWTResult();Claims claims = null;try {claims = parseJWT(jwtStr);checkResult.setSuccess(true);checkResult.setClaims(claims);} catch (ExpiredJwtException e) { // token超时checkResult.setErrCode(JWT_ERRCODE_EXPIRE);checkResult.setSuccess(false);} catch (SignatureException e) { // 校验失败checkResult.setErrCode(JWT_ERRCODE_FAIL);checkResult.setSuccess(false);} catch (Exception e) {checkResult.setErrCode(JWT_ERRCODE_FAIL);checkResult.setSuccess(false);}return checkResult;}/*** * 解析JWT字符串* @param jwt 就是服务器为客户端生成的签名数据,就是token。* @return* @throws Exception*/public static Claims parseJWT(String jwt) throws Exception {SecretKey secretKey = generalKey();return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody(); // getBody获取的就是token中记录的payload数据。就是payload中保存的所有的claims。}/*** 生成subject信息* @param subObj - 要转换的对象。* @return java对象->JSON字符串出错时返回null*/public static String generalSubject(Object subObj){try {return MAPPER.writeValueAsString(subObj);} catch (JsonProcessingException e) {e.printStackTrace();return null;}}}

2:注意

使用JWT实现单点登录时,需要注意token时效性。token是保存在客户端的令牌数据,如果永久有效,则有被劫持的可能。token在设计的时候,可以考虑一次性有效或一段时间内有效。如果设置有效时长,则需要考虑是否需要刷新token有效期问题。

3:token保存位置

使用JWT技术生成的token,客户端在保存的时候可以考虑cookie或localStorage。cookie保存方式,可以实现跨域传递数据。localStorage是域私有的本地存储,无法实现跨域。

4:webstorage

webstorage可保存的数据容量为5M。且只能存储字符串数据。webstorage分为localStorage和sessionStorage。

localStorage的生命周期是永久的,关闭页面或浏览器之后localStorage中的数据也不会消失。localStorage除非主动删除数据,否则数据永远不会消失。
       sessionStorage是会话相关的本地存储单元,生命周期是在仅在当前会话下有效。sessionStorage引入了一个“浏览器窗口”的概念,sessionStorage是在同源的窗口中始终存在的数据。只要这个浏览器窗口没有关闭,即使刷新页面或者进入同源另一个页面,数据依然存在。但是sessionStorage在关闭了浏览器窗口后就会被销毁。同时独立的打开同一个窗口同一个页面,sessionStorage也是不一样的。

七:Restful接口设计

1:Rest简述

REST(英文:Representational State Transfer,简称REST)描述了一个架构样式的网络系统,比如 web 应用程序。它首次出现在 2000 年 Roy Fielding 的博士论文中,他是 HTTP 规范的主要编写者之一。在目前主流的三种Web服务交互方案中,REST相比于SOAP(Simple Object Access protocol,简单对象访问协议)以及XML-RPC更加简单明了,无论是对URL的处理还是对Payload的编码,REST都倾向于用更加简单轻量的方法设计和实现。值得注意的是REST并没有一个明确的标准,而更像是一种设计的风格。

2    Restful简述

对应的中文是rest式的;Restful web service是一种常见的rest的应用,是遵守了rest风格的web服务;rest式的web服务是一种ROA(The Resource-Oriented Architecture)(面向资源的架构).

3    Restful特性

3.1    普通架构

每次请求的接口或者地址,都在做描述,例如查询的时候用了query,新增的时候用了save。如:

http://127.0.0.1/user/query/1 GET 根据用户id查询用户数据
http://127.0.0.1/user/save  POST 新增用户

3.2    Restful架构

使用get请求,就是查询.使用post请求,就是新增的请求,意图明显,没有必要做描述,这就是restful:

http://127.0.0.1/user/1  GET 根据用户id查询用户数据
http://127.0.0.1/user  POST 新增用户

3.3 :Restful操作方式

HTTP方法

资源操作

幂等性

是否安全

GET

查询

POST

新增

PUT

修改

DELETE

删除

注:幂等性:多次访问,结果资源状态是否相同。安全:访问是否会变更服务器资源状态

3.4:响应状态码

编码

HTTP方法

响应体内容

描述

200

get/put

资源数据

操作成功

201

post

源数据

创建成功

202

post/put/delete

请求已接受

204

delete/put

请求已处理,无返回数据

301

get

link 链接

资源已被移除

303

get

link

重定向

304

get

资源没有被修改

400

get/post/put/delete

错误提示消息

参数错误(缺少,格式错误等)

401

get/post/put/delete

错误提示消息

未授权

403

get/post/put/delete

错误提示消息

访问受限、授权过期

404

get/post/put/delete

错误提示消息

资源、服务未找到

405

get/post/put/delete

错误提示消息

不允许的HTTP方法

409

get/post/put/delete

错误提示消息

资源冲突或资源被锁定

415

get/post/put/delete

错误提示消息

不支持的数据类型或媒体类型

429

get/post/put/delete

错误提示消息

请求过多被限制

500

get/post/put/delete

错误提示消息

系统错误

501

get/post/put/delete

错误提示消息

接口未实现

4:基于SpringMVC的Restful核心代码

package hhxy.controller;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;import com.sxt.service.TestRestfulService;@RequestMapping("/user")
@Controller
public class TestRestfulController {  @Autowired  private TestRestfulService newUserService;  /** * 根据用户id查询用户数据 *  * @param id  path variable参数* @return */  @RequestMapping(value = "/{id}", method = RequestMethod.GET)  @ResponseBody  public ResponseEntity<String> queryUserById(@PathVariable("id") Long id) {  try {  String user = this.newUserService.queryUserById(id);  if (null == user) {  // 资源不存在,响应404  return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);  }  // 200  // return ResponseEntity.status(HttpStatus.OK).body(user);  return ResponseEntity.ok(user);  } catch (Exception e) {  e.printStackTrace();  }  // 500  return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);  }  /** * 新增用户 *  * @param user * @return */  @RequestMapping(method = RequestMethod.POST)  public ResponseEntity<Void> saveUser(String user) {  try {  this.newUserService.saveUser(user);  return ResponseEntity.status(HttpStatus.CREATED).build();  } catch (Exception e) {  // TODO Auto-generated catch block  e.printStackTrace();  }  // 500  return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);  }  /** * 更新用户资源 *  * @param user * @return */  @RequestMapping(method = RequestMethod.PUT)  public ResponseEntity<Void> updateUser(String user) {  try {  this.newUserService.updateUser(user);  return ResponseEntity.status(HttpStatus.NO_CONTENT).build();  } catch (Exception e) {  e.printStackTrace();  }  // 500  return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);  }  /** * 删除用户资源 *  * @param user * @return */  @RequestMapping(method = RequestMethod.DELETE)  public ResponseEntity<Void> deleteUser(@RequestParam(value = "id", defaultValue = "0") Long id) {  try {  if (id.intValue() == 0) {  // 请求参数有误  return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();  }  this.newUserService.deleteUserById(id);  // 204  return ResponseEntity.status(HttpStatus.NO_CONTENT).build();  } catch (Exception e) {  e.printStackTrace();  }  // 500  return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);  }
}

八:接口安全机制

在对外发布服务接口的时候,定制一套签名机制,保证数据传递有效性的。

1: 安全机制的设计方案

1.1:单向加密

在理论上,从明文加密到密文后,不可反向解密的。可以从迭代和加盐的方式尽可能保证加密数据不可反向解密。传递敏感数据的时候使用的。如:密码。在金融相关交易中,用户密码是敏感数据,其他数据是非敏感数据。所有的金融相关的应用中,客户端都有一个独立的密码输入控件。这个控件就是做单向加密的。

使用单向加密的时候,传递的数据只有密文,没有明文,也没有密钥。

1.2:双向加密

是可以实现加密和解密双向运算的算法。需要通过密钥实现加解密计算的。

密钥种类:公钥、私钥。

公钥:可以对外公开的,就是可以在网络中传递的。

私钥:必须保密的,绝对不会对外暴露的。

在传递安全数据的时候使用。所谓安全数据,就是不可篡改的数据。如:金融交易中的收款人卡号,转账的金额,货币的种类等。

使用双向加密的时候,传递数据可以有明文,密文,公钥。

1.2.1:对称加密

只有一个密钥,就是私钥。

1.2.2    非对称加密

有两个密钥,公钥和私钥。

2:DES加密

DES的密文是非定长密文。根据明文数据和key数据动态伸缩的。

2.1:核心代码

前端des.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%>
<html>
<head><meta http-equiv="content-type" content="text/html;charset=utf-8"><title>DES算法</title><script src="/js/jquery.min.js"></script><script src="/js/tripledes.js"></script><script src="/js/mode-ecb-min.js"></script><script>function uuid() {var s = [];var hexDigits = "0123456789abcdef";for (var i = 0; i < 36; i++) {s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);}s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01s[8] = s[13] = s[18] = s[23] = "-";var uuid = s.join("");return uuid;}/** 加密函数* message - 要加密的源数据* key - 密钥*/function encryptByDES(message, key) {// 解析密钥, 将密钥转换成16进制数据。 就是解析为字节数据。var keyHex = CryptoJS.enc.Utf8.parse(key);// 创建DES加密工具。 构建器。var encrypted = CryptoJS.DES.encrypt(message, keyHex, {mode: CryptoJS.mode.ECB, // 加密的模式, ECB加密模式。padding: CryptoJS.pad.Pkcs7 // 加密的padding});return encrypted.toString(); // 加密,并获取加密后的密文数据。}/** 解密函数* ciphertext - 要解密的密文数据。* key - 密钥*/function decryptByDES(ciphertext, key) {var keyHex = CryptoJS.enc.Utf8.parse(key);// 创建解密工具var decrypted = CryptoJS.DES.decrypt({ciphertext: CryptoJS.enc.Base64.parse(ciphertext) // 将密文数据解析为可解密的字节数据。}, keyHex, {mode: CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7});return decrypted.toString(CryptoJS.enc.Utf8); // 解密过程,并返回明文。}function doPost(){var name = $("#nameText").val();var password = $("#passwordText").val();var message = name + password;var key = uuid();var param = {};param.name=name;param.password=password;param.key=key;// 正确的加密param.message = encryptByDES(message, key);// 测试解密错误,如:请求拦截。// param.message = "WrongSecurityMessage00";// 测试异常情况。DES加密后的密文数据长度一定是8的整数倍。// param.message = "testException";$.ajax({'url':'/testDes','data':param,'success':function(data){if(data){alert("密文:"+data.securityMessage+";key:"+data.key);var respMsg = decryptByDES(data.securityMessage, data.key);alert(respMsg);}else{alert("服务器忙请稍后重试!");}}});}</script>
</head><body><center><table><caption>DES安全测试</caption><tr><td style="text-align: right; padding-right: 5px">姓名:</td><td style="text-align: left; padding-left: 5px"><input type="text" name="name" id="nameText"/></td></tr><tr><td style="text-align: right; padding-right: 5px">密码:</td><td style="text-align: left; padding-left: 5px"><input type="text" name="password" id="passwordText"/></td></tr><tr><td style="text-align: right; padding-right: 5px" colspan="2"><input type="button" value="测试" onclick="doPost();" /></td></tr></table></center>
</body>
</html>

后端核心代码

package hhxy.des.utils;import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import java.security.SecureRandom;/*** 加密解密工具类*/
public class DesCrypt {// 默认的KEY。此密匙应该根据用户推算或记录在物理存储中。private static final String KEY = "hhxy";// 字符编码。private static final String CODE_TYPE = "UTF-8";/*** DES加密* @param datasource 要加密的源数据。* @return 加密后的数据。*/public static String encode(String key, String datasource) throws Exception{if(null == key){key = KEY;}// 随机生成器。如果种子一样,则生成的随机信息可推测。SecureRandom random = new SecureRandom();// 创建DES密匙。依据提供的密匙字符串创建密匙。 密钥源信息。 需要通过密钥工厂再次推算的,才能得到最终的密钥数据。DESKeySpec desKey = new DESKeySpec(key.getBytes(CODE_TYPE));// 创建一个密匙工厂,然后用它把DESKeySpec转换成SecretKeySecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");SecretKey securekey = keyFactory.generateSecret(desKey);// Cipher对象实际完成加密操作Cipher cipher = Cipher.getInstance("DES");// 用密匙初始化Cipher对象。 Cipher.ENCRYPT_MODE - 加密模式cipher.init(Cipher.ENCRYPT_MODE, securekey, random);// 现在,获取数据并加密。// 加密后的数据不要new String。 java中的字符串对象都是有字符集信息的。// java中的UTF8字符集又是长度变化的。一个字符串长度为2~3byte[] temp = Base64.encodeBase64(cipher.doFinal(datasource.getBytes()));return IOUtils.toString(temp,"UTF-8");}/*** DES解密 * @param src 要解密的密文数据* @return*/public static String decode(String key, String src) throws Exception {if(null == key){key = KEY;}// DES算法要求有一个可信任的随机数源SecureRandom random = new SecureRandom();// 创建一个DESKeySpec对象DESKeySpec desKey = new DESKeySpec(key.getBytes(CODE_TYPE));// 创建一个密匙工厂SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");// 将DESKeySpec对象转换成SecretKey对象SecretKey securekey = keyFactory.generateSecret(desKey);// Cipher对象实际完成解密操作Cipher cipher = Cipher.getInstance("DES");// 用密匙初始化Cipher对象。 Cipher.DECRYPT_MODE - 解密模式cipher.init(Cipher.DECRYPT_MODE, securekey, random);// 真正开始解密操作return IOUtils.toString(cipher.doFinal(Base64.decodeBase64(src)),"UTF-8");}public static String getKEY(){return KEY;}}

3:AES加密

AES的key要求长度为16。

3.1:核心代码

前端aes.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding="UTF-8"%>
<html>
<head><meta http-equiv="content-type" content="text/html;charset=utf-8"><title>AES算法</title><script src="/js/jquery.min.js"></script><script src="/js/aes.min.js"></script><script>// 随机数生成算法。 len-生成结果的长度, radix-生成结果的组成,是二进制,十进制还是十六进制数。function uuid(len, radix) {var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');var uuid = [], i;radix = radix || chars.length;if (len) {// Compact formfor (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random()*radix];} else {// rfc4122, version 4 formvar r;// rfc4122 requires these charactersuuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';uuid[14] = '4';// Fill in random data.  At i==19 set the high bits of clock sequence as// per rfc4122, sec. 4.1.5for (i = 0; i < 36; i++) {if (!uuid[i]) {r = 0 | Math.random()*16;uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];}}}return uuid.join('');}/** 加密函数* message - 明文数据* key - 密钥*/function encryptByAES(message, key){var keyHex = CryptoJS.enc.Utf8.parse(key);var srcs = CryptoJS.enc.Utf8.parse(message);var encrypted = CryptoJS.AES.encrypt(srcs, keyHex, {mode:CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7});return encrypted.toString();}/** 解密函数* ciphertext - 要解密的密文。*/function decryptByAES(ciphertext, key){var keyHex = CryptoJS.enc.Utf8.parse(key);var decrypt = CryptoJS.AES.decrypt(ciphertext, keyHex, {mode:CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7});return CryptoJS.enc.Utf8.stringify(decrypt).toString();}function doPost(){var name = $("#nameText").val();var password = $("#passwordText").val();var message = name + password;var key = uuid(32,16);var param = {};param.name=name;param.password=password;param.key=key;// 正确的加密param.message = encryptByAES(message, key);// 测试解密错误,如:请求拦截。// param.message = "WrongSecurityMessage00";// 测试异常情况。AES加密后的密文数据长度一定是8的整数倍。// param.message = "testException";$.ajax({'url':'/testAes','data':param,'success':function(data){if(data){alert("密文:"+data.securityMessage+";key:"+data.key);var respMsg = decryptByAES(data.securityMessage, data.key);alert(respMsg);}else{alert("服务器忙请稍后重试!");}}});}</script>
</head><body><center><table><caption>AES安全测试</caption><tr><td style="text-align: right; padding-right: 5px">姓名:</td><td style="text-align: left; padding-left: 5px"><input type="text" name="name" id="nameText"/></td></tr><tr><td style="text-align: right; padding-right: 5px">密码:</td><td style="text-align: left; padding-left: 5px"><input type="text" name="password" id="passwordText"/></td></tr><tr><td style="text-align: right; padding-right: 5px" colspan="2"><input type="button" value="测试" onclick="doPost();" /></td></tr></table></center>
</body>
</html>

后端核心代码

package hhxy.aes.utils;import java.math.BigInteger;import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;import sun.misc.BASE64Decoder;/*** AES的加密和解密* @author libo*/
public class AesCrypt {// 密钥 ,长度是16private static final String KEY = "hhxy";  // 算法private static final String ALGORITHMSTR = "AES/ECB/PKCS5Padding";// private static final String ALGORITHMSTR = "AES/CBC/PKCS5Padding";/** * 将byte[]转为各种进制的字符串 * @param bytes byte[] * @param radix 可以转换进制的范围,从Character.MIN_RADIX到Character.MAX_RADIX,超出范围后变为10进制 * @return 转换后的字符串 */  public static String binary(byte[] bytes, int radix){  return new BigInteger(1, bytes).toString(radix);// 这里的1代表正数  }  /** * base 64 encode * @param bytes 待编码的byte[] * @return 编码后的base 64 code */  public static String base64Encode(byte[] bytes){  return Base64.encodeBase64String(bytes);  }  /** * base 64 decode * @param base64Code 待解码的base 64 code * @return 解码后的byte[] * @throws Exception */  public static byte[] base64Decode(String base64Code) throws Exception{  return StringUtils.isEmpty(base64Code) ? null : new BASE64Decoder().decodeBuffer(base64Code);  }  /** * AES加密 * @param content 待加密的内容 * @param encryptKey 加密密钥 * @return 加密后的byte[] * @throws Exception */  public static byte[] aesEncryptToBytes(String content, String encryptKey) throws Exception {  // 密钥生成器// KeyGenerator kgen = KeyGenerator.getInstance("AES");  // 密钥生成器初始化, 密钥生成器初始化,会影响到Cipher的处理。// kgen.init(128);  Cipher cipher = Cipher.getInstance(ALGORITHMSTR);  /*// AES/CBC/PKCS5Padding 算法模式为CBC可以增加偏移量,可增加加密算法强度。String ivParameter = "0392039203920300";IvParameterSpec iv = new IvParameterSpec(ivParameter.getBytes());cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptKey.getBytes(), "AES"), iv);  */// 初始化, Cipher.ENCRYPT_MODE-加密模式, SecretKeySpec-具体的密钥cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptKey.getBytes(), "AES"));  return cipher.doFinal(content.getBytes("utf-8"));  }  /** * AES加密为base 64 code * @param content 待加密的内容 * @param encryptKey 加密密钥 * @return 加密后的base 64 code * @throws Exception */  public static String aesEncrypt(String content, String encryptKey) throws Exception {  return base64Encode(aesEncryptToBytes(content, encryptKey));  }  /** * AES解密 * @param encryptBytes 待解密的byte[] * @param decryptKey 解密密钥 * @return 解密后的String * @throws Exception */  public static String aesDecryptByBytes(byte[] encryptBytes, String decryptKey) throws Exception {  /* KeyGenerator kgen = KeyGenerator.getInstance("AES");  kgen.init(128);  */Cipher cipher = Cipher.getInstance(ALGORITHMSTR);  // Cipher.DECRYPT_MODE - 解密模式cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(decryptKey.getBytes(), "AES"));  byte[] decryptBytes = cipher.doFinal(encryptBytes);  return new String(decryptBytes);  }  /** * 将base 64 code AES解密 * @param encryptStr 待解密的base 64 code * @param decryptKey 解密密钥 * @return 解密后的string * @throws Exception */  public static String aesDecrypt(String encryptStr, String decryptKey) throws Exception {  return StringUtils.isEmpty(encryptStr) ? null : aesDecryptByBytes(base64Decode(encryptStr), decryptKey);  }   public static String getKEY(){return KEY;}/*** 测试*/public static void main(String[] args) throws Exception {  String content = "testtest";  System.out.println("加密前:" + content);  System.out.println("加密密钥和解密密钥:" + KEY);  String encrypt = aesEncrypt(content, KEY);  System.out.println("加密后:" + encrypt);  String decrypt = aesDecrypt(encrypt, KEY);  System.out.println("解密后:" + decrypt);  }
}

4    使用场景

DES和AES在使用场景上没有区别。传递非敏感的安全性数据可以使用。如:QQ通讯录获取,微信中的消息传递。

5    对比

DES - 加密后的数据是16的整数倍。 是16字节整数倍。

AES - 要求key的长度必须是16字节。 AES相对效率较低,但是可以通过偏移量强化加密。

单点登录--Day10相关推荐

  1. [转]单点登录原理与简单实现

    一.单系统登录机制 1.http无状态协议 web应用采用browser/server架构,http作为通信协议.http是无状态协议,浏览器的每一次请求,服务器会独立处理,不与之前或之后的请求产生关 ...

  2. 在ASP.NET 中实现单点登录

    出自:[孟宪会之精彩世界] 发布日期:2005年1月27日 8点48分0秒 [有删改] 由于某些原因,在我们的应用中会遇到一个用户只能在一个地方登录的情况,也就是我们通常所说的单点登录.在ASP.NE ...

  3. 单点登录与权限管理本质:session和cookie介绍

    本篇开始写「单点登录与权限管理」系列的第一部分:单点登录与权限管理本质,这部分主要介绍相关的知识概念.抽象的处理过程.常见的实现框架.通过这部分的介绍,能够对单点登录与权限管理有整体上的了解,对其相关 ...

  4. 【C#】ASP.NET网页中添加单点登录功能

    背景 首先,要说明的是,原先需求定义的是,同一个账号只能同时有一个人来登录,如果另外一个登录的话,前一个登陆者就自动被踢掉.本来原先要做成存储到服务器的数据库中,但是后来如果是非正常退出的话 下次就没 ...

  5. 配置Exchange OWA和Sharepoint网站单点登录

    配置Exchange OWA和Sharepoint网站单点登录 如果我们在组织中已经部署完成了Lync.Exchange以及Sharepoint,那么我们会发现这三套系统在通过域账户登录计算机时,如果 ...

  6. Spring Cloud云架构 - SSO单点登录之OAuth2.0登录流程(2)

    上一篇是站在巨人的肩膀上去研究OAuth2.0,也是为了快速帮助大家认识OAuth2.0,闲话少说,我根据框架中OAuth2.0的使用总结,画了一个简单的流程图(根据用户名+密码实现OAuth2.0的 ...

  7. SSO单点登录基于CAS架构封装 Memcached 实例

    2019独角兽企业重金招聘Python工程师标准>>> SSO认证中心是CAS整个应用架构的一个极其重要的关键点,必须满足如下两点要求: 1.高可用,不允许程序发生故障.如果认证中心 ...

  8. 使用 CAS 在 Tomcat 中实现单点登录

    CAS 介绍 CAS 是 Yale 大学发起的一个开源项目,旨在为 Web 应用系统提供一种可靠的单点登录方法,CAS 在 2004 年 12 月正式成为 JA-SIG 的一个项目.CAS 具有以下特 ...

  9. SharePoint 2010 单点登录

    SharePoint2010单点登录 1.进入管理中心>应用程序管理 2.找到  Secure Store Service 应用程序代理 3.然后就是新建了 5.输入网站集管理员 6.这个时候S ...

  10. 单点登录系统用 8 张漫画就解释了

    以下文章来源方志朋的博客,回复"666"获面试宝典 来源:http://blog.leapoahead.com/2015/09/07/user-authentication-wit ...

最新文章

  1. 1-1 机器学习和深度学习综述-paddle
  2. 【opencv】(13) 案例:停车场空余车位检测,附python完整代码
  3. 报名 | 贝叶斯计算方法在生物制药领域的应用(基于SAS)
  4. 测试 C、Python、Java 等 16 种编程语言的 Hello World:7 种存在 Bug?
  5. LOL手游王者局诺手教学,3级压制阿卡丽,全程一个拼字
  6. P3041-[USACO12JAN]Video Game G【AC自动机,dp】
  7. 前端学习(955):移动端特效导读
  8. ThinkPad P73 拆机清灰日志
  9. 数据库中了勒索病毒,怎么办?
  10. Elasticsearch 使用同义词 二
  11. 下岗工冰城卖火“鱼豆腐”
  12. 中国石油大学(北京)-《安全检测与监测》第一阶段在线作业
  13. Vmware 打不开vmx文件
  14. 从零了解进程(操作系统定位,进程的概念,特征,虚拟地址)
  15. 记一次频繁YoungGC生产问题排查思路及解决方案
  16. Easy App Locker - 给你的 mac 应用加锁保护你的隐私
  17. GPLT团体程序设计天梯赛 L1-088 静静的推荐
  18. 斯密特正交化+多项式拟合
  19. ajax远程调用,jquery中的ajax方法怎样通过JSONP进行远程调用
  20. office 2010 excle不能打开多个窗口

热门文章

  1. jarvisoj_tell_me_something
  2. 2016创业五大风向标:看十大硅谷前沿公司都在做什么?
  3. 2022-2027年中国弹簧床垫行业发展监测及投资战略研究报告
  4. 基于matlab回声,基于MATLAB的回声状态网络,可用于趋势测量
  5. Saving Tang Monk(翻译)
  6. 一致性哈希的学习和理解
  7. Linux系统下设定用户ssh登陆超时
  8. Magicol: Indoor Localization Using Pervasive Magnetic Field and Opportunistic WiFi Sensing
  9. 机器学习 基础理论 学习笔记 (1)方差与偏差
  10. MCmod:冰与火之歌:龙骑士(一)