有一些接口,需要用户登录以后才能访问,用户没有登录则无法访问。 因此,对于一些限制用户访问的接口,可以在请求头中增加一个校验参数,用于判断接口对应的用户是否登录。 而对于一些不需要登录即可访问的接口,则需要进行放行操作。
采用JWT方式生成token,登录成功后将用户的部分信息加密后生成token,并且保存到redis中。
com.auth0
java-jwt
3.14.0
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.mashibing.internalcommon.dto.TokenResult;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JWTUtils {
// JWT签名
private static final String SIGN = "i_am_study";
// 用户信息
private static final String JWT_KEY_PHONE = "phone";
private static final String JWT_KEY_IDENTITY = "identity";
private static final String TOKEN_TYPE = "tokenType";
private static final String TOKEN_TIME = "tokenTime";
/**
* 根据用户信息生成JWT
* @param passengerPhone 手机号
* @param identity 用户身份类型标识
* @param tokenType token类型标识
* @return
*/
public static String generatorToken(String passengerPhone, String identity, String tokenType){
Map map = new HashMap<>();
map.put(JWT_KEY_PHONE, passengerPhone);
map.put(JWT_KEY_IDENTITY, identity);
map.put(TOKEN_TYPE, tokenType);
map.put(TOKEN_TIME, Calendar.getInstance().getTime().toString());
// 整合 map
JWTCreator.Builder builder = JWT.create();
map.forEach(
(k, v) -> {
builder.withClaim(k, v);
}
);
String sign = builder.sign(Algorithm.HMAC256(SIGN));
return sign;
}
/**
* 解析JWT获取用户信息
* @param token JWT类型字符串
* @return
*/
public static TokenResult paseToken(String token) {
DecodedJWT verify = JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
String phone = verify.getClaim(JWT_KEY_PHONE).asString();
String identity = verify.getClaim(JWT_KEY_IDENTITY).asString();
TokenResult tokenResult = new TokenResult();
tokenResult.setIdentity(identity);
tokenResult.setPhone(phone);
return tokenResult;
}
/**
* 验证JWT合法性
* @param token JWT字符串
* @return
*/
public static TokenResult checkToken(String token) {
TokenResult tokenResult = null;
try {
tokenResult = JWTUtils.paseToken(token);
} catch (Exception e) {
}
return tokenResult;
}
}
用户登录采用的是手机号+验证码的方式,根据手机号从Redis中拿到验证码,并与用户输入的验证码进行比较,如果相等则返回双token。
/**
* 用户根据手机号和验证码登录
* @param driverPhone 手机号
* @param verificationCode 验证码
* @return
*/
public ResponseResult checkVerficationCode(String driverPhone, String verificationCode) {
// 1.根据手机号获取验证码
String key = RedisPrefixUtils.generatorKeyByPhone(driverPhone, IdentityConstant.DRIVER_IDENTITY);
String codeRedis = redisTemplate.opsForValue().get(key);
System.out.println("从redis获取的验证码是: " + codeRedis);
// 2.校验验证码
if (StringUtil.isNullOrEmpty(codeRedis)) {
return ResponseResult.fail(CommonStatusEnum.VERIFICATON_CODE_EXPIRE.getCode(),CommonStatusEnum.VERIFICATON_CODE_EXPIRE.getValue());
}
if (!verificationCode.trim().equals(codeRedis)){
return ResponseResult.fail(CommonStatusEnum.VERIFICATION_CODE_ERRO.getCode(),CommonStatusEnum.VERIFICATION_CODE_ERRO.getValue());
}
// 4.生成token返回
String accessToken = JWTUtils.generatorToken(driverPhone, IdentityConstant.DRIVER_IDENTITY, TokenConstant.ACCESS_TOKEN_TYPE);
String refreshToken = JWTUtils.generatorToken(driverPhone, IdentityConstant.DRIVER_IDENTITY, TokenConstant.REFRESH_TOKEN_TYPE);
// 生成key
String accessTokenKey = RedisPrefixUtils.generatorTokenKey(driverPhone, IdentityConstant.DRIVER_IDENTITY, TokenConstant.ACCESS_TOKEN_TYPE);
String refreshTokenKey = RedisPrefixUtils.generatorTokenKey(driverPhone, IdentityConstant.DRIVER_IDENTITY, TokenConstant.REFRESH_TOKEN_TYPE);
// 存到redis
redisTemplate.opsForValue().set(accessTokenKey, accessToken, 30, TimeUnit.DAYS);
redisTemplate.opsForValue().set(refreshTokenKey, refreshToken, 31, TimeUnit.DAYS);
// 结果返回
TokenResponse tokenResponse = new TokenResponse();
tokenResponse.setAcccessToken(accessToken);
tokenResponse.setRefreshToken(refreshToken);
return ResponseResult.success(tokenResponse);
}
重写HandlerInterceptor
接口的preHandle
方法,拿到token进行判断 。 只需要从请求头中拿到token,然后根据token拿到对应的存储到redis中的key,根据key查询redis判断是否存在对应token,存在则放行,不存在则不放行。
import net.sf.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
public class JwtInterceptor implements HandlerInterceptor {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("Authorization");
boolean result = true;
String resultString = "";
// 解析token,校验token的准确性
TokenResult tokenResult = JWTUtils.checkToken(token);
if(tokenResult == null){
resultString = "token valid";
result = false;
}else {
// 解析token,获取token中携带的参数信息
tokenResult = JWTUtils.paseToken(token);
// 判断redis中是否存在对应token,即判断token是否到期
String tokenKey = RedisPrefixUtils.generatorTokenKey(tokenResult.getPhone(), IdentityConstant.DRIVER_IDENTITY, TokenConstant.ACCESS_TOKEN_TYPE);
String redisToken = stringRedisTemplate.opsForValue().get(tokenKey);
if (redisToken==null || !redisToken.trim().equals(token)){
resultString = "token valid";
result = false;
}
}
if (!result) {
// 告知前端
PrintWriter out = response.getWriter();
out.print(JSONObject.fromObject(ResponseResult.fail(resultString)).toString());
}
return result;
}
}
import org.springframework.context.annotation.Bean;
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 InterceptorConfig implements WebMvcConfigurer {
@Bean
public JwtInterceptor jwtInterceptor(){
return new JwtInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor())
// 拦截的路径
.addPathPatterns("/**")
// 不拦截的路径
.excludePathPatterns(
"/noauth",
"/error",
"/verification-code",
"/verification-code-check"
);
}
}