1 什么是JWT
SON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且独立的方式,可以在各方之间作为JSON对象安全地传输信息。此信息可以通过数字签名进行验证和信任。JWT可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。
虽然JWT可以加密以在各方之间提供保密,但只将专注于签名令牌。签名令牌可以验证其中包含的声明的完整性,而加密令牌则隐藏其他方的声明。当使用公钥/私钥对签署令牌时,签名还证明只有持有私钥的一方是签署私钥的一方。
通俗来讲,JWT是一个含签名并携带用户相关信息的加密串,页面请求校验登录接口时,请求头中携带JWT串到后端服务,后端通过签名加密串匹配校验,保证信息未被篡改。校验通过则认为是可靠的请求,将正常返回数据。
2 JWT的使用场景
2.1 授权:这是最常见的使用场景,解决单点登录问题。因为JWT使用起来轻便,开销小,服务端不用记录用户状态信息(无状态),所以使用比较广泛;
2.2 息交换:JWT是在各个服务之间安全传输信息的好方法。因为JWT可以签名,例如,使用公钥/私钥对儿 - 可以确定请求方是合法的。此外,由于使用标头和有效负载计算签名,还可以验证内容是否未被篡改。
JWT的结构
header(头信息)
由两部分组成,令牌类型(即:JWT)、散列算法(HMAC、RSASSA、RSASSA-PSS等)
Payload(有效载荷)
JWT的第二部分是payload,其中包含claims。claims是关于实体(常用的是用户信息)和其他数据的声明,claims有三种类型: registered, public, and private claims。
Registered claims: 这些是一组预定义的claims,非强制性的,但是推荐使用, iss(发行人), exp(到期时间), sub(主题), aud(观众)等;
Public claims: 自定义claims,注意不要和JWT注册表中属性冲突,这里可以查看JWT注册表
Private claims: 这些是自定义的claims,用于在同意使用这些claims的各方之间共享信息,它们既不是Registered claims,也不是Public claims。
Signature
要创建签名部分,必须采用编码的Header,编码的Payload,秘钥,Header中指定的算法,并对其进行签名。
例如,如果要使用HMAC SHA256算法
3 JWT的工作原理
在身份验证中,当用户使用其凭据成功登录时,将返回JSON Web Token(即:JWT)。由于令牌是凭证,因此必须非常小心以防止出现安全问题。一般情况下,不应将令牌保留的时间超过要求。理论上超时时间越短越好。
在某些情况下,这可以作为无状态授权机制。服务器的受保护路由将检查Authorization
header中的有效JWT ,如果有效,则允许用户访问受保护资源。如果JWT包含必要的数据,则可以减少查询数据库或缓存信息。
如果在Authorization header中发送令牌,则跨域资源共享(CORS)将不会成为问题,因为它不使用cookie。
4 使用步骤
1 创建JWT工具类,用于创建tokne和解析token
@ConfigurationProperties( prefix = "jwt.config")
public class JwtUtil {
private String key ;
private long ttl ;//一个小时
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public long getTtl() {
return ttl;
}
public void setTtl(long ttl) {
this.ttl = ttl;
}
/**
* 生成JWT
*
* @param id
* @param subject
* @return
*/
public String createJWT(String id, String subject, String roles) {
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
JwtBuilder builder = Jwts.builder().setId(id)
.setSubject(subject)
.setIssuedAt(now)
.signWith(SignatureAlgorithm.HS256, key).claim("roles", roles);
if (ttl > 0) {
builder.setExpiration( new Date( nowMillis + ttl));
}
return builder.compact();
}
/**
* 解析JWT
* @param jwtStr
* @return
*/
public Claims parseJWT(String jwtStr){
return Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(jwtStr)
.getBody();
}
}
在配置文件中添加相关配置
jwt:
config:
key: XXXX
ttl: 3600000
2 配置Spring Security
@Configuration
public class CommonSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/**").permitAll() //设置请求所有路径都不需要权限
.anyRequest().authenticated() //表示所有请求都允许通过
.and().csrf().disable();
}
@Bean
public BCryptPasswordEncoder bCrypt() {
return new BCryptPasswordEncoder();
}
}
3 自定义一个Exception并创建对应的controller来处理该异常
public class AuthorizationException extends RuntimeException {
public AuthorizationException(){
super("没有权限");
}
}
@ControllerAdvice
public class BaseAuthorizationExceptionHandler {
@ResponseBody
@ExceptionHandler(value = AuthenticationException.class)
public Result error(AuthenticationException e){
return new Result(false, StatusCode.ACCESSERROR, e.getMessage());
}
}
4 在用户成功登录时返回token
map.put("token", jwtUtil.createJWT(admin.getId(), admin.getLoginname(), role));
map.put("role", role);
5 用户再发送请求时,将token放在header中
6 在服务端使用拦截器获取header中的token并解析
如果解析成功则执行响应的controller,否则抛出异常
public class RoleUtil {
//get roles from token
public Object getRoleFromToken(JwtUtil jwtUtil, HttpServletRequest request) {
String token = request.getHeader("Authorization");
System.out.printf("jwtstr is " + token);
//先验证token是否存在以及格式是否正确
if (token == null || "".equals(token) || !token.startsWith("Bearer "))
throw new AuthorizationException();
//解析token并提取用户角色信息
try {
String roles = jwtUtil.parseJWT(token.substring(7)).get("roles").toString();
return roles;
} catch (Exception ex) {
throw new AuthorizationException();
}
}
}
@Component
public class AdminJwtInterceptor implements HandlerInterceptor {
@Autowired
HttpServletRequest request;
@Autowired
JwtUtil jwtUtil;
@Autowired
RoleUtil roleUtil;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object roles = roleUtil.getRoleFromToken(jwtUtil, request);
if(roles.toString().contains("admin") )
return true;
else
throw new AuthorizationException();
}
}
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
@Autowired
AdminJwtInterceptor jwtInterceptor;
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor)
.addPathPatterns("/admin/**")
.excludePathPatterns("/**/login");
}
}