为什么使用JWT?
随着技术的发展,分布式web应用的普及,通过session管理用户登录状态成本越来越高,因此慢慢发展成为token的方式做登录身份校验,然后通过token去取redis中的缓存的用户信息,随着之后jwt的出现,校验方式更加简单便捷化,无需通过redis缓存,而是直接根据token取出保存的用户信息,以及对token可用性校验,单点登录更为简单。项目为了实现前后端分离。开始采用jwt技术,同时这样也可以减轻服务端压力,服务器无需存储存储。
了解一下JWT的组成
一个jwt实际上就是一个字符串,它由三部分组成,头部、载荷与签名,这三个部分都是json格式。
3部分之间用“.”号做分隔。例如eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
-
JWT头
JWT头
JWT头部分是一个描述JWT元数据的JSON对象,通常如下所示。
{
“alg”: “HS256”,
“typ”: “JWT”
}
在上面的代码中,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。
最后,使用Base64 URL算法将上述JSON对象转换为字符串保存。 -
有效载荷
有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择。
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT
除以上默认字段外,我们还可以自定义私有字段,如下例:
{
“sub”: “1234567890”,
“name”: “chongchong”,
“admin”: true
}
请注意,默认情况下JWT是未加密的,任何人都可以解读其内容,因此不要构建隐私信息字段,存放保密信息,以防止信息泄露。
JSON对象也使用Base64 URL算法转换为字符串保存。 -
签名哈希
签名哈希部分是对上面两部分数据签名,通过指定的算法生成哈希,以确保数据不会被篡改。
首先,需要指定一个密码(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。然后,使用标头中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名。
HMACSHA256(base64UrlEncode(header) + “.” + base64UrlEncode(payload),secret)
在计算出签名哈希后,JWT头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用"."分隔,就构成整个JWT对象。
代码实现
-
jwt maven依赖
<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.4.0</version> </dependency>
-
JWTUtil 用于生成token,解析token
public class JWTUtil {
private static SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS512;
private static Key getSecretKey(String key) {
return new SecretKeySpec(key.getBytes(), signatureAlgorithm.getJcaName());
}
//生成token
public static String createJsonWebToken(UserToken userToken, String key) {
String token = Jwts.builder()
.setSubject(userToken.getId().toString())
.claim("name", userToken.getName())
.claim("role", userToken.getRole().toString())
.claim("orgId", userToken.getOrgId().toString())
.claim("userName", userToken.getUserName())
.signWith(signatureAlgorithm, getSecretKey(key)).compact();
return token;
}
//解析token
public static UserToken parseAndValidate(String token, String key) {
UserToken authTokenDetails = null;
try {
Claims claims = Jwts.parser().setSigningKey(getSecretKey(key)).parseClaimsJws(token).getBody();
String userId = claims.getSubject();
String name = (String) claims.get("name");
String roleNames = (String) claims.get("role");
String orgId = (String) claims.get("orgId");
authTokenDetails = new UserToken();
authTokenDetails.setId(Long.valueOf(userId));
authTokenDetails.setName(name);
authTokenDetails.setRole(Role.valueOf(roleNames));
authTokenDetails.setOrgId(Long.valueOf(orgId));
} catch (JwtException ex) {
log.error("TOKEN解析异常###key->{}, token->{}", key, token);
log.error(ex.getMessage(), ex);
}
return authTokenDetails;
}
}
- 添加使用jwt认证的filter
@AllArgsConstructor
public class JWTAuthenticationFilter extends GenericFilterBean {
private static final String HEADER_STRING = "Authorization";// 存放Token的Header Key
private String jwtKey;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (!((HttpServletRequest) request).getRequestURI().startsWith("/manage/")) {
String token = ((HttpServletRequest) request).getHeader(HEADER_STRING);
if (StringUtils.isBlank(token)) {
token = request.getParameter("token");
}
if (StringUtils.isNotEmpty(token)) {
UserToken userToken = JWTUtil.parseAndValidate(token, jwtKey);
if (userToken != null) {
// 得到 权限(角色)
List<GrantedAuthority> authorities = AuthorityUtils
.commaSeparatedStringToAuthorityList("ROLE_" + userToken.getRole().toString());
Authentication authentication = new UsernamePasswordAuthenticationToken(userToken.getId(), null,
authorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
UserContext.setUser(userToken);
}
}
}
chain.doFilter(request, response);
}
}
- 补充代码
Role:
@AllArgsConstructor
@Getter
public enum Role {
ADMIN("管理员",""),
private String name;
private String appname;
private static Map<String, String> RoleMap = new HashMap<>();
static {
for (Role role : values()) {
RoleMap.put(role.toString(), role.name);
}
}
public static Map<String, String> getMap() {
return RoleMap;
}
}
UserToken:
@Getter
@Setter
public class UserToken {
private Long id;
private String name;
private Role role;
private LocalDate expireDate;
private Long orgId;
private String userName;
private Long clazzId;
}