WEB应用前后端分离实践 关键词:rest架构+跨域访问+JWT校验
以往一直习惯将技术资料整理到有道笔记上,最近临时搭建了一个WEB应用,相关技术点在网上搜罗了大半天,很多都是支离破碎,走了不少弯路,不过最终还是成功了。遂考虑将这次实践过程分享出来,技术的根本是为了解决问题, 但技术的进步关键还是要靠分享,帖子比较长,看官自行找重点。
话不多说,直接进入正题。最近半个月用了洪荒之力开发了一套基于前后端分离架构的企业级的商户网站。为什么要采用前后端分离技术,很简单,目的是为了让后端人员专注做服务接口设计,前端人员专注用户体验设计,不像ERP系统那样禁锢与一贯的页面风格。
项目具体采用以下技术:
服务框架: jersey+mybaties 这个框架一般用的不多,但是作为rest框架而言,与SPRING只是大同小异,但是代码的规范性更强,因为是纯粹的接口框架。
登录缓存控制: JWT JWT曾和SESSION有过不少争议。session的原理在于由服务端保存用户的sessionid,弊端就是会占用服务器大量内存,所以在大型项目当中会借用缓存工具进行存储。JWT的方式是将用户状态分散到了各个客户端,这样减轻了服务器的压力,也可不必使用缓存来处理,因为JWT的原理是通过将用户信息进行base64编码与加密签名来识别用户身份,当然JWT当中也允许加入用户的其他身份或者角色信息。具体JWT原理各位有兴趣可以去百度。
跨域访问机制:CORS 在解决跨域问题上,遇到了很大的坑,都是浏览器的同源策略害的,也是打算这次发帖的一个重要原因。
先来分析一下跨域请求的一些机制:目前跨域请求大概有这么几种,个人觉得利弊如下:
1、JSONP 这个方式对于我个人而言,确实没有一点P用, 客户端都是AJAX的POST请求,然后JSONP只支持GET请求方式,所以放弃使用
2、iframe 很久以前做前端的时候,友人就告诫我能不用iframe的时候尽量少用。毕竟商户端网站后期的改造还是比较复杂,不排除会出现什么其他的坑。
3、proxy代理 曾考虑在跨域无法访问的情况下,做一个springMvc工程作为代理跳转,不过这样做,要同时维护两个项目,在成本上不到万不得已不提倡。
4、cors 最终选择这种方式,是因为配置上比较简单,而网上很多帖子都是通过cors实现跨域功能,但在实际调试过程中也踩过不少坑。后面慢慢介绍。
先说坑,关键字:
1、跨域功能在服务器上无法实现(本地正常)
2、nginx配置自定义header过滤(本地正常)
3、登录过滤器问题
4、jersey文件上传带参数获取
下面我分三点把项目情况描述一下:
一、JWT用户校验
机制是这样,用户第一次登陆系统,根据用户相关信息,生成一串BASE64编码作为用户token,返回给客户端,客户端每次请求将这个token添加到request 请求头当中,系统过滤器通过对token的校验来验证用户是否合法。这里面存在两个问题:1、用户注销系统如何操作 2、token在请求头当中如何传输。直接看代码:
(一)、jar包引用:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.6.0</version>
</dependency>
(二)、登录方法:
(一)、TOKEN 工具类
包含创建token,获取token附带信息。这里是把key文件保存在了项目当中,这个key文件只能生成一次,用户验证是通过这个key文件来处理,实际上是一串序列化的字符串。也可以改造成放入到数据库当中。
话不多说,直接进入正题。最近半个月用了洪荒之力开发了一套基于前后端分离架构的企业级的商户网站。为什么要采用前后端分离技术,很简单,目的是为了让后端人员专注做服务接口设计,前端人员专注用户体验设计,不像ERP系统那样禁锢与一贯的页面风格。
项目具体采用以下技术:
服务框架: jersey+mybaties 这个框架一般用的不多,但是作为rest框架而言,与SPRING只是大同小异,但是代码的规范性更强,因为是纯粹的接口框架。
登录缓存控制: JWT JWT曾和SESSION有过不少争议。session的原理在于由服务端保存用户的sessionid,弊端就是会占用服务器大量内存,所以在大型项目当中会借用缓存工具进行存储。JWT的方式是将用户状态分散到了各个客户端,这样减轻了服务器的压力,也可不必使用缓存来处理,因为JWT的原理是通过将用户信息进行base64编码与加密签名来识别用户身份,当然JWT当中也允许加入用户的其他身份或者角色信息。具体JWT原理各位有兴趣可以去百度。
跨域访问机制:CORS 在解决跨域问题上,遇到了很大的坑,都是浏览器的同源策略害的,也是打算这次发帖的一个重要原因。
先来分析一下跨域请求的一些机制:目前跨域请求大概有这么几种,个人觉得利弊如下:
1、JSONP 这个方式对于我个人而言,确实没有一点P用, 客户端都是AJAX的POST请求,然后JSONP只支持GET请求方式,所以放弃使用
2、iframe 很久以前做前端的时候,友人就告诫我能不用iframe的时候尽量少用。毕竟商户端网站后期的改造还是比较复杂,不排除会出现什么其他的坑。
3、proxy代理 曾考虑在跨域无法访问的情况下,做一个springMvc工程作为代理跳转,不过这样做,要同时维护两个项目,在成本上不到万不得已不提倡。
4、cors 最终选择这种方式,是因为配置上比较简单,而网上很多帖子都是通过cors实现跨域功能,但在实际调试过程中也踩过不少坑。后面慢慢介绍。
先说坑,关键字:
1、跨域功能在服务器上无法实现(本地正常)
2、nginx配置自定义header过滤(本地正常)
3、登录过滤器问题
4、jersey文件上传带参数获取
下面我分三点把项目情况描述一下:
一、JWT用户校验
机制是这样,用户第一次登陆系统,根据用户相关信息,生成一串BASE64编码作为用户token,返回给客户端,客户端每次请求将这个token添加到request 请求头当中,系统过滤器通过对token的校验来验证用户是否合法。这里面存在两个问题:1、用户注销系统如何操作 2、token在请求头当中如何传输。直接看代码:
(一)、jar包引用:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.6.0</version>
</dependency>
(二)、登录方法:
@POST
@Path("/loginUser")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public Response loginUser(String resquestJsonBody,@Context HttpServletRequest request)
{
logger.info(String.format("Request IP--%s;requestJsonBody--%s", IpUtils.getIpAddr(request), resquestJsonBody));
ZyLoginResponseVo zyLoginResponseVo = new ZyLoginResponseVo();
String jsonRequestStr = RequestUtils.str2Json(resquestJsonBody);
ZyLoginRequestVo zyLoginRequestVo = JSONObject.parseObject(jsonRequestStr, ZyLoginRequestVo.class);
Map<String,Object> loginResult = zyLoginApiService.loginByUserName(zyLoginRequestVo);
int code = Integer.parseInt(String.valueOf(loginResult.get("loginCode")));
if (loginResult.get("loginCode").equals(Constants.AUTH_RESULT_SUCCESS))
{
//查询用户信息
ZyUserDto resultDto= zyLoginApiService.findUserByName(zyLoginRequestVo.getUser_name());
if(null == resultDto){
//用户不存在
zyLoginResponseVo.setCode(code);
zyLoginResponseVo.setDesc("User is not exists!!");
logger.info("The responseJsonObject:" + JSONObject.toJSONString(zyLoginResponseVo));
return Response.status(200).entity(JSONObject.toJSONString(zyLoginResponseVo)).type(new MediaType(CommonStr.APPLICATION, CommonStr.JSON, CommonStr.UTF8)).build();
}
//组装缓存数据
String userName = String.valueOf(loginResult.get("userName"));
String mobilePhone = String.valueOf(loginResult.get("mobilePhone"));
Map<String,Object> claims = new HashMap<String,Object>();
claims.put("acessToken", loginResult.get("accessToken"));
claims.put("companyname", resultDto.getCompanyname());
if(StringUtils.isEmpty(userName)){
claims.put("name", resultDto.getUsername());
}else{
claims.put("name", userName);
}
if(StringUtils.isEmpty(mobilePhone)){
claims.put("phone", resultDto.getPhone());
}else{
claims.put("phone", mobilePhone);
}
claims.put("status", resultDto.getStatus());
claims.put("companykey", resultDto.getCompanykey());
claims.put("companyid", resultDto.getCompanyid());
claims.put("id", resultDto.getId());
// 设置这个token的生命时间
Date expiry = getExpiryDate(30 * 24 * 60);//30天的有效日期
// 使用Token工具类得到token,生成的策略是利用用户的姓名,到期时间,和私钥
// 使用Key key =MacProvider.generateKey(SignatureAlgorithm.HS512);
// HS512签名算法,必须保存生成的这个key到硬盘上,不然下次会出错,因为是hash算法,所以会变
String userToken = TokenUtil.getJWTString(zyLoginRequestVo.getUser_name(), expiry,KeyUtil.getKey(context),resultDto.getCompanyid(),claims);
logger.info("login Success, Token is:" + userToken+"; user is :"+userName);
System.err.println("token is :"+userToken);
zyLoginResponseVo.setCode(code);
zyLoginResponseVo.createInstanceData().setToken(userToken);
zyLoginResponseVo.setDesc("登录成功!");
}else{
String desc = ResultCodeDescUtil.getResultDesc(code);
zyLoginResponseVo.setCode(code);
zyLoginResponseVo.setDesc(desc);
}
logger.info("The responseJsonObject:" + JSONObject.toJSONString(zyLoginResponseVo));
return Response.status(200).entity(JSONObject.toJSONString(zyLoginResponseVo)).type(new MediaType(CommonStr.APPLICATION, CommonStr.JSON, CommonStr.UTF8)).build();
}(一)、TOKEN 工具类
包含创建token,获取token附带信息。这里是把key文件保存在了项目当中,这个key文件只能生成一次,用户验证是通过这个key文件来处理,实际上是一串序列化的字符串。也可以改造成放入到数据库当中。
package ichano.developer.util;/**
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.security.Key;
import java.util.Date;
import java.util.Map;
import javax.annotation.security.PermitAll;
/**
* @ClassName: TokenUtil
* @Description: TODO(token管理工具类)
* @author lizhijun
* @date 2016年8月11日 下午12:57:44
*
*/
@PermitAll
public class TokenUtil {
public static String getJWTString(String tel,Date expires,Key key,String commpanyId,Map<String,Object> claims){
if (tel == null) {
throw new NullPointerException("null username is illegal");
}
if (expires == null) {
throw new NullPointerException("null expires is illegal");
}
if (key == null) {
throw new NullPointerException("null key is illegal");
}
SignatureAlgorithm signatureAlgorithm =SignatureAlgorithm.HS256;
String jwtString = Jwts.builder()
.setIssuer("Jersey-Security-Basic")
.setSubject(tel)
.setAudience("user")
.setExpiration(expires)
.setClaims(claims)
.setIssuedAt(new Date())
.setId(commpanyId)
.signWith(signatureAlgorithm,key)
.compact();
return jwtString;
}
public static boolean isValid(String token, Key key) {
try {
Jwts.parser().setSigningKey(key).parseClaimsJws(token.trim());
return true;
} catch (Exception e) {
return false;
}
}
public static String getName(String jwsToken, Key key) {
if (isValid(jwsToken, key)) {
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(key).parseClaimsJws(jwsToken);
String name = String.valueOf(claimsJws.getBody().get("name"));
return name;
}
return null;
}
public static String[] getRoles(String jwsToken, Key key) {
if (isValid(jwsToken, key)) {
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(key).parseClaimsJws(jwsToken);
return claimsJws.getBody().getAudience().split(",");
}
return new String[]{};
}
public static int getVersion(String jwsToken, Key key) {
if (isValid(jwsToken, key)) {
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(key).parseClaimsJws(jwsToken);
return Integer.parseInt(claimsJws.getBody().getId());
}
return -1;
}
/**
* @Title: getCompanyId
* @Description: TODO(获取企业ID)
* @param @param jwsToken
* @param @param key
* @param @return 设定文件
* @return String 返回类型
* @throws
*/
public static String getCompanyId(String jwsToken, Key key) {
if (isValid(jwsToken, key)) {
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(key).parseClaimsJws(jwsToken);
String companyid = String.valueOf(claimsJws.getBody().get("id"));
return companyid;
}
return null;
}
/**
* @Title: setAcccessToken
* @Description: TODO(存放鉴权中心token)
* @param @param accessToken
* @param @param key
* @param @return 设定文件
* @return String 返回类型
* @throws
*/
public static void setAcccessToken(String authToken, Key key,String accessToken) {
if (isValid(authToken, key)) {
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(key).parseClaimsJws(authToken);
claimsJws.getBody().put("acessToken", accessToken);
}
}
/**
* @Title: getCompanyId
* @Description: TODO(获取鉴权中心Token)
* @param @param jwsToken
* @param @param key
* @param @return 设定文件
* @return String 返回类型
* @throws
*/
public static String getAccessToken(String jwsToken, Key key) {
if (isValid(jwsToken, key)) {
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(key).parseClaimsJws(jwsToken);
return claimsJws.getBody().getSubject();
}
return null;
}
}
...全文
相关推荐
基于SpringBoot+Vue的前后端分离仓储管理系统设计与实现
本系统利用现代Web技术,采用前后端分离的架构模式,以SpringBoot为后端开发框架,Vue.js为前端框架,实现了功能完备、界面友好的仓储管理系统。
SpringBoot+Vue树洞论坛:匿名社区前后端分离架构实践
在Web应用开发领域,前后端分离架构已成为主流模式,它通过解耦前端展示与后端逻辑,提升了系统的可维护性和扩展性。其核心原理在于前端负责用户界面渲染与交互,后端专注提供RESTful API数据服务,两者通过HTTP协议进行JSON格式通信。这种架构的技术价值在于支持多端适配、独立部署和团队并行开发。在社区论坛、社交平台等应用场景中,分离架构能更好地支持动态内容加载和个性化交互。本文聚焦于一个独特的“树洞”论坛实现,其中SpringBoot后端与Vue前端的组合,配合UUID匿名身份系统和轻量化数据模型,构建
浅谈django rest jwt vue 跨域问题
在开发Web应用时,特别是在使用现代前后端分离的技术栈,如Django REST、JWT和Vue.js时,跨域问题是一个常见的挑战。这篇文章将探讨如何处理这些框架下的跨域问题。
Django+JWT实现Token认证的实现方法
在现代Web应用中,特别是在前后端分离的架构中,API的安全认证变得至关重要。
基于django+redis开发的前后端分离demo
在本项目中,"基于django+redis开发的前后端分离demo"是一个小规模的示例,旨在帮助初学者理解如何使用Python的Django框架与Redis数据库实现前后端分离的Web应用开发。


Web 开发
