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
redisTemplate(LettuceConnectionFactory lettuceConnectionFactory){ RedisTemplate 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 RedisTemplateredisTemplate(LettuceConnectionFactory lettuceConnectionFactory){ RedisTemplate 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
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 RedisTemplateredisTemplate; // 放入数据 @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