写在前面:近期在做一个前后台分离的项目 Springboot + vue 登录验证的时候选用了shiro 因为是拿来即用的 所以前后端部署完成后出现很多bug... 种种原因 最后选择了友好又亲切的Jwt : )
好了 直接上代码了
wait 粗暴上代码之前让我先小小说下这个小模块是什么原理 (毕竟鱼的记忆力)
以我的理解的来说的话 Jwt相当于一个令牌 一旦用户成功登录 服务器端就会给用户办法一个令牌 用户后续的每一次请求 都会带着这个令牌 服务器将会校验这个令牌 允许该用户访问该令牌允许的路由,服务和资源 这个令牌能够轻松地跨不同域使用
Jwt(JSON Web Tokens)的令牌结构
在紧凑的形式中,Jwt 包含三个由(.)分隔得部分
头
有效载荷
签名
因此,Jwt 通常看起来为 xxxx.yyyy.zzzz
头
通常由两部分组成:令牌的类型,即JWT和正在使用的散列算法,如HMAC SHA256或RSA。
例如:
{
"alg": "HS256",
"typ": "JWT"
}
然后,这个JSON被Base64Url编码,形成JWT的第一部分。
有效载荷
令牌的第二部分是包含声明的有效负载。声明是关于实体(通常是用户)和其他元数据的声明。
例如:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
有效载荷会根据自己业务需求来编写 其中还包括 iss(发行者), exp(到期时间), sub(主题), aud(受众)等
这个JSON被Base64Url编码,形成JWT的第二部分。
请注意,对于已签名的令牌,此信息尽管受到篡改保护,但任何人都可以阅读。除非加密,否则不要将秘密信息放在JWT的有效内容或标题元素中。
要创建签名部分,您必须采用编码标头,编码有效载荷,秘密,标头中指定的算法并签名
例如,如果你想使用HMAC SHA256算法,签名将按照以下方式创建:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
输出是三个由点分隔的Base64-URL字符串,可以在HTML和HTTP环境中轻松传递,而与基于XML的标准(如SAML)相比,它更加紧凑。
Jwt 部分如果想了解的更加详细的话 可以参考:https://ninghao.net/blog/2834
好了 大概就讲这么多 开始上代码了
1、首先 编写一个生成token 的工具类
public class JwtUtil {
final static String base64EncodedSecretKey = "你的私钥";//私钥
final static long TOKEN_EXP = 1000 * 60 * 10;//过期时间,测试使用十分钟
public static String getToken(String userName) {
return Jwts.builder()
.setSubject(userName)
.claim("roles", "user")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + TOKEN_EXP)) /*过期时间*/
.signWith(SignatureAlgorithm.HS256, base64EncodedSecretKey)
.compact();
}
}
2、在用户登录时 校验信息有效后 将token信息返回给客户端
/**
* 用户登录
* @param userPo
* @return
*/
@PostMapping("/login")
public ResponseResult login(@RequestBody UserPo userPo) {
String pwd = SHAUtils.encodeData(userPo.getPassword());
userPo.setPassword(pwd);
userService.findUserVoByUsernameAndPassword(userPo);
String token = JwtUtil.getToken(userPo.getUsername());
return new ResponseResult<>("Bearer:" + token);
}
3、好了 要开始写拦截器了 用户在登录后的每次请求 都会经过这个拦截器 校验token是否有效
import cn.yuchen.fr.utility.JwtInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//添加拦截器
registry.addInterceptor(new JwtInterceptor()).excludePathPatterns("/login","/user/hello");//放掉某些特定不需要校验token的路由
}
}
4、拦截器 JwtInterceptor()
import io.jsonwebtoken.Claims;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class JwtInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer:")) {
throw new UserLoginException("用户未登录");
}
//取得token
String token = authHeader.substring(7);
//验证token
Claims claims = JwtUtil.checkToken(token);
request.setAttribute("username",claims.getSubject());
return true;
}
}
5、 校验 Jwt 的工具类
/**
* 检查token,只要不正确就会抛出异常
**/
public static Claims checkToken(String token) throws ServletException {
try {
final Claims claims = Jwts.parser().setSigningKey(base64EncodedSecretKey).parseClaimsJws(token).getBody();
return claims;
} catch (ExpiredJwtException e1) {
throw new UserLoginInvalidException("登录信息过期,请重新登录");
} catch (Exception e) {
throw new UserLoginException("用户未登录,请重新登录");
}
}
到这里 后台部分就完成了 现在你只要和你前端小哥哥 or 小姐姐 约定好 登陆成功后 每次请求时将token放在requestHeader中就好了
wait wait 跨域 这个忘了写
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class GlobalCorsConfig {
@Bean
public FilterRegistrationBean corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(0);
return bean;
}
}
好了 下班了回家啦 :)
如果有什么问题 欢迎指正
ps:补上SHAUtils
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;public class SHAUtils {
public static String encodeData(String str){
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(str.getBytes());
byte byteData[] = md.digest();//将字节转换为十六进制格式方法一
StringBuffer sb = new StringBuffer();
for(int i = 0; i < byteData.length; i++){
sb.append(Integer.toString((byteData[i] & 0xff) + 0x100, 16).substring(1));
}
System.out.println("Hex format : " + sb.toString());
return sb.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
}