关于JWT的内容,请看以下链接,主要看它的原理,以及缺点!
https://blog.csdn.net/memmsc/article/details/78122931
步骤1:前端传userName+password到后端,后端为springcloud架构,经过网关的拦截器拦截请求,拦截器在项目启动的时候@Component进行加载。
步骤2:如果是第一次登陆,放行,进入JWT的加密生成Token阶段(还可以写入登陆用户的其他信息放在JWTmap中,之后可以利用Token来获取该用户信息),加密token需要一个随机数作为加密字段,将token的失效时间设置为一天,并且放到reids里面,设置该redis里面的token过期时间为30分钟,最后将Token返回给前端。
步骤3:以后任何的请求都带Token到后端去请求。
步骤4:拦截到非登陆请求,进行解密,鉴权,如果鉴权通过,更新redis里面token字段的失效时间,如果还有5分钟失效,再设置还有30分钟,目的就是让密码的过期时间变的活跃。
大致就是以上的过程,核心代码主要在网关拦截器解密鉴权和登陆接口的加密两部分
0,controller层的将得到的token做保存redis和设置过期时间的操作
- compactJws = authService.generateJwt(username, password, userBean);
- //将token存在redis里
- stringRedisTemplate.opsForValue().set("token", compactJws);
- //设置redis里面的数据失效时间为半小时
- stringRedisTemplate.expire("token",1800,TimeUnit.SECONDS);
1,登陆接口的加密:
- package com.movitech.user.service.imp;
-
- import com.movitech.commons.entity.UserBean;
- import com.movitech.commons.utils.CommonConstants;
- import com.movitech.user.service.AuthService;
- import io.jsonwebtoken.Jwts;
- import io.jsonwebtoken.SignatureAlgorithm;
- import org.joda.time.DateTime;
- import org.springframework.stereotype.Service;
-
- import java.util.Base64;
- import java.util.HashMap;
- import java.util.Map;
-
- /**
- * 用户身份验证Service
- */
- @Service(value = "authService")
- public class AuthServiceImpl implements AuthService {
- @Override
- public String generateJwt(String userName, String userPassword, UserBean userBean) {
- // Base64编码后的secretKey
- byte[] secretKey = Base64.getEncoder().encode(CommonConstants.SECURITY_KEY.getBytes());
- // 设置失效时间
- DateTime expirationDate = new DateTime().plusDays(1);
- //DateTime expirationDate = new DateTime().plusMinutes(30);
- // Claims是需要保存到token中的信息,可以自定义,需要存什么就放什么,会保存到token的payload中
- Map<String, Object> claims = new HashMap<>();
- // 用户角色
- claims.put("role", "user");
- // 用户名
- claims.put("userName", userName);
- claims.put(CommonConstants.USER_ID, userBean.getId());
- claims.put("uuid",UUID.randomUUID().toString());
- String compactJws = Jwts.builder()
- // 设置subject,一般是用户的唯一标识,比如用户对象的ID,用户名等,目前设置的是userCode
- .setSubject(userName)
- // 设置失效时间
- .setExpiration(expirationDate.toDate())
- .addClaims(claims)
- // 加密算法是HS512,加密解密统一就可以
- .signWith(SignatureAlgorithm.HS512, secretKey)
- .compact();
- return compactJws;
- }
-
- }
以上常量类和pojo此处省略。。。。
2,网关拦截器解密鉴权:
- package com.movitech.gateway.filter;
-
- import com.movitech.commons.dto.ErrorResponseMap;
- import com.movitech.commons.enums.ErrorCode;
- import com.movitech.commons.utils.CommonConstants;
- import com.movitech.commons.utils.JsonUtil;
- import com.movitech.commons.utils.ResponseUtil;
- import com.netflix.zuul.ZuulFilter;
- import com.netflix.zuul.context.RequestContext;
- import io.jsonwebtoken.*;
- import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
- import org.springframework.http.HttpHeaders;
- import org.springframework.http.HttpMethod;
- import org.springframework.stereotype.Component;
- import org.springframework.util.StringUtils;
-
- import javax.servlet.http.HttpServletRequest;
- import java.util.Base64;
-
- @Component
- public class SecurityFilter extends ZuulFilter {
- @Override
- public String filterType() {
- return FilterConstants.PRE_TYPE;
- }
-
- @Override
- public int filterOrder() {
- return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
- }
-
- @Override
- public boolean shouldFilter() {
- RequestContext ctx = RequestContext.getCurrentContext();
- HttpServletRequest request = ctx.getRequest();
- if (request.getRequestURL().toString().contains("loginInfo") || request.getRequestURL().toString().contains("info")) {
- return false;
- }
- // TODO
- return true;
- }
-
- @Override
- public Object run() {
- RequestContext ctx = RequestContext.getCurrentContext();
- HttpServletRequest request = ctx.getRequest();
- final String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
-
- if (HttpMethod.OPTIONS.name().equals(request.getMethod())) {
- return null;
- } else {
- if (StringUtils.isEmpty(authorizationHeader) || !authorizationHeader.startsWith(CommonConstants.BEARER)) {
- // Missing or invalid Authorization header
- ErrorResponseMap errorResponseMap = ResponseUtil.createErrorResponse(null, "Missing or invalid Authorization header!",
- ErrorCode.INVALID_AUTHORIZATION_HEADER, request, null);
- denyAccess(ctx,errorResponseMap);
- return JsonUtil.serializeToString(errorResponseMap);
- }
- final String token = authorizationHeader.substring(7);
- try {
- byte[] secretKey = Base64.getEncoder().encode(CommonConstants.SECURITY_KEY.getBytes());
- Claims claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody();
- if (claims != null) {
- //获取redis里面数据的存活时间
- Long expirationDate = stringRedisTemplate.getExpire("token",TimeUnit.SECONDS);
- //如果还剩余5分钟,重置redis里面数据的存活时间
- if(expirationDate > 300){
- stringRedisTemplate.expire("token",1800,TimeUnit.SECONDS);
- }else {
- ErrorResponseMap errorResponseMap = new ErrorResponseMap();
- Error error = new Error(null, "Token expired!", "", 1003,"");
- errorResponseMap.setSuccess(false);
- errorResponseMap.setMessage(null);
- errorResponseMap.setError(error);
- denyAccess(ctx,errorResponseMap);
- return JsonUtil.serializeToString(errorResponseMap);
- }
- String userName = (String) claims.get(CommonConstants.USER_CODE);
- Integer userId = (Integer) claims.get(CommonConstants.USER_ID);
- ctx.addZuulRequestHeader(CommonConstants.USER_CODE, userName);
- ctx.addZuulRequestHeader(CommonConstants.USER_ID, String.valueOf(userId));
- }
- } catch (MalformedJwtException ex) {
- ErrorResponseMap errorResponseMap = ResponseUtil.createErrorResponse(null, "Invalid token!",
- ErrorCode.INVALID_AUTHORIZATION_HEADER, request, ex);
- denyAccess(ctx,errorResponseMap);
- return JsonUtil.serializeToString(errorResponseMap);
- } catch (SignatureException ex) {
- ErrorResponseMap errorResponseMap = ResponseUtil.createErrorResponse(null, "Token Signature error!",
- ErrorCode.SIGNATURE_EXCEPTION, request, ex);
- denyAccess(ctx,errorResponseMap);
- return JsonUtil.serializeToString(errorResponseMap);
- } catch (ExpiredJwtException ex) {
- ErrorResponseMap errorResponseMap = ResponseUtil.createErrorResponse(null, "Token expired!",
- ErrorCode.EXPIRED_JWT_EXCEPTION, request, ex);
- denyAccess(ctx,errorResponseMap);
- return JsonUtil.serializeToString(errorResponseMap);
- }
- }
- return null;
- }
-
- private void denyAccess(RequestContext ctx, ErrorResponseMap authResult) {
- String result = JsonUtil.serializeToString(authResult);
- ctx.setSendZuulResponse(false);
- ctx.setResponseStatusCode(401);
- try {
- ctx.getResponse().getWriter().write(result);
- }catch (Exception e){}
- }
- }
以上代码是核心代码,下面是自己项目中涉及到的异常包装类,以及util类,可以不管下面的,直接去封装
以上涉及到的类:
(1)ErrorResponseMap
- package com.movitech.commons.dto;
-
- import com.fasterxml.jackson.annotation.JsonProperty;
- import com.movitech.commons.exception.Error;
- import lombok.Getter;
- import lombok.Setter;
-
- @Getter
- @Setter
- public class ErrorResponseMap extends ResponseMap {
- @JsonProperty(value = "error")
- private Error error;
- @JsonProperty(value = "stackTrace")
- private String stackTrace;
- }
(1.1)Error
- package com.movitech.commons.exception;
-
- import lombok.AllArgsConstructor;
- import lombok.Getter;
- import lombok.NoArgsConstructor;
- import lombok.Setter;
-
- @Getter
- @Setter
- @NoArgsConstructor
- @AllArgsConstructor
- public class Error {
- // 标准的 Http status code
- private Integer httpStatusCode;
- // 自定义的错误说明
- private String errorMsg;
- // 异常信息
- private String exceptionMsg;
- // 自定义的错误代码
- private Integer errorCode;
- // 异常的类名
- private String exceptionClassName;
- }
(2)ErrorCode
- package com.movitech.commons.enums;
-
- /**
- * 自定义的错误代码的枚举
- */
- public enum ErrorCode {
- // Token 签名错误
- SIGNATURE_EXCEPTION(1000),
- // Token 过期
- EXPIRED_JWT_EXCEPTION(1001),
- // 无效的Authorization header
- INVALID_AUTHORIZATION_HEADER(1002);
- private Integer errorCode;
- ErrorCode(Integer errorCode) {
- this.errorCode = errorCode;
- }
-
- @Override
- public String toString() {
- return errorCode.toString();
- }
-
- public Integer value() {
- return errorCode;
- }
- }
(3)JsonUtil
- package com.movitech.commons.utils;
-
- import com.fasterxml.jackson.core.JsonProcessingException;
- import com.fasterxml.jackson.databind.*;
- import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
- import org.springframework.stereotype.Component;
- import org.springframework.util.StringUtils;
-
- import java.io.IOException;
-
- /**
- * Json序列化和反序列化
- */
- @Component
- public class JsonUtil
- {
- public static ObjectMapper mapper;
- /* static {
- dao = new ObjectMapper();
- dao.configure(SerializationFeature.WRAP_ROOT_VALUE,true);
- dao.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
- }*/
-
- public JsonUtil(Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder) {
- mapper = jackson2ObjectMapperBuilder.build();
- }
-
- public static String serializeToString(Object object) {
- return serializeToString(object,false);
- }
-
- public static String serializeToString(Object object,Boolean rootValueState) {
- SerializationConfig serializationConfig = mapper.getSerializationConfig();
- mapper.configure(SerializationFeature.WRAP_ROOT_VALUE,rootValueState);
- if (object != null) {
- try {
- return mapper.writeValueAsString(object);
- } catch (JsonProcessingException e) {
- e.printStackTrace();
- } finally {
- mapper.setConfig(serializationConfig);
- }
- }
- return "";
- }
-
- public static byte[] serializeToBytes(Object object) {
- if (object != null) {
- try {
- return mapper.writeValueAsBytes(object);
- } catch (JsonProcessingException e) {
- e.printStackTrace();
- }
- }
- return null;
- }
-
- /**
- * 反序列化Json数据
- * @param jsonData Json数据字符串
- * @param valueType 反序列化的类型
- * @param rootValueState 是否解析Json root name
- * @param <T>
- * @return 反序列化后的POJO
- */
- public static <T> T deserialize(String jsonData,Class<T> valueType,Boolean rootValueState) {
- if (StringUtils.isEmpty(jsonData) || rootValueState == null) {
- return null;
- }
- DeserializationConfig deserializationConfig = mapper.getDeserializationConfig();
- mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE,rootValueState);
- try {
- return mapper.readValue(jsonData, valueType);
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- mapper.setConfig(deserializationConfig);
- }
- return null;
- }
-
- /**
- * 反序列化Json数据,默认解析Json root name
- * @param jsonData Json数据字符串
- * @param valueType 反序列化的类型
- * @param <T>
- * @return 反序列化后的POJO
- */
- public static <T> T deserialize(String jsonData,Class<T> valueType) {
- return deserialize(jsonData, valueType, true);
- }
-
- public static JavaType getCollectionType(Class<?> collectionClass, Class<?>... elementClasses) {
- return mapper.getTypeFactory().constructParametricType(collectionClass, elementClasses);
- }
-
- /**
- * 用Json数据中的key获取对应的value
- * @param jsonString json数据字符串
- * @param field 需要取值的字段
- * @param rootValueState 是否解析Json root name
- * @return 字段对应的值
- */
- public static String getValue(String jsonString,String field,Boolean rootValueState) {
- JsonNode node = getJsonNode(jsonString,field,rootValueState);
- return node == null ? "" : node.toString();
- }
-
- /**
- * 用Json数据中的key获取对应的value, 默认解析Json root name
- * @param jsonString json数据字符串
- * @param field 需要取值的字段
- * @return 字段对应的值
- */
- public static String getValue(String jsonString,String field) {
- return getValue(jsonString,field,true);
- }
-
- /**
- * 用Json数据中的key获取对应的value
- * @param jsonString json数据字符串
- * @param field 需要取值的字段
- * @param rootValueState 是否解析Json root name
- * @return 字段对应的值
- */
- public static JsonNode getJsonNode(String jsonString, String field, Boolean rootValueState) {
- if (StringUtils.isEmpty(jsonString) || StringUtils.isEmpty(field) || rootValueState == null) {
- return null;
- }
- // 准备工作 传入vo请参照第一篇里面的实体。此处不再重新贴上代码 浪费大家时间
- JsonNode node = null;// 这里的JsonNode和XML里面的Node很像
- // 默认的反序列化配置
- DeserializationConfig deserializationConfig = mapper.getDeserializationConfig();
- mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE,rootValueState);
- try {
- node = mapper.readTree(jsonString);
- } catch (IOException e) {
- e.printStackTrace();
- return null;
- } finally {
- mapper.setConfig(deserializationConfig);
- }
- return node.get(field) == null ? null : node.get(field);
- }
- }
(4)ResponseUtil
- package com.movitech.commons.utils;
-
- import com.movitech.commons.dto.ErrorResponseMap;
- import com.movitech.commons.enums.ErrorCode;
- import com.movitech.commons.exception.Error;
- import org.springframework.http.HttpStatus;
- import org.springframework.http.ResponseEntity;
-
- import javax.servlet.http.HttpServletRequest;
-
- public class ResponseUtil {
- public static ResponseEntity createResponseEntity(String errorMsg, ErrorCode errorCode, HttpServletRequest request, Throwable ex) {
- HttpStatus status = getStatus(request);
- ErrorResponseMap errorResponseMap = createErrorResponse(status, errorMsg, errorCode, request, ex);
- return new ResponseEntity<>(errorResponseMap, status);
- }
-
- public static ErrorResponseMap createErrorResponse(HttpStatus status, String errorMsg, ErrorCode errorCode, HttpServletRequest request, Throwable ex) {
- if (status == null) {
- status = getStatus(request);
- }
- ErrorResponseMap errorResponseMap = new ErrorResponseMap();
- Error error = new Error(status.value(), errorMsg, ex == null ? "" : ex.getMessage(),
- errorCode == null ? HttpStatus.INTERNAL_SERVER_ERROR.value() : errorCode.value(), ex == null ? "" : ex.getClass().getCanonicalName());
- errorResponseMap.setSuccess(false);
- errorResponseMap.setMessage(ex.getMessage());
- errorResponseMap.setError(error);
- // String stackTrace = StringUtils.arrayToDelimitedString(ex.getStackTrace(), "hahaha");
- // errorResponseMap.setStackTrace(stackTrace);
- return errorResponseMap;
- }
-
- public static HttpStatus getStatus(HttpServletRequest request) {
- Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
- if (statusCode == null) {
- return HttpStatus.INTERNAL_SERVER_ERROR;
- }
- return HttpStatus.valueOf(statusCode);
- }
- }