单体架构的时候,登陆操作需要整合shiro。
而现在到了微服务阶段,我们需要在各个服务中都整合shiro,这样就做了很多重复的工作。
这时就用到了网关 ,网关 是微服务项目的唯一入口。
之前是前端请求直接访问controller层,现在是先访问Gateway,所以 API ⽹关的通常作⽤是完成⼀些通⽤的功能,如请求认证,请求记录,请求限流,⿊⽩名单判断等。
网关就像一个大楼的大门。
那网关中的UserController怎么调用UserService呢?因为这两个服务是两个单独的进程,进程之间的通信,就用到了Dubbo, 也就是说将UserService中的服务暴露出来,在网关中进行调用。
zookeeper进行:管理和服务发现
网关是一个单独的服务,可以和下图中的其他服务在不同的机器上部署。
JWT
http协议是无状态的,这种特性意味着程序需要验证每一次请求来辨别客户端的身份。
cookie、session、token区别:
参看博客:https://www.cnblogs.com/moyand/p/9047978.html
http和https的联系与区别:
https://zhuanlan.zhihu.com/p/72616216
https://www.cnblogs.com/wqhwe/p/5407468.html
如何使用JWT:
JwtTokenUtils:
package com.mall.user.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.mall.commons.tool.exception.ValidateException;
import com.mall.user.constants.SysRetCodeConstants;
import lombok.Builder;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
/**
* create by ciggar on 2020/04/08
*/
@Slf4j
@Builder // lombok提供的注解
public class JwtTokenUtils {
/**
* 传输信息,必须是json格式,可以用来存放用户信息
*/
private String msg;
/**
* 所验证的jwt
*/
@Setter
private String token;
// 密钥
private final String secret = "324iu23094u598ndsofhsiufhaf_+0wq-42q421jiosadiusadiasd";
public String creatJwtToken() {
msg = new AESUtil(msg).encrypt();//先对信息进行aes加密(防止被破解) AES 对称加密
String token = null;
try {
token = JWT.create()
.withIssuer("ciggar").withExpiresAt(DateTime.now().plusDays(1).toDate())
.withClaim("user", msg)
// sign表示指定加密算法
.sign(Algorithm.HMAC256(secret));
} catch (Exception e) {
throw e;
}
log.info("加密后:" + token);
return token;
}
public static void main(String[] args) {
// 加密
// 每次产生的token都不一样,因为签发时间和token的过期时间都是不一样的。
// String token = JwtTokenUtils.builder().msg("cskaoyan").build().creatJwtToken();
// System.out.println("产生的token:" + token);
// 解密
String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJjaWdnYXIiLCJleHAiOjE1ODc5ODE1MDEsInVzZXIiOiIwQTgzNDAxMjA2QjA5RDY3NTJCMUJDRDk5QkIwQTMwMiJ9.6_izud3xe7Fypthm5i-Z58aylIqIGU_Pq0uxzagFsrw";
String info = JwtTokenUtils.builder().token(token).build().freeJwt();
System.out.println(info);
}
/**
* 解密jwt并验证是否正确
*/
public String freeJwt() {
DecodedJWT decodedJWT = null;
try {
//使用hmac256加密算法
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret))
.withIssuer("ciggar")
.build();
decodedJWT = verifier.verify(token);
log.info("签名人:" + decodedJWT.getIssuer() + " 加密方式:" + decodedJWT.getAlgorithm() + " 携带信息:" + decodedJWT.getClaim("user").asString());
} catch (Exception e) {
log.info("jwt解密出现错误,jwt或私钥或签证人不正确");
throw new ValidateException(SysRetCodeConstants.TOKEN_VALID_FAILED.getCode(), SysRetCodeConstants.TOKEN_VALID_FAILED.getMessage());
}
//获得token的头部,载荷和签名,只对比头部和载荷
String[] headPayload = token.split("\\.");
//获得jwt解密后头部
String header = decodedJWT.getHeader();
//获得jwt解密后载荷
String payload = decodedJWT.getPayload();
if (!header.equals(headPayload[0]) && !payload.equals(headPayload[1])) {
throw new ValidateException(SysRetCodeConstants.TOKEN_VALID_FAILED.getCode(), SysRetCodeConstants.TOKEN_VALID_FAILED.getMessage());
}
return new AESUtil(decodedJWT.getClaim("user").asString()).decrypt();
}
}
AESUtils:
package com.mall.user.utils;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.io.IOException;
import java.io.InputStream;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Properties;
/**
* ciggar
* create-date: 2019/7/22-17:41
*/
@Slf4j
public class AESUtil {
//加密或解密内容
@Setter
private String content;
//加密密钥
private String secret;
public AESUtil(String content) {
this.content = content;
this.secret = "iwofnoadnsa922342mnjaolkdsao9423242niosadopa_a02402sad";
}
/**
* 加密
*
* @return 加密后内容
*/
public String encrypt() {
Key key = getKey();
byte[] result = null;
try {
//创建密码器
Cipher cipher = Cipher.getInstance("AES");
//初始化为加密模式
cipher.init(Cipher.ENCRYPT_MODE, key);
//加密
result = cipher.doFinal(content.getBytes("UTF-8"));
} catch (Exception e) {
log.info("aes加密出错:" + e);
}
//将二进制转换成16进制
StringBuffer sb = new StringBuffer();
for (int i = 0; i < result.length; i++) {
String hex = Integer.toHexString(result[i] & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
sb.append(hex.toUpperCase());
}
return sb.toString();
}
/**
* 解密
*
* @return 解密后内容
*/
public String decrypt() {
//将16进制转为二进制
if (content.length() < 1)
return null;
byte[] result = new byte[content.length() / 2];
for (int i = 0; i < content.length() / 2; i++) {
int high = Integer.parseInt(content.substring(i * 2, i * 2 + 1), 16);
int low = Integer.parseInt(content.substring(i * 2 + 1, i * 2 + 2), 16);
result[i] = (byte) (high * 16 + low);
}
Key key = getKey();
byte[] decrypt = null;
try {
//创建密码器
Cipher cipher = Cipher.getInstance("AES");
//初始化为解密模式
cipher.init(Cipher.DECRYPT_MODE, key);
//解密
decrypt = cipher.doFinal(result);
} catch (Exception e) {
log.info("aes解密出错:" + e);
}
assert decrypt != null;
return new String(decrypt);
}
/**
* 根据私钥内容获得私钥
*/
private Key getKey() {
SecretKey key = null;
try {
//创建密钥生成器
KeyGenerator generator = KeyGenerator.getInstance("AES");
//初始化密钥
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
random.setSeed(secret.getBytes());
generator.init(128, random);
//生成密钥
key = generator.generateKey();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return key;
}
public static void main(String[] args) {
AESUtil aesUtil = new AESUtil("Hello");
String ec = aesUtil.encrypt();
System.out.println(ec);
System.out.println(new AESUtil(ec).decrypt());
}
}
TokenIntercepter:
package com.mall.user.intercepter;
import com.alibaba.fastjson.JSON;
import com.mall.commons.result.ResponseData;
import com.mall.commons.result.ResponseUtil;
import com.mall.commons.tool.utils.CookieUtil;
import com.mall.user.annotation.Anoymous;
import com.mall.user.constants.SysRetCodeConstants;
import com.mall.user.dto.CheckAuthRequest;
import com.mall.user.dto.CheckAuthResponse;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
/**
* 用来实现token拦截认证
* <p>
* 其实就是用来判断当前这个操作是否需要登录
*/
public class TokenIntercepter extends HandlerInterceptorAdapter {
@Reference(timeout = 3000,check = false)
ILoginService iUserLoginService;
public static String ACCESS_TOKEN = "access_token";
public static String USER_INFO_KEY = "userInfo";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// handler就是要访问的目标方法
// 如果handler不是HandlerMethod类型,就直接放行
// 什么样的handler不是HandlerMethod类型呢? 静态资源
// 如果访问的是静态资源,直接放行
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
// Object bean = handlerMethod.getBean();
// 判断是否需要登录,如果不需要,直接放行
if (isAnoymous(handlerMethod)) {
return true;
}
// 从cookie里面去取token
String token = CookieUtil.getCookieValue(request, ACCESS_TOKEN);
if (StringUtils.isEmpty(token)) {
ResponseData responseData = new ResponseUtil().setErrorMsg("token已失效");
response.setContentType("text/html;charset=UTF-8");
response.getWriter().write(JSON.toJSON(responseData).toString());
return false;
}
//从token中获取用户信息
CheckAuthRequest checkAuthRequest = new CheckAuthRequest();
checkAuthRequest.setToken(token);
// token获取到了,怎么去验证呢?
// 其实就是使用 JwtUtils.freeJwt()把这个token给解密
CheckAuthResponse checkAuthResponse = iUserLoginService.validToken(checkAuthRequest);
if (checkAuthResponse.getCode().equals(SysRetCodeConstants.SUCCESS.getCode())) {
// 放到了Request域中
request.setAttribute(USER_INFO_KEY, checkAuthResponse.getUserinfo()); //保存token解析后的信息后续要用
return true;
}
ResponseData responseData = new ResponseUtil().setErrorMsg(checkAuthResponse.getMsg());
response.setContentType("text/html;charset=UTF-8");
response.getWriter().write(JSON.toJSON(responseData).toString());
return false;
}
private boolean isAnoymous(HandlerMethod handlerMethod) {
Object bean = handlerMethod.getBean();
// 获取目标方法的类对象
Class clazz = bean.getClass();
// 如果类上有@Anoymous注解,返回true
if (clazz.getAnnotation(Anoymous.class) != null) {
return true;
}
// 获取方法对象
Method method = handlerMethod.getMethod();
// 如果方法上有@Anoymous注解,返回true,否则返回false
return method.getAnnotation(Anoymous.class) != null;
}
}