①、准备工具类AuthContextUtil创建ThreadLocal
public class AuthContextUtil{
private static final ThreadLocal<SysUser> threadLocal = new ThreadLocal<>();
public static void set(SysUser sysUser){
threadLocal.set(sysUser);
}
public static SysUser get(){
return threadLocal.get();
}
public static void remove(){
threadLocal.remove();
}
}
②、创建拦截器
@Component
public class LoginAuthInterceptor implements HandlerInterceptor{
@Autowired
private RedisTemplate<String,String> redisTemplate;
/**
表示方法之前执行
*/
@Override
public boolean preHandle(HttpServletRequest request,HttpServletResponse response,Object handler)
throws Exception{
String method = request.getMethod();
if("OPTIONS".equals(method)){ //如果是跨域预检请求,直接放行
return true;
}
//获取token
String token = request.getHeader("token");
if(StrUtil.isEmpty(token)){
responseNoLoginInfo(response);
return false;
}
//如果token不为空,此时验证token的合法性
String sysUserInfoJson = redisTemplate.opsForValue().get("user:ogin:"+token);
if(strUtil.isEmpty(sysUserInfoJson)){
responseNoLoginInfo(response);
return false;
}
//将用户数据存储到ThreadLocal中
SysUser sysUser = JSON.parseObject(sysUserInfoJson,SysUser.class);
AuthContextUtil.set(sysUser);
//重置redis中的有效时间
redisTemplate.expire("user:login:" + token,30,TimeUnit.MINUTES);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,HttpServletResponse response,Object handler,Exception ex){
AuthContextUtil.remove();
}
/**
响应208状态码给前端
*/
private void responseNoLoginInfo(HttpServietResponse response){
Result<Object> result = Result.build(null,ResultCodeEnum.LOGIN_AUTH);
PrintWriter writer = null;
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=utf8");
try{
writer = response.getWriter();
writer.print(JSON.toJSONString(result));
}catch(IOException e){
e.printStackTrace();
}finally{
if(writer!=null){
writer.close();
}
}
}
}
③、拦截器注册
@Component
public class WebMvcConfiguration implements WebMvcConfigurer{
// 注入拦截器
@Autowired
private LoginAuthInterceptor loginAuthInterceptor;
/**
拦截器注册
对于以下多路径的筛选,可以通过配置文件的方式
*/
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addIntercepor(loginAutInterceptor)
.excludePathPatterns("/admin/system/index/login","/admin/system/idenx/generateValidateCode")
.addPathPatterns("/**");
}
/**
addCorsMappings方法解决跨域问题
*/
@Override
public void addCorsMappings(CorsRegistry registry){
registry.addMapping("/**") //添加路径规则
.allowCredentials(true) //是否允许在跨域的情况下传递Cookie
.allowedOriginPatterns("*") //允许请求来源的域规则
.allowedMethods("*")
.allowedHeaders("*");
}
}
登录校验的优化
将多路径的筛选,放置到配置配置文件
①、application-dev.yml
spzx:
auth:
noAuthUrls:
- /admin/system/index/login
- /admin/system/index/generateValidateCode
②、实体类定义
@Data
@ConfigurationProperties(prefix="spzx.auth")
public class UserAuthProperties{
private List<String> noAuthUrls;
}
③、启动类添加注解:@EnableConfigurationProperties(value={UserAuthProperties.class})
④、注册拦截器代码的修改
@Autowired
private UserProperties userProperties;
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(loginAuthInterceptor)
.excludePathPatterns(userProperties.getNoAuthUrls())//优化后的代码
.addPathPatterns("/**");
}
==================================================================
配置类
@Configuration
public class WebMvcConfig implements WebMvcConfigurer{
@Override
public void addInterceptors(InterceptorRegistry registry){//org.springframework:spring-webmvc:5.3.24
registry.addInterceptor(new MyLoginInterceptor());
}
}
拦截器
//登录拦截器
@Slf4j
public class MyLoginIntercpetor implements HandlerInterceptor{
private static final String LOGIN_PATH = "user/login";
private static Map<String,AtomicInteger> visitCount;
private static final QQWry;
static{
visitCount = new HashMap<>(31);
qqWry = new QQWry();
}
//展示访问数量不是精确指标,如果要做到完全正确需要使用锁,防止计数存在并发问题
@Override
public boolean preHandler(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception{
log.info("【MyLoginInterceptor】调用了:{}", request.getRequestURI());
if (request.getRequestURI().equals(LOGIN_PATH)) {
String ipAddress = IPUtil.getIpAddress(request);
String province = qqWry.findIP(ipAddress).getMainInfo();
if (visitCount.containsKey(province)) {
visitCount.put(province,new AtomicInteger(visitCount.get(province).incrementAndGet()));
} else {
visitCount.put(province,new AtomicInteger());
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex){}
}
IPUtil工具类
public class IPUtil {
public static String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
QQWry
public class QQWry {
private static class QIndex {
public final long minIP;
public final long maxIP;
public final int recordOffset;
public QIndex(final long minIP, final long maxIP, final int recordOffset) {
this.minIP = minIP;
this.maxIP = maxIP;
this.recordOffset = recordOffset;
}
}
private static class QString {
public final String string;
/** length including the \0 end byte */
public final int length;
public QString(final String string, final int length) {
this.string = string;
this.length = length;
}
}
private static final int INDEX_RECORD_LENGTH = 7;
private static final byte REDIRECT_MODE_1 = 0x01;
private static final byte REDIRECT_MODE_2 = 0x02;
private static final byte STRING_END = '\0';
private final byte[] data;
private final long indexHead;
private final long indexTail;
/**
* Create QQWry by loading qqwry.dat from classpath.
*
* @throws IOException
* if encounter error while reading qqwry.dat
*/
public QQWry() {
InputStream in = QQWry.class.getClassLoader().getResourceAsStream("qqwry.dat");
ByteArrayOutputStream out = new ByteArrayOutputStream(10 * 1024 * 1024); // 10MB
byte[] buffer = new byte[4096];
while (true) {
int r = 0;
try {
r = in.read(buffer);
} catch (IOException e) {
e.printStackTrace();
}
if (r == -1) {
break;
}
out.write(buffer, 0, r);
}
data = out.toByteArray();
indexHead = readLong32(0);
indexTail = readLong32(4);
}
public IPZone findIP(final String ip) {
final long ipNum = toNumericIP(ip);
final QIndex idx = searchIndex(ipNum);
if (idx == null) {
return new IPZone(ip);
}
return readIP(ip, idx);
}
private long getMiddleOffset(final long begin, final long end) {
long records = (end - begin) / INDEX_RECORD_LENGTH;
records >>= 1;
if (records == 0) {
records = 1;
}
return begin + (records * INDEX_RECORD_LENGTH);
}
private QIndex readIndex(final int offset) {
final long min = readLong32(offset);
final int record = readInt24(offset + 4);
final long max = readLong32(record);
return new QIndex(min, max, record);
}
private int readInt24(final int offset) {
int v = data[offset] & 0xFF;
v |= ((data[offset + 1] << 8) & 0xFF00);
v |= ((data[offset + 2] << 16) & 0xFF0000);
return v;
}
private IPZone readIP(final String ip, final QIndex idx) {
final int pos = idx.recordOffset + 4; // skip ip
final byte mode = data[pos];
final IPZone z = new IPZone(ip);
if (mode == REDIRECT_MODE_1) {
final int offset = readInt24(pos + 1);
if (data[offset] == REDIRECT_MODE_2) {
readMode2(z, offset);
} else {
final QString mainInfo = readString(offset);
final String subInfo = readSubInfo(offset + mainInfo.length);
z.setMainInfo(mainInfo.string);
z.setSubInfo(subInfo);
}
} else if (mode == REDIRECT_MODE_2) {
readMode2(z, pos);
} else {
final QString mainInfo = readString(pos);
final String subInfo = readSubInfo(pos + mainInfo.length);
z.setMainInfo(mainInfo.string);
z.setSubInfo(subInfo);
}
return z;
}
private long readLong32(final int offset) {
long v = data[offset] & 0xFFL;
v |= (data[offset + 1] << 8L) & 0xFF00L;
v |= ((data[offset + 2] << 16L) & 0xFF0000L);
v |= ((data[offset + 3] << 24L) & 0xFF000000L);
return v;
}
private void readMode2(final IPZone z, final int offset) {
final int mainInfoOffset = readInt24(offset + 1);
final String main = readString(mainInfoOffset).string;
final String sub = readSubInfo(offset + 4);
z.setMainInfo(main);
z.setSubInfo(sub);
}
private QString readString(final int offset) {
int i = 0;
final byte[] buf = new byte[128];
for (;; i++) {
final byte b = data[offset + i];
if (STRING_END == b) {
break;
}
buf[i] = b;
}
try {
return new QString(new String(buf, 0, i, "GB18030"), i + 1);
} catch (final UnsupportedEncodingException e) {
return new QString("", 0);
}
}
private String readSubInfo(final int offset) {
final byte b = data[offset];
if ((b == REDIRECT_MODE_1) || (b == REDIRECT_MODE_2)) {
final int areaOffset = readInt24(offset + 1);
if (areaOffset == 0) {
return "";
} else {
return readString(areaOffset).string;
}
} else {
return readString(offset).string;
}
}
private QIndex searchIndex(long ip) {
long head = indexHead;
long tail = indexTail;
while (tail > head) {
final long cur = getMiddleOffset(head, tail);
final QIndex idx = readIndex((int) cur);
if ((ip >= idx.minIP) && (ip <= idx.maxIP)) {
return idx;
}
if ((cur == head) || (cur == tail)) {
return idx;
}
if (ip < idx.minIP) {
tail = cur;
} else if (ip > idx.maxIP) {
head = cur;
} else {
return idx;
}
}
return null;
}
/**
* ip从字符串转为long类型
* @param str
* @return res
*/
private long toNumericIP(String str) {
String[] splits = str.split("\\.");
if (4 != splits.length) {
throw new IllegalArgumentException("ip格式异常,请检查后重试");
}
long res = Long.parseLong(splits[0]) << 24L;
res += Long.parseLong(splits[1]) << 16L;
res += Long.parseLong(splits[2]) << 8L;
res += Long.parseLong(splits[3]);
return res;
}
}
IPZone
public class IPZone {
private final String ip;
private String mainInfo = "";
private String subInfo = "";
public IPZone(final String ip) {
this.ip = ip;
}
public String getIp() {
return ip;
}
public String getMainInfo() {
if (mainInfo.contains("省")) {
return mainInfo.substring(0,mainInfo.indexOf("省"));
}
if (mainInfo.contains("市")) {
return mainInfo.substring(0,mainInfo.indexOf("市"));
}
return mainInfo;
}
public String getSubInfo() {
return subInfo;
}
public void setMainInfo(final String info) {
this.mainInfo = info;
}
public void setSubInfo(final String info) {
this.subInfo = info;
}
@Override
public String toString() {
return new StringBuilder(mainInfo).append(subInfo).toString();
}
}