当前在开发的系统,我是半路加入进来帮助开发几个接口,也没有特意去全面的了解这个系统的架构安全验证等问题,
最多也就是在开发功能接口时看到有不规范,设计不合理的一些对象、方法会和团队其他成员交流一下。在之前的几篇文章有介绍《简单聊一聊代码的设计原则》、《你的代码注意规范吗》。
最近开发的这个接口我需要获取一下登录者的信息,然后就被惊掉下巴了。
are you ok?
这个系统竟然没有任何验证,登录的接口只是拿着账号密码去数据库匹配了一下。
登录之后没有任何处理,没有登录记录,没有生成token等一系列操作。
后面的所有请求完全没有任何验证,也就是说
只要你能发出请求就能进入系统。跟这个项目的开发driver碰了一下
,说是没有考虑那些东西,太麻烦。
?那能说啥?做个系统企业级系统,也不设计一下,照着产品原型就是有一顿敲,当学生作业了吗?当然这也不能全怪一线开发人员,毕竟每个人的经验不同,主要责任我觉得还是部门领导的。这个领导已经令人失望不是一次两次了《你的领导是傻X吗》。
话说回来,开发人员考虑也太不周到了。像登录验证把JWT这套东西直接拿来用就行呗,有啥麻烦的?
其实这事我完全可以不闻不问,我只是临时加入进来帮助开发几个接口就完成任务了。
本着对得起良心,对得起公司发的工资的责任,开始吧,毕竟也不是啥难事。那么今天我们就来看看这个JWT到底麻烦不麻烦!JWT是干嘛的咱就不介绍了,直接摞代码。
首先是token的工具类
![d3005937a6f4535db6b219c98020a105.png](https://i-blog.csdnimg.cn/blog_migrate/9f5dfab0268b6ac611524c7c9ade8908.jpeg)
import com.hnk.lib.response.exception.ApiException;import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.databind.ObjectMapper;import io.jsonwebtoken.Claims;import io.jsonwebtoken.Jwts;import io.jsonwebtoken.SignatureAlgorithm;import io.jsonwebtoken.io.Decoders;import io.jsonwebtoken.security.Keys;import lombok.Data;import lombok.extern.slf4j.Slf4j;import javax.crypto.SecretKey;import java.util.Calendar;import java.util.Date;import java.util.HashMap;import java.util.Map;public class DefaultJwtToken { private static String secret = "111111222222222223333333333344444444455555555fs+fsdfdsgd"; public static final String HEADER_TOKEN = "token"; private static final Map<String, String> TOKEN_MAP = new HashMap<>(); /** * 生成token */ public static String createToken(String subject) { SecretKey secretKey = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret)); Date nowDate = new Date(); Calendar calendar = Calendar.getInstance(); calendar.setTime(nowDate); calendar.add(Calendar.DAY_OF_MONTH, 10); Date expireDate = calendar.getTime(); String userToken = Jwts.builder().setHeaderParam("typ", "JWT") .setSubject(subject).setIssuedAt(nowDate).setExpiration(expireDate).signWith(secretKey, SignatureAlgorithm.HS256).compact(); TOKEN_MAP.put(subject, userToken); return userToken; } /** * 为方便生成token */ public static String createToken(LoginUser user) { String subject = user.toTokenJson(); return createToken(subject); } public static String createRefreshToken(String subject) { SecretKey secretKey = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret)); return Jwts.builder().setHeaderParam("typ", "JWT") .setSubject(subject).setIssuedAt(new Date()) .signWith(secretKey, SignatureAlgorithm.HS256).compact(); } /** * 获取token中注册信息 */ public static Claims getTokenClaim(String token) { return Jwts.parserBuilder().setSigningKey(secret).build().parseClaimsJws(token).getBody(); } /** * 验证token是否过期失效 */ public static boolean isTokenExpired(Date expirationTime) { return expirationTime.before(new Date()); } /** * 获取token失效时间 */ public Date getExpirationDateFromToken(String token) { return getTokenClaim(token).getExpiration(); } /** * 获取用户名从token中 */ public static String getUsernameFromToken(String token) { return getTokenClaim(token).getSubject(); } public static LoginUser getLoginUserFromToken(String token) { ObjectMapper objectMapper = new ObjectMapper(); try { return objectMapper.readValue(getUsernameFromToken(token), LoginUser.class); } catch (JsonProcessingException e) { return null; } } /** * 获取jwt发布时间 */ public Date getIssuedAtDateFromToken(String token) throws ApiException { return getTokenClaim(token).getIssuedAt(); } public static Map<String, String> getTOKEN_MAP() { return TOKEN_MAP; }}
public class LoginUser { private String accountName; private String userUid; //构造函数 set get 省略}
添加拦截器
import com.hnk.lib.response.ApiResponse;import com.hnk.lib.response.constants.ApiCodeConstants;import io.jsonwebtoken.Claims;import org.apache.commons.lang3.StringUtils;import org.springframework.http.MediaType;import org.springframework.stereotype.Component;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.io.BufferedWriter;import java.io.IOException;import java.io.OutputStreamWriter;import java.lang.reflect.Method;@Componentpublic class TokenInterceptor extends HandlerInterceptorAdapter { private static final int UNAUTHORIZED_ERROR_CODE = HttpServletResponse.SC_UNAUTHORIZED; private static final String UNAUTHORIZED_ERROR_MESSAGE = "401 unauthorized"; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); if (method.isAnnotationPresent(Authentication.class) || handlerMethod.getBeanType().isAnnotationPresent(Authentication.class)) { String token = request.getHeader(DefaultJwtToken.HEADER_TOKEN); if (StringUtils.isNotBlank(token)) { Claims claims = DefaultJwtToken.getTokenClaim(token); if (claims != null) { String userName = claims.getSubject(); String compareToken = DefaultJwtToken.getTOKEN_MAP().get(userName); if (token.equals(compareToken)) { Authentication authentication = method.getAnnotation(Authentication.class); if (authentication.required()) { if (!DefaultJwtToken.isTokenExpired(claims.getExpiration())) { return true; } } else { return true; } } } } response.setStatus(UNAUTHORIZED_ERROR_CODE); response.setContentType(MediaType.APPLICATION_JSON_VALUE); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(response.getOutputStream())); writer.write(new ApiResponse<>(ApiCodeConstants.TOKEN_INVALID.getCode(), ApiCodeConstants.TOKEN_INVALID.getMessage(), UNAUTHORIZED_ERROR_MESSAGE).toJson()); writer.close(); return false; } return true; }}
在需要验证token的对象或方法上添加注解
@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public @interface Authentication { boolean required() default true;}
为了使拦截器起作用当然还要配置一下
import com.hnk.lib.token.CurrentUserMethodArgumentResolver;import com.hnk.lib.token.TokenInterceptor;import org.springframework.context.annotation.Configuration;import org.springframework.web.method.support.HandlerMethodArgumentResolver;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import javax.annotation.Resource;import java.util.List;@Configurationpublic class WebConfigFilter implements WebMvcConfigurer { @Resource private TokenInterceptor tokenInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(tokenInterceptor).addPathPatterns("/**"); }}
Token这步算是完事了。但还有一个常规的需求,就是根据Token获取当前登录者信息。具体使用方式如下:
@GetMapping("platform-corp/list") @ResponseBody public Object getPlatformCorpList(@CurrentInstUser LoginUser user,@RequestParam(value = "keywords", defaultValue = "", required = false) String keywords
所以我们再加一步
@Target(ElementType.PARAMETER)@Retention(RetentionPolicy.RUNTIME)public @interface CurrentUser {}import org.apache.commons.lang3.StringUtils;import org.springframework.core.MethodParameter;import org.springframework.stereotype.Component;import org.springframework.web.bind.support.WebDataBinderFactory;import org.springframework.web.context.request.NativeWebRequest;import org.springframework.web.method.support.HandlerMethodArgumentResolver;import org.springframework.web.method.support.ModelAndViewContainer;import javax.servlet.http.HttpServletRequest;@Componentpublic class CurrentUserMethodArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { // 如果参数类型是 User 并且有 CurrentUser 注解则支持 if (parameter.getParameterType().isAssignableFrom(LoginUser.class) && parameter.hasParameterAnnotation(CurrentUser.class)) { return true; } return false; } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { return getUser(webRequest.getNativeRequest(HttpServletRequest.class)); } public static LoginUser getUser(HttpServletRequest webRequest) { //取出session信息 String token = webRequest.getHeader(JwtConfig.HEADER_TOKEN); if (StringUtils.isBlank(token)) { return new LoginUser(); } return JwtConfig.getLoginUserFromToken(token); }}
登录后验证token并将token返回给前端,在后续调用接口中必须携带token进行请求。完事!