package com.alatus.config.filter;
import com.alatus.constant.Constants;
import com.alatus.model.TUser;
import com.alatus.result.R;
import com.alatus.service.RedisService;
import com.alatus.util.JSONUtils;
import com.alatus.util.JWTUtils;
import com.alatus.util.ResponseUtils;
import com.alatus.result.CodeEnum;
import jakarta.annotation.Resource;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import static com.alatus.result.CodeEnum.TOKEN_IS_EXPIRED;
@Component
public class TokenVerifyFilter extends OncePerRequestFilter {
@Resource
private RedisService redisService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (request.getRequestURI().equals(Constants.LOGIN_URI)) { //如果是登录请求,此时还没有生成jwt,那不需要对登录请求进行jwt验证
//验证jwt通过了 ,让Filter链继续执行,也就是继续执行下一个Filter
filterChain.doFilter(request, response);
} else {
String token = request.getHeader("Authorization");
System.out.println(token);
if(!StringUtils.hasText("Authorization")){
// 没拿到token,将失败这个枚举传回去,解析并取出常量拼接
R result = R.FAIL(CodeEnum.TOKEN_IS_EMPTY);
// 封装
String resultJSON = JSONUtils.toJSON(result);
// 返回
ResponseUtils.write(response,resultJSON);
return;
}
// 验证token有没有被篡改过,也是验证token合法性
if (!(JWTUtils.verifyJWT(token))){
// token不合法
R result = R.FAIL(CodeEnum.TOKEN_IS_NONE_MATCH);
// 封装
String resultJSON = JSONUtils.toJSON(result);
// 返回
ResponseUtils.write(response,resultJSON);
return;
}
TUser tUser = JWTUtils.parseUserFromJWT(token);
String redisToken = (String) redisService.getValue(Constants.REDIS_JWT_KEY + tUser.getId());
if(!StringUtils.hasText(redisToken)){
// 没有获取到内容说明token过期了
R fail = R.FAIL(TOKEN_IS_EXPIRED.getMsg());
String json = JSONUtils.toJSON(fail);
ResponseUtils.write(response,json);
return;
}
if (!redisToken.equals(token)) {
// 登陆失败token错误
R result = R.FAIL(CodeEnum.TOKEN_IS_ERROR.getMsg());
// 把R对象转为JSON
String json = JSONUtils.toJSON(result);
ResponseUtils.write(response,json);
return;
}
// jwt验证通过了
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(tUser,tUser.getLoginPwd(),tUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
// 验证jwt通过了,让filter链继续执行
filterChain.doFilter(request,response);
}
}
}
package com.alatus.config.filter;
import com.alatus.constant.Constants;
import com.alatus.model.TUser;
import com.alatus.result.R;
import com.alatus.service.RedisService;
import com.alatus.util.JSONUtils;
import com.alatus.util.JWTUtils;
import com.alatus.util.ResponseUtils;
import com.alatus.result.CodeEnum;
import jakarta.annotation.Resource;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import static com.alatus.result.CodeEnum.TOKEN_IS_EXPIRED;
@Component
public class TokenVerifyFilter extends OncePerRequestFilter {
@Resource
private RedisService redisService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (request.getRequestURI().equals(Constants.LOGIN_URI)) { //如果是登录请求,此时还没有生成jwt,那不需要对登录请求进行jwt验证
//验证jwt通过了 ,让Filter链继续执行,也就是继续执行下一个Filter
filterChain.doFilter(request, response);
} else {
String token = request.getHeader("Authorization");
System.out.println(token);
if(!StringUtils.hasText("Authorization")){
// 没拿到token,将失败这个枚举传回去,解析并取出常量拼接
R result = R.FAIL(CodeEnum.TOKEN_IS_EMPTY);
// 封装
String resultJSON = JSONUtils.toJSON(result);
// 返回
ResponseUtils.write(response,resultJSON);
return;
}
// 验证token有没有被篡改过,也是验证token合法性
if (!(JWTUtils.verifyJWT(token))){
// token不合法
R result = R.FAIL(CodeEnum.TOKEN_IS_NONE_MATCH);
// 封装
String resultJSON = JSONUtils.toJSON(result);
// 返回
ResponseUtils.write(response,resultJSON);
return;
}
TUser tUser = JWTUtils.parseUserFromJWT(token);
String redisToken = (String) redisService.getValue(Constants.REDIS_JWT_KEY + tUser.getId());
if(!StringUtils.hasText(redisToken)){
// 没有获取到内容说明token过期了
R fail = R.FAIL(TOKEN_IS_EXPIRED.getMsg());
String json = JSONUtils.toJSON(fail);
ResponseUtils.write(response,json);
return;
}
if (!redisToken.equals(token)) {
// 登陆失败token错误
R result = R.FAIL(CodeEnum.TOKEN_IS_ERROR.getMsg());
// 把R对象转为JSON
String json = JSONUtils.toJSON(result);
ResponseUtils.write(response,json);
return;
}
// jwt验证通过了
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(tUser,tUser.getLoginPwd(),tUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
// 验证jwt通过了,让filter链继续执行
filterChain.doFilter(request,response);
}
}
}
package com.alatus.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig
{
@Bean
public RedisTemplate<String,Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory){
RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(lettuceConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
package com.alatus.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig
{
@Bean
public RedisTemplate<String,Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory){
RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(lettuceConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
package com.alatus.config;
import com.alatus.config.filter.TokenVerifyFilter;
import com.alatus.config.handler.MyAuthenticationFailureHandler;
import com.alatus.config.handler.MyAuthenticationSuccessHandler;
import com.alatus.constant.Constants;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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.SecurityFilterChain;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
@Configuration
public class SecurityConfig {
@Resource
private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
@Resource
private TokenVerifyFilter tokenVerifyFilter;
// 配置加密器
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Resource
private MyAuthenticationFailureHandler myAuthenticationFailureHandler;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity,CorsConfigurationSource configurationSource) throws Exception{
return httpSecurity
.formLogin((formLogin) -> {
formLogin.loginProcessingUrl((Constants.LOGIN_URI))
.usernameParameter("loginAct")
.passwordParameter("loginPwd")
.successHandler(myAuthenticationSuccessHandler)
.failureHandler(myAuthenticationFailureHandler);
})
// SecurityFilterChain改变了默认行为,不再拦截了,需要手动拦截
.authorizeHttpRequests((authorize) -> {
// 对任何请求进行拦截,任何请求都需要登录才可以访问
// /api/login这个请求放开,其他请求正常拦截
authorize.requestMatchers("/api/login").permitAll().anyRequest().authenticated();
})
.csrf((csrf) -> {
//禁用跨站请求伪造
csrf.disable();
})
//支持跨域请求
.cors((cors)->{
cors.configurationSource(configurationSource);
})
.sessionManagement((session) -> {
// 让session的创建策略为不创建
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
})
// 添加我们自定义的filter
.addFilterBefore(tokenVerifyFilter, LogoutFilter.class)
.build();
}
@Bean
public CorsConfigurationSource configurationSource(){
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedOrigins(Arrays.asList("*"));//允许任意来源
corsConfiguration.setAllowedMethods(Arrays.asList("*"));//允许任意方法请求
corsConfiguration.setAllowedHeaders(Arrays.asList("*"));//允许请求头任意内容
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
// 任何路径都按这个来
source.registerCorsConfiguration("/**",corsConfiguration);
return source;
}
}
package com.alatus.config;
import com.alatus.config.filter.TokenVerifyFilter;
import com.alatus.config.handler.MyAuthenticationFailureHandler;
import com.alatus.config.handler.MyAuthenticationSuccessHandler;
import com.alatus.constant.Constants;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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.SecurityFilterChain;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
@Configuration
public class SecurityConfig {
@Resource
private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
@Resource
private TokenVerifyFilter tokenVerifyFilter;
// 配置加密器
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Resource
private MyAuthenticationFailureHandler myAuthenticationFailureHandler;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity,CorsConfigurationSource configurationSource) throws Exception{
return httpSecurity
.formLogin((formLogin) -> {
formLogin.loginProcessingUrl((Constants.LOGIN_URI))
.usernameParameter("loginAct")
.passwordParameter("loginPwd")
.successHandler(myAuthenticationSuccessHandler)
.failureHandler(myAuthenticationFailureHandler);
})
// SecurityFilterChain改变了默认行为,不再拦截了,需要手动拦截
.authorizeHttpRequests((authorize) -> {
// 对任何请求进行拦截,任何请求都需要登录才可以访问
// /api/login这个请求放开,其他请求正常拦截
authorize.requestMatchers("/api/login").permitAll().anyRequest().authenticated();
})
.csrf((csrf) -> {
//禁用跨站请求伪造
csrf.disable();
})
//支持跨域请求
.cors((cors)->{
cors.configurationSource(configurationSource);
})
.sessionManagement((session) -> {
// 让session的创建策略为不创建
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
})
// 添加我们自定义的filter
.addFilterBefore(tokenVerifyFilter, LogoutFilter.class)
.build();
}
@Bean
public CorsConfigurationSource configurationSource(){
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedOrigins(Arrays.asList("*"));//允许任意来源
corsConfiguration.setAllowedMethods(Arrays.asList("*"));//允许任意方法请求
corsConfiguration.setAllowedHeaders(Arrays.asList("*"));//允许请求头任意内容
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
// 任何路径都按这个来
source.registerCorsConfiguration("/**",corsConfiguration);
return source;
}
}
package com.alatus.constant;
public class Constants {
public static final String LOGIN_URI = "/api/login";
// redis的key命名规范,项目名:模块名:功能名:唯一业务参数(比如用户ID)
public static final String REDIS_JWT_KEY = "crmSystem:user:login:";
// 七天时间
public static final Long EXPIRE_TIME = 7 * 24 * 60 * 60L;
// 三十分钟
public static final Long DEFAULT_EXPIRE_TIME = 30 * 60L;
}
package com.alatus.constant;
public class Constants {
public static final String LOGIN_URI = "/api/login";
// redis的key命名规范,项目名:模块名:功能名:唯一业务参数(比如用户ID)
public static final String REDIS_JWT_KEY = "crmSystem:user:login:";
// 七天时间
public static final Long EXPIRE_TIME = 7 * 24 * 60 * 60L;
// 三十分钟
public static final Long DEFAULT_EXPIRE_TIME = 30 * 60L;
}
package com.alatus.result;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum CodeEnum {
OK(200,"成功"),
FAIL(500,"失败"),
TOKEN_IS_EMPTY(901,"请求Token参数为空"),
TOKEN_IS_EXPIRED(902,"Token已过期"),
TOKEN_IS_ERROR(903,"Token有误"),
TOKEN_IS_NONE_MATCH(904,"Token信息不合法");
// 结果码
private int code;
// 结果信息
private String msg;
}
package com.alatus.result;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum CodeEnum {
OK(200,"成功"),
FAIL(500,"失败"),
TOKEN_IS_EMPTY(901,"请求Token参数为空"),
TOKEN_IS_EXPIRED(902,"Token已过期"),
TOKEN_IS_ERROR(903,"Token有误"),
TOKEN_IS_NONE_MATCH(904,"Token信息不合法");
// 结果码
private int code;
// 结果信息
private String msg;
}
package com.alatus.service;
import java.util.concurrent.TimeUnit;
public interface RedisService {
void setValue(String key,Object value);
Object getValue(String key);
Boolean removeValue(String key);
Boolean expire(String key, Long timeOut, TimeUnit timeUnit);
}
package com.alatus.service;
import java.util.concurrent.TimeUnit;
public interface RedisService {
void setValue(String key,Object value);
Object getValue(String key);
Boolean removeValue(String key);
Boolean expire(String key, Long timeOut, TimeUnit timeUnit);
}
package com.alatus.service.impl;
import com.alatus.service.RedisService;
import jakarta.annotation.Resource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class RedisServiceImpl implements RedisService {
// 数据源
@Resource
private RedisTemplate<String,Object> redisTemplate;
// 放入数据
@Override
public void setValue(String key, Object value) {
redisTemplate.opsForValue().set(key,value);
}
//取出数据
@Override
public Object getValue(String key) {
return redisTemplate.opsForValue().get(key);
}
//删除数据
@Override
public Boolean removeValue(String key) {
return redisTemplate.delete(key);
}
@Override
public Boolean expire(String key, Long timeOut, TimeUnit timeUnit) {
return redisTemplate.expire(key,timeOut,timeUnit);
}
}
package com.alatus.service.impl;
import com.alatus.service.RedisService;
import jakarta.annotation.Resource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class RedisServiceImpl implements RedisService {
// 数据源
@Resource
private RedisTemplate<String,Object> redisTemplate;
// 放入数据
@Override
public void setValue(String key, Object value) {
redisTemplate.opsForValue().set(key,value);
}
//取出数据
@Override
public Object getValue(String key) {
return redisTemplate.opsForValue().get(key);
}
//删除数据
@Override
public Boolean removeValue(String key) {
return redisTemplate.delete(key);
}
@Override
public Boolean expire(String key, Long timeOut, TimeUnit timeUnit) {
return redisTemplate.expire(key,timeOut,timeUnit);
}
}
---
spring:
data:
redis:
lettuce:
pool:
max-idle: 8
min-idle: 0
max-wait: -1ms
max-active: 8
cluster:
refresh:
adaptive: true
period: 2000
cluster:
max-redirects: 3
nodes: 192.168.189.128:6381,192.168.189.128:6382,192.168.189.130:6383,192.168.189.130:6384,192.168.189.129:6385,192.168.189.129:6386
password: abc123
timeout: 5000
---
server:
port: 8089
servlet:
context-path: /
session:
persistent: false
---
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/dlyk?useUnicode=true&characterEncoding=utf-8&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: abc123
hikari:
maximum-pool-size: 30
minimum-idle: 30
connection-timeout: 5000
idle-timeout: 0
max-lifetime: 18000000
---
mybatis:
mapper-locations: classpath:mapper/*.xml
---
spring:
data:
redis:
lettuce:
pool:
max-idle: 8
min-idle: 0
max-wait: -1ms
max-active: 8
cluster:
refresh:
adaptive: true
period: 2000
cluster:
max-redirects: 3
nodes: 192.168.189.128:6381,192.168.189.128:6382,192.168.189.130:6383,192.168.189.130:6384,192.168.189.129:6385,192.168.189.129:6386
password: abc123
timeout: 5000
---
server:
port: 8089
servlet:
context-path: /
session:
persistent: false
---
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/dlyk?useUnicode=true&characterEncoding=utf-8&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: abc123
hikari:
maximum-pool-size: 30
minimum-idle: 30
connection-timeout: 5000
idle-timeout: 0
max-lifetime: 18000000
---
mybatis:
mapper-locations: classpath:mapper/*.xml