目录
今天与大家分享jwt令牌控制用户登陆 浅浅的分享一波 后台与前台分离部分实例
与小编对jwt的理解 和基本使用jwt入门
如果内容 有问题 欢迎评价 最后 如果可以 谢谢大哥们点点浏览 觉得小编写的不错的 欢迎关注小编 这也才小编写的动力
1. jwt令牌的使用
1.1 什么是JWT?
JSON Web Token(JWT)是目前最流行的跨域身份验证解决方案
1.2 什么是jwt令牌?
简单来说,jwt令牌就是跨域身份验证完美解决方案,控制用户登陆的大门,判断用户是否合法。合法用户 则进行登陆; 不合法 不让用户登陆。
传统的intenet服务的认证是通过session进行的,当用户通过了安全认证后,则在服务端的session对象中保存该用户的认证信息,这样该用户对服务的访问被认为是安全的。
如果需要进行服务集群则需要处理好共享session的问题。 如果一个庞大的系统需要按服务分解为多个独立的服务,使用分布式架构,则这种方式更难处理。使用jwt可以方便的处理上面提到的问题。
1.3 了解jwt的结构
- JWT头:JWT头部分是一个描述JWT元数据的JSON对象,通常如下所示。{"alg": "HS256","typ": "JWT"}。
- 有效载荷:有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。这些声明被JWT标准称为claims。
例如:
{"sub":"123","name":"Tom","admin":true},sub代表这个token的所有人,存储的是所有人的ID;name表示这个所有人的名字;admin表示所有人是否管理员的角色。当后面对JWT进行验证的时候,这些claim都能发挥特定的作用。
1.4 JWT实际结构示例
- 1、标头(Header)
标头通常由两部分组成︰令牌的类型(即 JWT )和所使用的签名算法,例如 HNAC SHA256 或 RSA 。它会使用 Base64 编码组成 JWT 结构的第一部分。
注意: Base64 是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。
{ "alg" : "HS256", "typ" : "JWT" }
alg:加密算法 HS256:这是 JWT 推荐的也是默认的加密算法(签名算法)
- 2、有效载荷(Payload)
令牌的第二部分是有效负载,其中包含声明。声明是有关实体(通常是用户信息)和其他数据的声明。同样的,它会使用 Base64 编码组成 JWT 结构的第二部分
{ "sub" : "1234567890", "name" : "John Doe", "admin" : true }
注意:不要在 Payload 中存放用户的敏感信息(比如:密码);因为令牌的第一部分和第二部分都是用 Base64 编码的
- 3、签名(Signature)
前面两部分都是使用 Base64 进行编码的,即前端可以解开知道里面的信息。Signature 需要使用编码后的 header 和 payload 以及我们提供的一个密钥,然后使用 header 中指定的签名算法(HS256)进行签名。签名的作用是保证 JWT 没有被篡改过
如:
HIMACSHA256(base64UrlEncode(header) + “.” + base64Ur1Encode(payload) ,secret);
结构示图
签名目的
最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被窜改。如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的JWT的话,那么服务器端会判断出新的头部和负载形成的签名和JWT附带上的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。
信息安全问题
在这里大家一定会问一个问题: Base64是一种编码,是可逆的,那么我的信息不就被暴露了吗?
是的。所以,在JWT中,不应该在负载里面加入任何敏感的数据在上面的例子中,我们传输的是用户的User ID。这个值实际上不是什么敏﹐感内容,一般情况下被知道也是安全的。但是像密码这样的内容就不能被放在JWT中了。如果将用户的密码放在了JWT中,那么怀有恶意的第﹐三方通过Base64解码就能很快地知道你的密码了。因此JWT适合用于向Web应用传递一些非敏感信息。JWT还经常用于设计用户认证和授权系统,甚至实现Web应用的单点登录。
2. jwt后台示例
后台流程:
1)将资料中src目录下的代码(CorsFilter.java,JwtFilter.java,JwtUtils.java)拷入项目的util目录下(可以根据项目设置的目录结构调整)。
2)程序的解释详见代码中的注释
3)在web.xml中配置JwtFilter过滤器
4)在用户登录验证成功后,需要为该用户生成jwt令牌,具体实现可参考UserAction.java
5)测试后台的验证服务,确定在验证通过的前提下可以正确的生成jwt令牌。
需要导入架包 使用maven 导入
<!-- jwt 令牌需要架包 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.3</version>
</dependency>
- 1)(CorsFilter.java,JwtFilter.java,JwtUtils.java)拷入项目的util目录下(可以根据项目设置的目录结构调整)。
CorsFilter 作用域过滤器
package com.zking.vueserver.utils;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 配置tomcat允许跨域访问
*
* @author Administrator
*
*/
public class CorsFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
HttpServletRequest req = (HttpServletRequest) servletRequest;
// Access-Control-Allow-Origin就是我们需要设置的域名
// Access-Control-Allow-Headers跨域允许包含的头。
// Access-Control-Allow-Methods是允许的请求方式
httpResponse.setHeader("Access-Control-Allow-Origin", "*");// *,任何域名
httpResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE");
httpResponse.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept,jwt");
//允许客户端,处理一个新的响应头jwt
httpResponse.setHeader("Access-Control-Expose-Headers", "jwt");
// axios的ajax会发两次请求,第一次提交方式为:option,直接返回即可
if ("OPTIONS".equals(req.getMethod())) {
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
- JwtFilter 过滤器 发现没有jwt命令直接请求失效
package com.zking.vueserver.utils;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import io.jsonwebtoken.Claims;
/**
* JWT验证过滤器,配置顺序 :CorsFilte-->JwtFilter-->struts2中央控制器
*
* @author Administrator
*
*/
public class JwtFilter implements Filter {
// 排除的URL,一般为登陆的URL(请改成自己登陆的URL)
private static String EXCLUDE = "^/login";
private static Pattern PATTERN = Pattern.compile(EXCLUDE);
private boolean OFF = false;// true关闭jwt令牌验证功能
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
String path = req.getServletPath();
if (OFF || isExcludeUrl(path)) {// 登陆直接放行
chain.doFilter(request, response);
return;
}
// 从客户端请求头中获得令牌并验证
String jwt = req.getHeader(JwtUtils.JWT_HEADER_KEY);
Claims claims = this.validateJwtToken(jwt);
if (null == claims) {
// resp.setCharacterEncoding("UTF-8");
resp.sendError(403, "JWT令牌已过期或已失效");
return;
} else {
String newJwt = JwtUtils.copyJwt(jwt, JwtUtils.JWT_WEB_TTL);
resp.setHeader(JwtUtils.JWT_HEADER_KEY, newJwt);
chain.doFilter(request, response);
}
}
/**
* 验证jwt令牌,验证通过返回声明(包括公有和私有),返回null则表示验证失败
*/
private Claims validateJwtToken(String jwt) {
Claims claims = null;
try {
if (null != jwt) {
claims = JwtUtils.parseJwt(jwt);
}
} catch (Exception e) {
e.printStackTrace();
}
return claims;
}
/**
* 是否为排除的URL
*
* @param path
* @return
*/
private boolean isExcludeUrl(String path) {
Matcher matcher = PATTERN.matcher(path);
return matcher.matches();
}
}
JwtUtils 工具类 程序的解释详见代码中的注释
package com.zking.vueserver.utils;
import java.util.Base64;
import java.util.Date;
import java.util.Map;
import java.util.UUID;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
/**
* JWT验证过滤器:配置顺序 CorsFilte->JwtUtilsr-->StrutsPrepareAndExecuteFilter
*
*/
public class JwtUtils {
/**
* JWT_WEB_TTL:WEBAPP应用中token的有效时间,默认30分钟
*/
public static final long JWT_WEB_TTL = 30 * 60 * 1000;
/**
* 将jwt令牌保存到header中的key
*/
public static final String JWT_HEADER_KEY = "jwt";
// 指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。
private static final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;
private static final String JWT_SECRET = "f356cdce935c42328ad2001d7e9552a3";// JWT密匙
private static final SecretKey JWT_KEY;// 使用JWT密匙生成的加密key
static {
byte[] encodedKey = Base64.getDecoder().decode(JWT_SECRET);
JWT_KEY = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
}
private JwtUtils() {
}
/**
* 解密jwt,获得所有声明(包括标准和私有声明)
*
* @param jwt
* @return
* @throws Exception
*/
public static Claims parseJwt(String jwt) {
Claims claims = Jwts.parser().setSigningKey(JWT_KEY).parseClaimsJws(jwt).getBody();
return claims;
}
/**
* 创建JWT令牌,签发时间为当前时间
*
* @param claims
* 创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的)
* @param ttlMillis
* JWT的有效时间(单位毫秒),当前时间+有效时间=过期时间
* @return jwt令牌
*/
public static String createJwt(Map<String, Object> claims, long ttlMillis) {
// 生成JWT的时间,即签发时间
long nowMillis = System.currentTimeMillis();
// 下面就是在为payload添加各种标准声明和私有声明了
// 这里其实就是new一个JwtBuilder,设置jwt的body
JwtBuilder builder = Jwts.builder()
// 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
.setClaims(claims)
// 设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
// 可以在未登陆前作为身份标识使用
.setId(UUID.randomUUID().toString().replace("-", ""))
// iss(Issuser)签发者,写死
// .setIssuer("zking")
// iat: jwt的签发时间
.setIssuedAt(new Date(nowMillis))
// 代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可放数据{"uid":"zs"}。此处没放
// .setSubject("{}")
// 设置签名使用的签名算法和签名使用的秘钥
.signWith(SIGNATURE_ALGORITHM, JWT_KEY)
// 设置JWT的过期时间
.setExpiration(new Date(nowMillis + ttlMillis));
return builder.compact();
}
/**
* 复制jwt,并重新设置签发时间(为当前时间)和失效时间
*
* @param jwt
* 被复制的jwt令牌
* @param ttlMillis
* jwt的有效时间(单位毫秒),当前时间+有效时间=过期时间
* @return
*/
public static String copyJwt(String jwt, Long ttlMillis) {
Claims claims = parseJwt(jwt);
// 生成JWT的时间,即签发时间
long nowMillis = System.currentTimeMillis();
// 下面就是在为payload添加各种标准声明和私有声明了
// 这里其实就是new一个JwtBuilder,设置jwt的body
JwtBuilder builder = Jwts.builder()
// 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
.setClaims(claims)
// 设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
// 可以在未登陆前作为身份标识使用
//.setId(UUID.randomUUID().toString().replace("-", ""))
// iss(Issuser)签发者,写死
// .setIssuer("zking")
// iat: jwt的签发时间
.setIssuedAt(new Date(nowMillis))
// 代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可放数据{"uid":"zs"}。此处没放
// .setSubject("{}")
// 设置签名使用的签名算法和签名使用的秘钥
.signWith(SIGNATURE_ALGORITHM, JWT_KEY)
// 设置JWT的过期时间
.setExpiration(new Date(nowMillis + ttlMillis));
return builder.compact();
}
}
- 3)在web.xml中配置JwtFilter过滤器,但是配置跨域请求必须配置在jwt过滤器的前面
<!-- 处理跨域请求 通过 -->
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>com.zking.vueserver.utils.CorsFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- jwt -->
<filter>
<filter-name>JwtFilter</filter-name>
<filter-class>com.zking.vueserver.utils.JwtFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>JwtFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
- 4)在用户登录验证成功后,需要为该用户生成jwt令牌,具体实现可参考UserAction.java
@RequestMapping("/login")
@ResponseBody
//接受json post请求 需要注解RequestBody
public Object login( User us, HttpServletResponse response) {
RetrunData re = null;
if ("admin".equals(us.getUserName()) && "123".equals(us.getPassWord())) {
//认证成功,则发放令牌
Map<String,Object> param = new HashMap<>();
param.put("user", us);
String jwt = JwtUtils.createJwt(param, JwtUtils.JWT_WEB_TTL);
response.addHeader("jwt", jwt);
re = new RetrunData(1, "登录成功", us);
} else {
re = new RetrunData(-1, "登录失败", "ns");
}
return re;
}
在用户登陆控制器进行认证,成功,则发放令牌
Map<String,Object> param = new HashMap<>();
param.put("user", us);//保存用户信息
//获取jwt头 如果认证成功 则发放令牌 并刷新令牌 保存到用户信息中 下次登陆就跨域获取用户信息中刷新后的令牌
String jwt = JwtUtils.createJwt(param, JwtUtils.JWT_WEB_TTL);
response.addHeader("jwt", jwt);
- 5)测试后台的验证服务,确定在验证通过的前提下可以正确的生成jwt令牌。
3. 前台加入jwt功能
1) 在store/state.js中加入jwt变量
//存放全局参数的容器,组件可以通过state.js获取全局参数
const state = {
LeftAsideState: 'open',
PersonName:'张飞',
//存放jwt令牌
jwt:null
}
export default state
2)在store/mutations.js中加入设置jwt方法
//设置JWT令牌
setJwtToken: (state, payload) => {
state.jwt = payload.jwt;
}
3)在store/getters.js中加入获取jwt的方法
//获取jwt令牌
getJwtToken: function(state) {
return state.jwt;
}
4)修改main.js应用入口
//将vue实例赋给window.vm属性,以便于在http.js的响应拦截器和请求拦截器中获取vue实例
window.vm = new Vue({
el: '#app',
router,
store,
components: {App},
template: '<App/>'
})
5)完善api/http.js中的请求拦截器和响应拦截器
// 请求拦截器
axios.interceptors.request.use(function(config) {
//获取store中的jwt令牌,如果正常获取,将其放入请求头,
//在调用后台服务时,服务会通过过滤器验证jwt令牌的有效性
//如果没有令牌或令牌无效则拒绝服务。
let jwt = window.vm.$store.getters.getJwtToken;
if(jwt) {
config.headers['jwt'] = jwt;
}
return config;
}, function(error) {
return Promise.reject(error);
});
// 响应拦截器
axios.interceptors.response.use(function(response) {
//获取响应头中的jwt令牌数据(认证服务将生成的令牌放入header的jwt属性中)
//如果获取到jwt令牌,在保存到store中,以后发送请求时需要将令牌放入请求头
let jwt = response.headers['jwt'];
if(jwt) {
window.vm.$store.commit('setJwtToken',{jwt:jwt});
}
return response;
}, function(error) {
return Promise.reject(error);
});
配置完就可以测试了,最后结果: