赞
踩
JWT 全称 Json web Token,在此所讲述的是 JWT 用于身份认证,用服务器端生成的JWT去替代原始的Session认证,以提高安全性。
JWT本质是一个Token令牌,是由三部分组成的字符串,分别是头部(header)、载荷(payload)和签名(signature)。头部一般包含该 JWT 的基本信息,例如所使用的加密算法;载荷一般包含所需要传递的信息,如用户名;签名则是通过对头部、载荷和密钥加密生成的,用于验证 JWT 的真实性和完整性(即拿到前端传过来的Token,通过其头部、载荷和密钥去生成一个签名,然后比对是否与传过来的Token签名部分是否一致)。
<!--SpringSecurity-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JWT -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
/**
* jwt加密和解密的工具类
*/
public class JWTUtil {
/**
* 签发JWT;这里创建的jwt
* @param id
* @param subject 可以是JSON数据 尽可能少
* @param ttlMillis
* @return
*/
public static String createJWT(String id, String subject, long ttlMillis) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
SecretKey secretKey = generalKey(); // 通过操作加密生成key
JwtBuilder builder = Jwts.builder()
.setId(id)
.setSubject(subject) // 主题
.setIssuer("xc")// 签发者:小柴
.setIssuedAt(now) // 签发时间
.signWith(signatureAlgorithm, secretKey); // 签名算法以及密匙
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
builder.setExpiration(expDate); // 过期时间
}
return builder.compact();
}
/**
* 生成jwt token
*
* @param username
* @return
*/
public static String createJWT(String username) {
return createJWT(username, username, 60 * 60 * 1000);
}
/**
* 验证JWT
* 根据验证时抛出的超时异常、签名异常、其他异常进行一定的操作
*
* @param jwtStr
* @return
*/
public static CheckResult validateJWT(String jwtStr) {
CheckResult checkResult = new CheckResult();
// 如果jwtStr为空的话,设置errcode为jwt不存在
if(StringUtils.isEmpty(jwtStr)){
checkResult.setSuccess(false);
checkResult.setErrCode(JWTConstant.JWT_ERRCODE_NULL);
}
Claims claims = null;
try {
claims = parseJWT(jwtStr);
checkResult.setSuccess(true);
checkResult.setClaims(claims);
} catch (ExpiredJwtException e) {
checkResult.setErrCode(JWTConstant.JWT_ERRCODE_EXPIRE);
checkResult.setSuccess(false);
} catch (SignatureException e) {
checkResult.setErrCode(JWTConstant.JWT_ERRCODE_FAIL);
checkResult.setSuccess(false);
} catch (Exception e) {
checkResult.setErrCode(JWTConstant.JWT_ERRCODE_FAIL);
checkResult.setSuccess(false);
}
return checkResult;
}
/**
* 生成加密Key
*
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.decode(JWTConstant.JWT_SECRET);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
/**
* 解析JWT字符串
*
* @param jwt
* @return 返回 jwt 解析后的 payload
* @throws Exception
*/
public static Claims parseJWT(String jwt) {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
}
在这个工具类中用到了俩个自定义的类,一个封装是验证 jwt 结果集实体类 CheckResult
,它内部封装了三个属性:errorCode:错误编码,success:验证是否成功,claims:jwt 中包含的一些信息;使用工具类验证JWT时返回该对象,具体代码如下:
@Data
@NoArgsConstructor
/**
* JWT 验证信息
*/
public class CheckResult {
private int errCode;
private boolean success;
private Claims claims;
}
另一个是一个在验证/生成 JWT 时所需用到的常量类 JWTConstant
,如:验证失败所对应异常的编码(自定义的),JWT 秘钥等等。具体代码如下:
public class JWTConstant {
/**
* token
*/
public static final int JWT_ERRCODE_NULL = 4000; //Token不存在
public static final int JWT_ERRCODE_EXPIRE = 4001; //Token过期
public static final int JWT_ERRCODE_FAIL = 4002; //验证不通过
/**
* JWT 秘钥 1
*/
public static final String JWT_SECRET = "bG92ZS14bXE=";
/**
* JWT 秘钥 2
*/
public static final String JWT_SECERT2 = "8677df7fc3a34e26a61c034d5ec8245d"; //密匙
public static final long JWT_TTL = 24*60 * 60 * 1000; //token有效时间
}
// 关闭session
// 关闭原因:
// 1. 前后端进行通信,每个请求都是一个独立的事务,开启session管理可能会使得信息无法共享
// 2. 采用session管理的话,多个用户进行访问服务器端的内存会占用过高,这是因为session的废除机制是超时机制
// 3. 采用session管理功能,这也是一个安全漏洞
// 这里使用jwt(Java web token)令牌的方式进行认证,不需要session了
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
AuthenticationSuccessHandler
中的 onAuthenticationSuccess
方法对登录成功的一些操作(即登录成功后需要返回给前端的数据就可以在这个方法中进行实现),那有了JWT工具类,这方法就简单实现了,下面是实现的具体代码(当然如何配置这个handler这里就不说了,在专栏里有专门的博客解释了):@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
@Resource
private ObjectMapper objectMapper;
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
// 设置响应编码格式
response.setContentType("json/application;charset=utf-8");
// 获取用户名
String username = authentication.getName();
// 生成 jwt
String jwt = JWTUtil.createJWT(username);
ServletOutputStream out = response.getOutputStream();
// 将 jwt 返回给前端
out.write(objectMapper.writeValueAsString(BackResult.success(jwt)).getBytes());
out.close();
}
}
SecurityContextgHolder
中的,内部默认使用的是 ThreadLocal 去存放认证信息(内部用了策略模式,默认采用的策略是用ThreadLocal),当一个请求结束后这个Authentication会移除,原本移除会放在Session里一同返回给前端,但咱现在把Session管理给静止了(这在【深入浅出 Spring Security(四)】登录用户数据的获取,超详细的源码分析 中详细说明了)。咱现在用的是JWT认证方式了,前端拿到这个token后,放在请求头中向后端发送请求时,后端得对这个token进行验证,如果验证成功了咱得从这个token中提取一些数据封装成Authentication放入 SecurityContextHolder 中,将 SecurityContextHolder 中的对应 Authentication 中的 authenticated
属性设置为 true,以表示认证成功,即这个请求认证成功了(但不代表授权成功哈,提一嘴赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。