因为,HTTP是无状态的,HTTP访问了服务器,服务器是没有办法知道又是你这个HTTP发送的。所以不能通过HTTP来进行登录后的身份认证的效果。
这个时候就需要Cookie,Session,Token来进行身份认证。
其实,无论Cookie,Session,Token实现身份认真的本质就是存储。
Cookies的流程:
1.用户发送HTTP请求进行登录。
2.用户登录成功,服务器就会创建一个Cookie,将该Cookie添加到Set-Cookie的一个响应头,并且携带用户名和密码(一般是加密的,也可能不会携带用户名密码)。
4.服务器进行验证。
因为,Cookie是不安全的,因为我们的密码用户都是存在Cookie中的,就算进行了加密,某种程度上也是暴露的。如下:
因此,就有了Session。
总结:
Session会话机制是基于Cookie的。
但是,随着集群的架构出现,多台服务器之间必须要共享Session会话才行!
因此,就有了将Session会话信息存储到数据库中,一般数据库选择redis非关系型。
总结:
cookie和session的缺点:
而JWT,token的优点就是弥补了上面的缺点。因为,token主要来解决前后端分离的场景。
JWT任何语言都可以使用!因为JWT是一种思想。具体的语言实现操作才是JWT实现。
token的英文直译是令牌的意思。
JWT全称叫做JSON Web Token,一般我们叫做token实现登录验证。
JWT需要的salt盐作用就是加强加密的复杂度,这么破解起来就更加麻烦,当然这个“盐”越长越复杂,加密后破解起来就越麻烦。
总结:
JWT官方网站
我们去Libraries中,找到Java相关的JWT。
这里我们就是使用两种官方的JWT实现:
第一步:导包,导依赖。
<dependency>
<groupId>com.auth0groupId>
<artifactId>java-jwtartifactId>
<version>3.18.3version>
dependency>
第二步:调用JWT。
import java.util.Calendar;
import org.junit.Test;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator.Builder;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.InvalidClaimException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.sun.org.apache.bcel.internal.generic.ALOAD;
public class JavaJwtTest {
//hmac256摘要算法,跟md5类似,只是这个hmac256需要指定key,通常这个key叫做salt盐。
String key = "123456abc";
/*
* 测试java-jwt生成token字符串
* jwt三部分组成:
* header.payload.signature
*/
@Test
public void testGenerateToken() {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND, 60*5);
//claim英文直译:声明意思
Builder builder = JWT.create()
//withClaim中设定的就是payload的内容。
.withClaim("userid", 123)
.withClaim("userName", "itholmes")
.withClaim("url", "https//www.itholmes.top")
//设置过期时间
.withExpiresAt(calendar.getTime());
String token = builder.sign(Algorithm.HMAC256(key));
System.out.println(token);
}
//校验
@Test
public void testVerify() {
String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyTmFtZSI6Iml0aG9sbWVzIiwiZXhwIjoxNjQ2NzUxMTk3LCJ1c2VyaWQiOjEyMywidXJsIjoiaHR0cHMvL3d3dy5pdGhvbG1lcy50b3AifQ.e7y_9iO_jtVXo90qaq5H0aiUmg29I9XeJqAgUCDb1zo";
JWTVerifier build = JWT.require(Algorithm.HMAC256(key)).build();
DecodedJWT verify = null;
try{
verify = build.verify(token);
System.out.println(verify);
}catch(TokenExpiredException e) {
System.out.println("该token已经过期。");
}catch (SignatureVerificationException e) {
System.out.println("签名不一致");
}catch (AlgorithmMismatchException e) {
System.out.println("签名算法不匹配");
}catch (InvalidClaimException e) {
System.out.println("payload不可用");
}catch (Exception e) {
System.out.println("其他错误校验失败");
}
System.out.println(verify);
if(verify!=null) {
//asInt()方法转换类型。
Integer userid = verify.getClaim("userid").asInt();
String userName = verify.getClaim("userName").asString();
String url = verify.getClaim("url").asString();
System.out.println();
}
}
}
第一步:同样导包导依赖。
<dependency>
<groupId>io.jsonwebtokengroupId>
<artifactId>jjwtartifactId>
<version>0.9.1version>
dependency>
第二步:Jwts调用。
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
public class JJwtTest {
String key="123435abc";
//通过jjwt生成token字符串
@Test
public void testGenerateToken() {
//过期时间
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND, 60*5);
//创建payLoad的私有声明(根据特定的业务需要添加)
Map<String, Object> claims = new HashMap<String, Object>();
claims.put("userid",123);
claims.put("userName","itholmes");
claims.put("url","https//www.itholmes.top");
JwtBuilder builder = Jwts.builder()
//设置payLoad,传入的是map类型
.setClaims(claims)
//设置过期时间
.setExpiration(calendar.getTime())
//设置签名使用的签名算法和签名使用的秘钥
.signWith(SignatureAlgorithm.HS256, key);
String compact = builder.compact();
System.out.println(compact);
}
//校验token
@Test
public void testVerify() {
String token="eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyTmFtZSI6Iml0aG9sbWVzIiwiZXhwIjoxNjQ2NzUyNjcxLCJ1c2VyaWQiOjEyMywidXJsIjoiaHR0cHMvL3d3dy5pdGhvbG1lcy50b3AifQ.6M9yg8iGutEgoYNv97n3GV5_o29iL2YXbrdpME_xtXc";
Claims claims = Jwts.parser()
.setSigningKey(key)//设置签名秘钥
.parseClaimsJws(token).getBody();//设置需要解析的jwt
Integer userId = claims.get("userid",Integer.class);
String userName = claims.get("userName",String.class);
String url = claims.get("url",String.class);
System.out.println();
}
}
package com.itholmes.utils;
import java.util.Calendar;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator.Builder;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.InvalidClaimException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.itholmes.bean.User;
public class JwtUtil {
//jwt的key也就是salt盐
private static final String key = "www.itholmes.top";
//生成token
public static String generateToken(User user) throws JsonProcessingException {
//使用jackson来生成json字符串方便存取
ObjectMapper jackson = new ObjectMapper();
String user_string = jackson.writeValueAsString(user);
//通过calendar设置过期时间
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_YEAR, 7);
//创建builder对象
Builder builder = JWT.create()
//设置payload内容
.withClaim("userInfo", user_string)
.withExpiresAt(calendar.getTime());
//生成token
String token = builder.sign(Algorithm.HMAC256(key));
return token;
}
//校验token
public static DecodedJWT verify(String tokenToVerity) {
DecodedJWT verify = null;
try{
verify = JWT
.require(Algorithm.HMAC256(key))
.build()
.verify(tokenToVerity);
}catch(TokenExpiredException e) {
e.printStackTrace();
System.out.println("该token已经过期。");
}catch (SignatureVerificationException e) {
e.printStackTrace();
System.out.println("签名不一致");
}catch (AlgorithmMismatchException e) {
e.printStackTrace();
System.out.println("签名算法不匹配");
}catch (InvalidClaimException e) {
e.printStackTrace();
System.out.println("payload不可用");
}catch (Exception e) {
e.printStackTrace();
System.out.println("其他错误校验失败");
}
return verify;
}
//将DecodedJWT中的信息解析出来进行比对
public static User parse(DecodedJWT decodeJWT) throws JsonMappingException, JsonProcessingException {
Claim claim = decodeJWT.getClaim("userInfo");
if(claim!=null) {
String userString = claim.asString();
ObjectMapper jackson = new ObjectMapper();
User user = jackson.readValue(userString, User.class);
return user;
}
return null;
}
}
前端存储有两种方式:
前端存储token后,之后每次请求都携带这个token,用于让服务端校验。
如何携带token?
一般我们通过请求头来操作。例如:设置一个Authorization请求头获取本地存储的token,放到里面;传到服务器进行验证。
token的使用一般我们都是进行前后端分离的情况下使用。
前端发送token的几种常用的方式:
对于ajax我们可以通过beforeSend参数的xhr对象来设定请求头。
package com.itholmes.filter;
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.annotation.WebFilter;
import javax.servlet.http.HttpServletResponse;
@WebFilter("/*")
public class CorsFilter implements Filter{
public void init(FilterConfig filterConfig) throws ServletException {
// TODO Auto-generated method stub
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
//设置跨域问题。
HttpServletResponse resp = (HttpServletResponse)response;
resp.addHeader("Access-Control-Allow-Origin", "*");
resp.addHeader("Access-Control-Allow-Headers", "*");
resp.addHeader("Access-Control-Allow-Methods", "*");
resp.addHeader("Access-Control-Max-Age", "3600");
resp.addHeader("Access-Control-Allow-Credentials", "false");
chain.doFilter(request, resp);
}
public void destroy() {
// TODO Auto-generated method stub
}
}
(这里的secret就是盐,这个盐我们可以理解为将这些东西混杂在了一起。)
其实JWT实现过程很简单,也可以自己手写一个JWT来进行操作。