1. 整合环境
- 添加pom依赖
<!--jwt依赖-->
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.3</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.9.7</version>
</dependency>
- 添加工具类 (文档最后)
2. 整合流程示意图
3.实现
- 步骤一: 登录时生成token, 并返回
- 步骤二: 前端登录成功后, 将token保存到sessionStorage
- 步骤三: 每次请求追加请求头
- 步骤四: 在网关中, 编写网关过滤器, 对请求进行校验
- 步骤五: token获取失败, 在api.js中,跳转登录页面登录 (可选)
具体实现
- 步骤一: 登录时生成token, 并返回
private static final String priKeyPath = "D:\\ras\\ras.pri";
@PostMapping("/login")
public BaseResult login(@RequestBody Customer customer){
System.out.println(customer);
Customer loginCustomer = customerServiceImpl.login(customer);
if(loginCustomer == null){
return BaseResult.error("用户名和密码错误");
}
//登录成功,生成token
String token=null;
try {
token = JwtUtils.generateToken(loginCustomer, 30, RasUtils.getPrivateKey(priKeyPath));
} catch (Exception e) {
e.printStackTrace();
}
return BaseResult.ok("登录成功",loginCustomer).append("token",token);
}
- 步骤二: 前端登录成功后, 将token保存到sessionStorage(login.vue)
async loginCu(){
let {data} = await login(this.custom);
if(data.code == 1){
this.$message.success(data.message);
// 保存token到浏览器
sessionStorage.setItem('token',data.other.token);
this.$router.push("/home");
}else{
this.$message.error(data.message);
}
}
- 步骤三: 每次请求追加请求头(api.js)
axios.interceptors.request.use(request => {
// 给每个请求都添加token
let token = sessionStorage.getItem('token');
if(token){
request.headers.authorization = token;
}
// 放行
return request;
},error => {});
- 步骤四: 在网关中, 编写网关过滤器, 对请求进行校验
package com.czxy.filter;
import com.czxy.domain.Customer;
import com.czxy.utils.JwtUtils;
import com.czxy.utils.RasUtils;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
/**
* @author zhaorenhui@itcast.cn
* @version 1.0
* @date 2019/12/26
*/
@Component
public class LoginFilter extends ZuulFilter {
private static final String pubKeyPath = "D:\\ras\\ras.pub";
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 1;
}
@Override
public boolean shouldFilter() {
//判断是否是登录路径
//1,获取请求上下文
RequestContext requestContext = RequestContext.getCurrentContext();
//2,获得请求
HttpServletRequest request = requestContext.getRequest();
//3,获得请求路径
String requestURI = request.getRequestURI();
System.err.println(requestURI);
//4,判断
if("/api/examcustomer/customer/login".equals(requestURI)){
return false;
}
return true;
}
@Override
public Object run() throws ZuulException {
//1,获得请求上下文
RequestContext requestContext = RequestContext.getCurrentContext();
//2,获得请求对象
HttpServletRequest request = requestContext.getRequest();
//3,获得请求头,获得token值
String token = request.getHeader("Authorization");
//4,判断-校验
try {
JwtUtils.getObjectFromToken(token, RasUtils.getPublicKey(pubKeyPath), Customer.class);
} catch (Exception e) {
e.printStackTrace();
// 获取token错误, 不予许放行, 返回403状态码
requestContext.setSendZuulResponse(false);
requestContext.setResponseStatusCode(403);
}
//放行
return null;
}
}
- 步骤五: token获取失败, 在api.js中,跳转登录页面登录 (可选)
axios.interceptors.response.use(response => {
// 放行
return response
}, error => {
// 错误提示
// 获取响应状态码
console.info(error.response);
if(error.response.status=='403'){
router.push("/login")
}
console.info(error)
Message.error(error.message)
return Promise.reject(error)
})
4.工具类
- JwtUtils
package com.czxy.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.commons.beanutils.BeanUtils;
import org.joda.time.DateTime;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.security.PrivateKey;
import java.security.PublicKey;
public class JwtUtils {
/**
* 私钥加密token
* @param data 需要加密的数据(载荷内容)
* @param expireMinutes 过期时间,单位:分钟
* @param privateKey 私钥
* @return
*/
public static String generateToken(Object data, int expireMinutes, PrivateKey privateKey) throws Exception {
//1 获得jwt构建对象
JwtBuilder jwtBuilder = Jwts.builder();
//2 设置数据
if( data == null ) {
throw new RuntimeException("数据不能为空");
}
BeanInfo beanInfo = Introspector.getBeanInfo(data.getClass());
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
// 获得属性名
String name = propertyDescriptor.getName();
// 获得属性值
Object value = propertyDescriptor.getReadMethod().invoke(data);
if(value != null) {
jwtBuilder.claim(name,value);
}
}
//3 设置过期时间
jwtBuilder.setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate());
//4 设置加密
jwtBuilder.signWith(SignatureAlgorithm.RS256, privateKey);
//5 构建
return jwtBuilder.compact();
}
/**
* 通过公钥解析token
* @param token 需要解析的数据
* @param publicKey 公钥
* @param beanClass 封装的JavaBean
* @return
* @throws Exception
*/
public static <T> T getObjectFromToken(String token, PublicKey publicKey,Class<T> beanClass) throws Exception {
//1 获得解析后内容
Claims body = Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token).getBody();
//2 将内容封装到对象JavaBean
T bean = beanClass.newInstance();
BeanInfo beanInfo = Introspector.getBeanInfo(beanClass);
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
// 获得属性名
String name = propertyDescriptor.getName();
// 通过属性名,获得对应解析的数据
Object value = body.get(name);
if(value != null) {
// 将获得的数据封装到对应的JavaBean中
BeanUtils.setProperty(bean,name,value);
}
}
return bean;
}
}
- RasUtils
package com.czxy.utils;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
public class RasUtils {
/**
* 从文件中读取公钥
*
* @param filename 公钥保存路径,相对于classpath
* @return 公钥对象
* @throws Exception
*/
public static PublicKey getPublicKey(String filename) throws Exception {
byte[] bytes = readFile(filename);
return getPublicKey(bytes);
}
/**
* 从文件中读取密钥
*
* @param filename 私钥保存路径,相对于classpath
* @return 私钥对象
* @throws Exception
*/
public static PrivateKey getPrivateKey(String filename) throws Exception {
byte[] bytes = readFile(filename);
return getPrivateKey(bytes);
}
/**
* 获取公钥
*
* @param bytes 公钥的字节形式
* @return
* @throws Exception
*/
public static PublicKey getPublicKey(byte[] bytes) throws Exception {
X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
KeyFactory factory = KeyFactory.getInstance("RSA");
return factory.generatePublic(spec);
}
/**
* 获取密钥
*
* @param bytes 私钥的字节形式
* @return
* @throws Exception
*/
public static PrivateKey getPrivateKey(byte[] bytes) throws Exception {
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
KeyFactory factory = KeyFactory.getInstance("RSA");
return factory.generatePrivate(spec);
}
/**
* 根据密文,生存rsa公钥和私钥,并写入指定文件
*
* @param publicKeyFilename 公钥文件路径
* @param privateKeyFilename 私钥文件路径
* @param secret 生成密钥的密文
* @throws Exception
*/
public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret) throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
SecureRandom secureRandom = new SecureRandom(secret.getBytes());
keyPairGenerator.initialize(1024, secureRandom);
KeyPair keyPair = keyPairGenerator.genKeyPair();
// 获取公钥并写出
byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
writeFile(publicKeyFilename, publicKeyBytes);
// 获取私钥并写出
byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
writeFile(privateKeyFilename, privateKeyBytes);
}
private static byte[] readFile(String fileName) throws Exception {
return Files.readAllBytes(new File(fileName).toPath());
}
private static void writeFile(String destPath, byte[] bytes) throws IOException {
File dest = new File(destPath);
//创建父文件夹
if(!dest.getParentFile().exists()){
dest.getParentFile().mkdirs();
}
//创建需要的文件
if (!dest.exists()) {
dest.createNewFile();
}
Files.write(dest.toPath(), bytes);
}
}