前端请求拦截器
工具类 cryptomd5Util.js
const crypto = require('crypto')
const publicKey = 'xxxx'
export function encryptedHmacMd5Data(appid, data, timestamp) {
const hmac = crypto.createHmac('md5', publicKey)
let params = data
if (typeof data === 'object') {
for (const i in data) {
emptyParams(data, i)
}
params = JSON.stringify(data)
} else if (typeof data === 'string') {
params = data.replace(data.match('undefined'), '')
}
let paramsdata = appid + params + timestamp
paramsdata = paramsdata.replace(paramsdata.match('undefined'), '')
const up = hmac.update(paramsdata, 'utf-8')
return up.digest('hex')
}
function emptyParams(data, idx) {
if (data[idx] === undefined || data[idx] == null) {
data[idx] = ''
} else if (data[idx] instanceof Array) {
for (const i in data[idx]) {
emptyParams(data[idx], i)
}
} else if (typeof data[idx] === 'object') {
for (const key in data[idx]) {
if (data[idx][key] == null) {
data[idx][key] = ''
continue
}
emptyParams(data[idx], key)
}
}
}
export function encryptedMd5(data) {
return crypto.createHash('md5').update(data, 'utf-8').digest('hex')
}
request.js(重点看request拦截器)
import axios from 'axios'
import router from '@/router/routers'
import { Notification } from 'element-ui'
import store from '../store'
import { getToken } from '@/utils/auth'
import Config from '@/settings'
import Cookies from 'js-cookie'
import { encryptedHmacMd5Data } from '@/utils/cryptomd5Util'
// 创建axios实例
const service = axios.create({
baseURL: process.env.NODE_ENV === 'production' ? process.env.VUE_APP_BASE_API : '/', // api 的 base_url
timeout: Config.timeout // 请求超时时间
})
// request拦截器
service.interceptors.request.use(
config => {
if (getToken()) {
config.headers['Authorization'] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
//自定义appid
const systemAppid = 'xxxxx'
config.headers['appid'] = systemAppid
const timestamp = new Date().getTime()
config.headers['timestamp'] = timestamp
let params = ''
if (config.method === 'GET' || config.method === 'get') {
const arrs0 = JSON.parse(JSON.stringify(config.url))
const arrs = arrs0.split('?')
const arrs1 = arrs[1] || []
if (arrs1.length > 0) {
const arrs2 = arrs1.split('&')
arrs2.forEach((item) => {
const int = item.indexOf('=')
const str = item.slice(int + 1)
params = params + str
})
} else {
params = undefined
}
} else {
params = config.data
}
//根据appid,入参,时间戳加密签名
config.headers['sign'] = encryptedHmacMd5Data(systemAppid, params, timestamp)
config.headers['Content-Type'] = 'application/json'
return config
},
error => {
Promise.reject(error)
}
)
export default service
后端我也是很清楚,因为用的是springSecurity,看了一个博客看的生无可恋(自己技术太菜)原理解析
就根据在Spring中,Filter对应的Bean为GenericFilterBean。在自己项目搜索
先找到SpringSecurityConfig类 里面有configure方法
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.njry.modules.security.config;
import com.njry.annotation.AnonymousAccess;
import com.njry.modules.security.security.JwtAccessDeniedHandler;
import com.njry.modules.security.security.JwtAuthenticationEntryPoint;
import com.njry.modules.security.security.TokenConfigurer;
import com.njry.modules.security.security.TokenProvider;
import com.njry.utils.enums.RequestMethodEnum;
import lombok.RequiredArgsConstructor;
import com.njry.modules.security.config.bean.SecurityProperties;
import com.njry.modules.security.security.*;
import com.njry.modules.security.service.OnlineUserService;
import com.njry.modules.security.service.UserCacheManager;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.util.*;
/**
* @author Zheng Jie
*/
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
private final TokenProvider tokenProvider;
private final CorsFilter corsFilter;
private final JwtAuthenticationEntryPoint authenticationErrorHandler;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
private final ApplicationContext applicationContext;
private final SecurityProperties properties;
private final OnlineUserService onlineUserService;
private final UserCacheManager userCacheManager;
@Bean
GrantedAuthorityDefaults grantedAuthorityDefaults() {
// 去除 ROLE_ 前缀
return new GrantedAuthorityDefaults("");
}
@Bean
public PasswordEncoder passwordEncoder() {
// 密码加密方式
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// 搜寻匿名标记 url: @AnonymousAccess
RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping) applicationContext.getBean("requestMappingHandlerMapping");
Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();
// 获取匿名标记
Map<String, Set<String>> anonymousUrls = getAnonymousUrl(handlerMethodMap);
httpSecurity
// 禁用 CSRF
.csrf().disable()
.addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
// 授权异常
.exceptionHandling()
.authenticationEntryPoint(authenticationErrorHandler)
.accessDeniedHandler(jwtAccessDeniedHandler)
// 防止iframe 造成跨域
.and()
.headers()
.frameOptions()
.disable()
// 不创建会话
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 静态资源等等
.antMatchers(
HttpMethod.GET,
"/*.html",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/webSocket/**"
).permitAll()
// swagger 文档
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/swagger-resources/**").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/*/api-docs").permitAll()
// 文件
.antMatchers("/avatar/**").permitAll()
.antMatchers("/file/**").permitAll()
// 阿里巴巴 druid
.antMatchers("/druid/**").permitAll()
.antMatchers("/druid/**").permitAll()
// 放行OPTIONS请求
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
// 自定义匿名访问所有url放行:允许匿名和带Token访问,细腻化到每个 Request 类型
// GET
.antMatchers(HttpMethod.GET, anonymousUrls.get(RequestMethodEnum.GET.getType()).toArray(new String[0])).permitAll()
// POST
.antMatchers(HttpMethod.POST, anonymousUrls.get(RequestMethodEnum.POST.getType()).toArray(new String[0])).permitAll()
// PUT
.antMatchers(HttpMethod.PUT, anonymousUrls.get(RequestMethodEnum.PUT.getType()).toArray(new String[0])).permitAll()
// PATCH
.antMatchers(HttpMethod.PATCH, anonymousUrls.get(RequestMethodEnum.PATCH.getType()).toArray(new String[0])).permitAll()
// DELETE
.antMatchers(HttpMethod.DELETE, anonymousUrls.get(RequestMethodEnum.DELETE.getType()).toArray(new String[0])).permitAll()
// 所有类型的接口都放行
.antMatchers(anonymousUrls.get(RequestMethodEnum.ALL.getType()).toArray(new String[0])).permitAll()
// 所有请求都需要认证
.anyRequest().authenticated()
.and().apply(securityConfigurerAdapter());
}
private TokenConfigurer securityConfigurerAdapter() {
return new TokenConfigurer(tokenProvider, properties, onlineUserService, userCacheManager);
}
private Map<String, Set<String>> getAnonymousUrl(Map<RequestMappingInfo, HandlerMethod> handlerMethodMap) {
Map<String, Set<String>> anonymousUrls = new HashMap<>(8);
Set<String> get = new HashSet<>();
Set<String> post = new HashSet<>();
Set<String> put = new HashSet<>();
Set<String> patch = new HashSet<>();
Set<String> delete = new HashSet<>();
Set<String> all = new HashSet<>();
for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethodMap.entrySet()) {
HandlerMethod handlerMethod = infoEntry.getValue();
AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class);
if (null != anonymousAccess) {
List<RequestMethod> requestMethods = new ArrayList<>(infoEntry.getKey().getMethodsCondition().getMethods());
RequestMethodEnum request = RequestMethodEnum.find(requestMethods.size() == 0 ? RequestMethodEnum.ALL.getType() : requestMethods.get(0).name());
switch (Objects.requireNonNull(request)) {
case GET:
get.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
case POST:
post.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
case PUT:
put.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
case PATCH:
patch.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
case DELETE:
delete.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
default:
all.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
}
}
}
anonymousUrls.put(RequestMethodEnum.GET.getType(), get);
anonymousUrls.put(RequestMethodEnum.POST.getType(), post);
anonymousUrls.put(RequestMethodEnum.PUT.getType(), put);
anonymousUrls.put(RequestMethodEnum.PATCH.getType(), patch);
anonymousUrls.put(RequestMethodEnum.DELETE.getType(), delete);
anonymousUrls.put(RequestMethodEnum.ALL.getType(), all);
return anonymousUrls;
}
}
接着看到securityConfigurerAdapter方法引用 TokenConfigurer类
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.njry.modules.security.security;
import lombok.RequiredArgsConstructor;
import com.njry.modules.security.config.bean.SecurityProperties;
import com.njry.modules.security.service.OnlineUserService;
import com.njry.modules.security.service.UserCacheManager;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* @author /
*/
@RequiredArgsConstructor
public class TokenConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private final TokenProvider tokenProvider;
private final SecurityProperties properties;
private final OnlineUserService onlineUserService;
private final UserCacheManager userCacheManager;
@Override
public void configure(HttpSecurity http) {
TokenFilter customFilter = new TokenFilter(tokenProvider, properties, onlineUserService, userCacheManager);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}
因为项目有了jwt的 UsernamePasswordAuthenticationToken(账号密码令牌)
Spring Security对账号密码令牌的支持为:UsernamePasswordAuthenticationToken,想使用该令牌进行身份识别需要在SecurityFilterChain中添加UsernamePasswordAuthenticationFilter
TokenConfigurer类 引用了 TokenFilter
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.njry.modules.security.security;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.njry.utils.Hmd5Util;
import io.jsonwebtoken.ExpiredJwtException;
import com.njry.modules.security.config.bean.SecurityProperties;
import com.njry.modules.security.service.UserCacheManager;
import com.njry.modules.security.service.dto.OnlineUserDto;
import com.njry.modules.security.service.OnlineUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
/**
* @author /
*/
public class TokenFilter extends GenericFilterBean {
private static final Logger log = LoggerFactory.getLogger(TokenFilter.class);
private final TokenProvider tokenProvider;
private final SecurityProperties properties;
private final OnlineUserService onlineUserService;
private final UserCacheManager userCacheManager;
/**
* @param tokenProvider Token
* @param properties JWT
* @param onlineUserService 用户在线
* @param userCacheManager 用户缓存工具
*/
public TokenFilter(TokenProvider tokenProvider, SecurityProperties properties, OnlineUserService onlineUserService, UserCacheManager userCacheManager) {
this.properties = properties;
this.onlineUserService = onlineUserService;
this.tokenProvider = tokenProvider;
this.userCacheManager = userCacheManager;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
String token = resolveToken(httpServletRequest);
// log.debug("TOKEN", token);
// 对于 Token 为空的不需要去查 Redis
if (StrUtil.isNotBlank(token)) {
OnlineUserDto onlineUserDto = null;
boolean cleanUserCache = false;
try {
String loginKey = tokenProvider.loginKey(token);
onlineUserDto = onlineUserService.getOne(loginKey);
} catch (ExpiredJwtException e) {
log.error(e.getMessage());
cleanUserCache = true;
} finally {
if (cleanUserCache || Objects.isNull(onlineUserDto)) {
userCacheManager.cleanUserCache(String.valueOf(tokenProvider.getClaims(token).get(TokenProvider.AUTHORITIES_KEY)));
}
}
if (onlineUserDto != null && StringUtils.hasText(token)) {
Authentication authentication = tokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
// Token 续期
tokenProvider.checkRenewal(token);
}
}
filterChain.doFilter(servletRequest, servletResponse);
/* String requestURL = httpServletRequest.getRequestURI();
Set<String> unfilterUrls = properties.getUnFilterUrl();
if(isPass(unfilterUrls,requestURL)){
filterChain.doFilter(servletRequest, servletResponse);
}else{
Map resultMap = chkSign(httpServletRequest,httpServletResponse);
// boolean flag = (boolean)resultMap.get("flag");
boolean flag = true;
RequestWrapper requestWrapper = (RequestWrapper)resultMap.get("requestWrapper");
System.out.println("==============flag======="+(flag?"Y":"N"));
if(!flag){
httpServletResponse.sendError(HttpServletResponse.SC_BAD_REQUEST, "错误的请求");
httpServletResponse.addHeader("SC_BAD_REQUEST","错误的请求");
return;
}
if(requestWrapper==null){
filterChain.doFilter(servletRequest, servletResponse);
}else{
filterChain.doFilter(requestWrapper, servletResponse);
}
}*/
}
/**
* 初步检测Token
*
* @param request /
* @return /
*/
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(properties.getHeader());
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(properties.getTokenStartWith())) {
// 去掉令牌前缀
return bearerToken.replace(properties.getTokenStartWith(), "");
} else {
log.debug("非法Token:{}", bearerToken);
}
return null;
}
/**
* 初步检测Token
*
* @param request /
* @return /
*/
private String resolveHeadParam(HttpServletRequest request,String paramName) {
String param = request.getHeader(paramName);
if (StringUtils.hasText(param) ) {
// 去掉令牌前缀
return param.replace(" ", "");
} else {
log.debug("非法参数:{}", paramName);
}
return null;
}
public Map chkSign(HttpServletRequest request, HttpServletResponse response) throws IOException {
String method = request.getMethod();
String sign = resolveHeadParam(request,"Sign");
String appId = resolveHeadParam(request,"Appid");
String timestamp = resolveHeadParam(request,"Timestamp");
int reqTime = properties.getReqTime();
Map resultMap = new HashMap();
resultMap.put("flag",true);
String ipAddr = com.njry.utils.StringUtils.getIp(request);
if (method.equals("GET")){
Map<String, String[]> paramsMap = request.getParameterMap();
Object[] keyObjs = paramsMap.keySet().toArray();
Arrays.sort(keyObjs);
StringBuffer sb = new StringBuffer();
for(Object paramkey : keyObjs){
if (com.njry.utils.StringUtils.notEmpty(paramkey).equals("sign") ) {
continue;
}
String[] paramsValues = paramsMap.get(paramkey);
if (null == paramsValues) {
paramsValues = new String[]{};
}
sb.append(com.njry.utils.StringUtils.arrayToStringNoTrim(paramsValues, ""));
}
if (appId==null){
resultMap.put("flag",false);
}else {
String key = null;
String secretKey = "xxxx";
if (secretKey==null){
resultMap.put("flag",false);
}
key = Hmd5Util.encryptData(secretKey,appId + sb.toString() + timestamp);
System.out.println("appId + sb.toString() + timestamp====》"+appId + sb.toString() + timestamp);
//需要加密内容
String paramsValue = appId + sb.toString() + timestamp;
//加密获取加密数据
String smallKey = key.toLowerCase();
String smallSign = sign.toLowerCase();
System.out.println("==============smallKey======="+smallKey);
System.out.println("==============smallSign======"+smallSign);
long curTime = System.currentTimeMillis();
System.out.println("==============curTime======"+curTime);
System.out.println("==============timestamp======"+(curTime - Long.parseLong(timestamp)));
System.out.println("==============reqTime======"+reqTime);
if (curTime - Long.parseLong(timestamp) > reqTime) {
System.out.println("==============FFFFF======");
resultMap.put("flag",false);
}
}
}else {
//获取请求参数
RequestWrapper requestWrapper = new RequestWrapper(request);
resultMap.put("requestWrapper",requestWrapper);
String data = requestWrapper.getBodyString();
System.out.println("==========data====》"+data);
if(data==null){
data = "";
}
if (appId==null){
resultMap.put("flag",false);
}else {
String key = null;
String secretKey = "xxxx";
if (secretKey==null){
resultMap.put("flag",false);
}
//加密获取加密数据
key = Hmd5Util.encryptData( secretKey,appId+data+timestamp);
System.out.println("appId + data+ timestamp====》"+appId + data + timestamp);
//需要加密内容
String paramsValue = appId + data + timestamp;
String smallKey = key.toLowerCase();
String smallSign = sign.toLowerCase();
System.out.println("==============smallKey======="+smallKey);
System.out.println("==============smallSign======"+smallSign);
if (!smallKey.equals(smallSign)){
resultMap.put("flag",false);
}
long curTime = System.currentTimeMillis();
if (curTime - Long.parseLong(timestamp) > reqTime){
resultMap.put("flag",false);
}
}
}
return resultMap;
}
public boolean isPass(Set<String> unFilterSet, String requestURI){
logger.info("=====requestURI = "+requestURI);
if(unFilterSet != null && unFilterSet.size() > 0){
for (String unFilterUri : unFilterSet) {
//logger.info("=====unFilterUri = "+unFilterUri);
if(!org.apache.commons.lang.StringUtils.isBlank(unFilterUri)){
unFilterUri = unFilterUri.trim();
if(unFilterUri.equals(requestURI)){
return true;
}else if(unFilterUri.startsWith("*") && unFilterUri.length() > 1 && unFilterUri.endsWith("*")){
String text = unFilterUri.substring(1, (unFilterUri.length() - 1));
//logger.info("=====contains text = " + text);
if(requestURI.contains(text)){
return true;
}
}else if(unFilterUri.startsWith("*") && !unFilterUri.endsWith("*")){
String text = unFilterUri.substring(1, (unFilterUri.length()));
//logger.info("=====endsWith text = " + text);
if(requestURI.endsWith(text)){
return true;
}
}else if(!unFilterUri.startsWith("*") && unFilterUri.endsWith("*")){
String text = unFilterUri.substring(0, (unFilterUri.length() - 1));
//logger.info("=====startsWith text = " + text);
if(requestURI.startsWith(text)){
return true;
}
}
}
}
}
return false;
}
}
这里先找redis里面有没有在线用户和缓存是否存在
下面验签注释了chkSign方法
一堆验证,里面有对appid的解密
package com.njry.utils;
import org.springframework.stereotype.Component;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
@Component
public class Hmd5Util {
public static final String KEY_MAC = "HmacMD5";
//密钥
private static String key = "密钥";
public static String encryptData(String KEY,String content){
try{
SecretKeySpec secretKey = new SecretKeySpec(KEY.getBytes("UTF-8"), KEY_MAC);
Mac mac= Mac.getInstance(KEY_MAC);
mac.init(secretKey);
mac.update(content.getBytes("UTF-8"));
return toHex(mac.doFinal());
}catch (Exception e) {
e.printStackTrace();
}
return "";
}
//转16进制字符串
public static String toHex(byte[] bytes) {
BigInteger bi = new BigInteger(1, bytes);
return String.format("%0" + (bytes.length << 1) + "X", bi);
}
public static void main(String[] args) {
}
}
总结
云里雾去