当前位置: 首页 > news >正文

Spring Security + Jwt 集成实现登录

文章目录

  • 前言
  • Maven 相关依赖
  • 配置文件
  • 自定义springsecurity相关认证流程
    • 继承WebSecurityConfigurerAdapter
    • 继承AbstractAuthenticationToken
    • 继承AbstractAuthenticationProcessingFilter
    • 实现AuthenticationProvider
    • 实现UserDetailsService
    • 实现AccessDeniedHandler
    • 实现AuthenticationEntryPoint
    • 实现AuthenticationFailureHandler
    • 实现AuthenticationFailureHandler
    • 实现AuthenticationSuccessHandler
    • 实现AuthenticationSuccessHandler
    • 继承OncePerRequestFilter
  • 总结


前言

主要实现了如下登录方式:

  1. 用户名+密码+验证码登录
  2. 邮箱+验证码登录


Maven 相关依赖

主要引入了如下依赖:

  1. spring-boot-starter-security依赖
    1. 为了解决警告排除了kotlin相关的三个依赖包
  2. jjwt依赖
        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId><!--解决警告: For Jackson Kotlin classes support please add "com.fasterxml.jackson.module:jackson-module-kotlin" to the classpath--><exclusions><exclusion><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-stdlib-jdk8</artifactId></exclusion><exclusion><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-stdlib</artifactId></exclusion><exclusion><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-reflect</artifactId></exclusion></exclusions></dependency>
        <dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId></dependency>

配置文件

主要配置如下:

  1. 配置了jwt生成token秘钥
  2. 配置了jwt的token过期时间,单位为毫秒
  3. 配置了header中携带token的key值
 jwt: #jwt参数配置secret: secretexpiration: 14400000authorization: Authorization

自定义springsecurity相关认证流程

继承WebSecurityConfigurerAdapter

主要做了如下操作:

  1. 重写了安全框架全局配置configureGlobal(AuthenticationManagerBuilder auth)方法
    1. 指定了密码加密规则
    2. 关闭了隐藏用户找不到异常功能
  2. 放开了http防火墙,不对非法json请求拦截
  3. 重写了http安全配置configure(HttpSecurity http)方法
    1. 禁用csr攻击保护
    2. 禁用form表单登录
    3. 禁用页面缓存
    4. 自定义异常拦截处理
      1. 身份验证失败MyAuthenticationEntryPoint()异常拦截器
      2. 访问被拒绝MyAccessDeniedHandler()异常拦截器
    5. 会话创建策略无状态(不使用session)
    6. 授权请求配置
      1. 接口请求
    7. UsernamePasswordAuthenticationFilter()拦截器之前添加令牌验证过滤器
    8. 登出成功MyLogoutSuccessHandler()拦截器
    9. 添加自定义身份验证适配器MyAuthenticationConfigurer()
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate MyAuthenticationEntryPoint authenticationEntryPoint;@Autowiredprivate MyAccessDeniedHandler accessDeniedHandler;@Autowiredprivate MyLogoutSuccessHandler logoutSuccessHandler;@Autowiredprivate JwtAuthorizationTokenFilter jwtAuthorizationTokenFilter;/*** 全局配置** @param auth 身份验证管理器生成器*/@SneakyThrows@Autowiredpublic void configureGlobal(AuthenticationManagerBuilder auth) {DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();//自定义servicedaoAuthenticationProvider.setUserDetailsService(super.userDetailsService());//配置密码生成规则daoAuthenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder());//不隐藏异常daoAuthenticationProvider.setHideUserNotFoundExceptions(false);auth.authenticationProvider(daoAuthenticationProvider);}/*** http防火墙,放开非法json请求** @return HttpFirewall*/@Beanpublic HttpFirewall httpFirewall() {return new DefaultHttpFirewall();}/*** http安全配置** @param http the {@link HttpSecurity} to modify*/@SneakyThrows@Overrideprotected void configure(HttpSecurity http) {http//禁用csr攻击保护.csrf().disable()// 禁用form表单登录.formLogin().disable()// 禁用页面缓存.headers().cacheControl().disable().and()//异常处理.exceptionHandling()//身份验证失败.authenticationEntryPoint(authenticationEntryPoint)//访问被拒绝的处理程序.accessDeniedHandler(accessDeniedHandler).and()//会话创建策略无状态(不使用session).sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()//授权请求.authorizeRequests()//允许所有.antMatchers(HttpMethod.OPTIONS, HttpPermitAll.getHttpOptionPermits()).permitAll().antMatchers(HttpMethod.POST, HttpPermitAll.getHttpPostPermits()).permitAll().antMatchers(HttpMethod.PUT, HttpPermitAll.getHttpPutPermits()).permitAll().antMatchers(HttpMethod.GET, HttpPermitAll.getHttpGetPermits()).permitAll().antMatchers(WebPermitAll.getWebPermits()).permitAll()//除了上面配置的请求白名单,任何请求都需要验证.anyRequest().authenticated().and()//添加令牌验证过滤器.addFilterAfter(jwtAuthorizationTokenFilter, UsernamePasswordAuthenticationFilter.class)//登出成功过滤器.logout().logoutSuccessHandler(logoutSuccessHandler)//添加自定义身份验证适配器.and().apply(new MyAuthenticationConfigurer());}}

继承AbstractAuthenticationToken

主要做了如下操作:

  1. 重新定义了principal概念
  2. 增加了验证码字段code
  3. 增加了验证码key字段uuid
  4. 登录类型type
  5. 自定义带参的构造函数
  6. 新增的参数对应的Getter和Setter方法
public class MyAuthenticationToken extends AbstractAuthenticationToken {/*** 用户名/邮箱*/private final Object principal;/*** 密码*/private Object credentials;/*** 验证码*/private String code;/*** 验证码id*/private String uuid;/*** 登录类型*/private String type;public MyAuthenticationToken(Object principal,Object credentials,String code,String uuid,String type) {super(null);this.principal = principal;this.credentials = credentials;this.code = code;this.uuid = uuid;this.type = type;setAuthenticated(false);}public MyAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities, Object credentials) {super(authorities);this.principal = principal;this.credentials = credentials;super.setAuthenticated(true);}public String getCode() {return code;}public void setCode(String code) {this.code = code;}public String getUuid() {return uuid;}public String getType() {return type;}public void setType(String type) {this.type = type;}@Overridepublic Object getCredentials() {return this.credentials;}@Overridepublic Object getPrincipal() {return this.principal;}public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {if (isAuthenticated) {throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");}super.setAuthenticated(false);}@Overridepublic void eraseCredentials() {super.eraseCredentials();credentials = null;}
}

继承AbstractAuthenticationProcessingFilter

主要做了如下操作:

  1. 从HttpServletRequest获取到相关参数
  2. 整理验证参数实体Authentication并移交给验证器AuthenticationManager
public class MyAuthenticationFilter extends AbstractAuthenticationProcessingFilter {public MyAuthenticationFilter(RequestMatcher matcher, AuthenticationManager localAuthManager) {super(matcher, localAuthManager);}@SneakyThrows@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {String username = request.getParameter(Constant.USERNAME);String password = request.getParameter(Constant.PASSWORD);String code = request.getParameter(Constant.CODE);String uuid = request.getParameter(Constant.UUID);String type = request.getParameter(Constant.TYPE);//整理验证信息并移交给验证器Authentication authentication = new MyAuthenticationToken(username, password, code, uuid, type);return this.getAuthenticationManager().authenticate(authentication);}
}

实现AuthenticationProvider

主要做了如下操作:

  1. 实现了认证方法authenticate(Authentication authentication)
    1. 通过Authentication获取登录相关信息
    2. 验证登录类型是否为空
    3. 如果是用户名密码验证码登录则执行defaultLogin()
    4. 如果是邮箱验证码登录则执行emailLogin()函数
    5. 调用loadUserByUsername()函数,校验用户是否有效
    6. 整理用户信息,回填验证信息到上下文中
  2. 创建了用户名密码验证码登录私有化函数defaultLogin()
    1. 通过uuid获取缓存中验证码
    2. 验证输入uuid和输入的验证码是否为空
    3. 验证缓存中是否存在uuid对应的验证码
    4. 验证输入的验证码和缓存中的验证码是否一致
    5. 通过用户名称获取用户信息
    6. 验证用户是否存在
    7. 验证密码是否正确
    8. 返回用户名称或者对应的错误代码
  3. 创建了邮箱-验证码登录私有化函数emailLogin()
    1. 通过uuid获取缓存中验证码
    2. 验证输入uuid和输入的验证码是否为空
    3. 验证缓存中是否存在uuid对应的验证码
    4. 验证输入的验证码和缓存中的验证码是否一致
    5. 通过邮箱获取用户信息
    6. 验证用户是否存在
    7. 返回用户名称或者对应的错误代码
  4. 实现了supports(Class<?> authentication)方法
    1. 指定自定义身份验证方法MyAuthenticationProvider使用自定义的MyAuthenticationToken实体类
public class MyAuthenticationProvider implements AuthenticationProvider {private final RedisUtil redisUtil;private final IUserService userService;private final IUserInfoService userInfoService;public MyAuthenticationProvider(RedisUtil redisUtil,IUserService userService,IUserInfoService userInfoService) {this.redisUtil = redisUtil;this.userService = userService;this.userInfoService = userInfoService;}@SneakyThrows@Overridepublic Authentication authenticate(Authentication authentication) {MyAuthenticationToken authenticationToken = (MyAuthenticationToken) authentication;String username = (String) authenticationToken.getPrincipal();String credentials = (String) authenticationToken.getCredentials();String code = authenticationToken.getCode();String uuid = authenticationToken.getUuid();String type = authenticationToken.getType();//验证类型为空if (StringUtils.isBlank(type)){username = MyCode.AUTHENTICATION_TYPE_EMPTY;}//用户名密码验证码登录if (type.equals(AuthTypeEnum.USERNAME_PASSWORD.getCode())) {username = defaultLogin(username, credentials, code, uuid);}else//邮箱验证码登录if (type.equals(AuthTypeEnum.EMAIL_CODE.getCode())) {username = emailLogin(username, code, uuid);}else {//验证类型无效username = MyCode.AUTHENTICATION_TYPE_INVALID;}//校验用户并返回用户信息UserDetails userDetails = userService.loadUserByUsername(username);//回填验证信息到上下文中MyAuthenticationToken myAuthenticationToken =new MyAuthenticationToken(userDetails,userDetails.getAuthorities(), authenticationToken.getCredentials());myAuthenticationToken.setDetails(authenticationToken);return myAuthenticationToken;}/*** 用户名密码验证码登录* @param username 用户名* @param credentials 密码* @param code 验证码* @param uuid 随机数* @return 用户名称或者状态码*/private String defaultLogin(String username, String credentials, String code, String uuid) {String accountCode = redisUtil.get(RedisConstant.USER_VERIFICATION_CODE.concat(CommonConstant.COLON).concat(uuid));if (StringUtils.isBlank(code) || StringUtils.isBlank(uuid)) {username = MyCode.VERIFICATION_CODE_EMPTY;}else if (accountCode == null){username = MyCode.VERIFICATION_CODE_FAIL;}else if (!Objects.equals(accountCode, code)) {username = MyCode.VERIFICATION_CODE_ERROR;}else {QueryWrapper<UserInfo> userInfoQueryWrapper = new QueryWrapper<>();userInfoQueryWrapper.eq(UserInfo.USERNAME, username);UserInfo userInfo = userInfoService.getOne(userInfoQueryWrapper);if (userInfo == null){username = MyCode.USER_NOT_FOUND;}else if (!new BCryptPasswordEncoder().matches(credentials, userInfo.getPwd())) {username = MyCode.PASSWORD_ERROR;}else {username = userInfo.getUsername();}}return username;}/*** 邮箱验证码登录* @param username 邮箱* @param code 验证码* @param uuid 随机数* @return 用户名称或者状态码*/private String emailLogin(String username, String code, String uuid) {String accountCode = redisUtil.get(RedisConstant.EMAIL_VERIFICATION_CODE.concat(CommonConstant.COLON).concat(uuid));if (StringUtils.isBlank(code) || StringUtils.isBlank(uuid)) {username = MyCode.VERIFICATION_CODE_EMPTY;}else if (accountCode == null){username = MyCode.VERIFICATION_CODE_FAIL;} else if (!accountCode.equals(code)) {username = MyCode.VERIFICATION_CODE_ERROR;}else {QueryWrapper<UserInfo> userInfoQueryWrapper = new QueryWrapper<>();userInfoQueryWrapper.eq(UserInfo.EMAIL, username);UserInfo userInfo = userInfoService.getOne(userInfoQueryWrapper);if (userInfo == null){username = MyCode.USER_NOT_FOUND;}else {username = userInfo.getUsername();}}return username;}@Overridepublic boolean supports(Class<?> authentication) {return MyAuthenticationToken.class.isAssignableFrom(authentication);}
}

实现UserDetailsService

主要做了如下操作:

  1. 实现了loadUserByUsername(String username)方法
    1. 对输入的username(错误代码或者是用户名)进行匹配
      1. 验证码为空
      2. 验证码失效
      3. 验证码错误
      4. 用户不存在
      5. 密码错误
      6. 获取缓存中的用户信息
      7. 校验缓存中是否存在用户信息
      8. 不存在则通过用户名称获取用户信息
      9. 封装为LoginUserVo()实体
      10. 验证用户名密码错误
      11. 验证用户过期
      12. 验证用户锁定
      13. 验证用户凭证过期
      14. 验证用户禁用
      15. 返回用户信息UserDetails()
@Service
public class UserServiceImpl implements IUserService, UserDetailsService {@Autowiredprivate RedisUtil redisUtil;@Autowiredprivate IUserInfoService userInfoService;@Overridepublic UserDetails loadUserByUsername(String username) {// 验证码为空if (username.equals(MyCode.VERIFICATION_CODE_EMPTY)){throw new UsernameNotFoundException(I18nUtils.getMessage(LoginConstant.VERIFICATION_CODE_EMPTY));}// 验证码失效if (username.equals(MyCode.VERIFICATION_CODE_FAIL)){throw new UsernameNotFoundException(I18nUtils.getMessage(LoginConstant.VERIFICATION_CODE_FAIL));}// 验证码错误if (username.equals(MyCode.VERIFICATION_CODE_ERROR)){throw new UsernameNotFoundException(I18nUtils.getMessage(LoginConstant.VERIFICATION_CODE_ERROR));}// 用户不存在if (username.equals(MyCode.USER_NOT_FOUND)){throw new UsernameNotFoundException(I18nUtils.getMessage(LoginConstant.BADCREDENTIALS_EXCEPTION));}// 密码错误if (username.equals(MyCode.PASSWORD_ERROR)){throw new UsernameNotFoundException(I18nUtils.getMessage(LoginConstant.PASSWORD_ERROR));}String userInfoStr = redisUtil.get(RedisConstant.USER.concat(CommonConstant.COLON).concat(username));LoginUserVo loginUserVo = null;if (userInfoStr != null) {loginUserVo = JSON.parseObject(userInfoStr, LoginUserVo.class);}else {QueryWrapper<UserInfo> userInfoQueryWrapper = new QueryWrapper<>();userInfoQueryWrapper.eq(UserInfo.USERNAME, username);UserInfo userInfo = userInfoService.getOne(userInfoQueryWrapper);if (userInfo != null) {loginUserVo = new LoginUserVo();BeanUtils.copyProperties(userInfo, loginUserVo);}}//用户名或密码错误if (loginUserVo == null) {throw new UsernameNotFoundException(I18nUtils.getMessage(LoginConstant.BADCREDENTIALS_EXCEPTION));}//用户过期if (loginUserVo.getIsExpired()) {throw new AccountExpiredException(I18nUtils.getMessage(LoginConstant.ACCOUNTEXPIRED_EXCEPTION));}//用户锁定if (loginUserVo.getIsLocked()) {throw new LockedException(I18nUtils.getMessage(LoginConstant.LOCKED_EXCEPTION));}//用户凭证过期if (loginUserVo.getIsCredentialsExpired()) {throw new CredentialsExpiredException(I18nUtils.getMessage(LoginConstant.CREDENTIALSEXPIRED_EXCEPTION));}//用户已禁用过期if (loginUserVo.getIsDisable()) {throw new DisabledException(I18nUtils.getMessage(LoginConstant.DISABLED_EXCEPTION));}return loginUserVo;}
}

实现AccessDeniedHandler

主要做了如下操作:

  1. 以文本输出流的形式返回给客户端相关信息(加持了中英文)
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {@SneakyThrows@Overridepublic void handle(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse, AccessDeniedException e) {httpServletResponse.setContentType(Constant.APPLICATION_JSON_UTF8_VALUE);httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);PrintWriter out = httpServletResponse.getWriter();out.write(JSON.toJSONString(RespJson.error(HttpServletResponse.SC_FORBIDDEN,I18nUtils.getMessage(LoginConstant.FORBIDDEN))));}
}

实现AuthenticationEntryPoint

主要做了如下操作:

  1. 以文本输出流的形式返回给客户端相关信息(加持了中英文)
@Component
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {@SneakyThrows@Overridepublic void commence(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse, AuthenticationException e) {httpServletResponse.setContentType(Constant.APPLICATION_JSON_UTF8_VALUE);httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);PrintWriter out = httpServletResponse.getWriter();out.write(JSON.toJSONString(RespJson.error(HttpServletResponse.SC_UNAUTHORIZED,I18nUtils.getMessage(LoginConstant.UNAUTHORIZED))));}
}

实现AuthenticationFailureHandler

主要做了如下操作:

  1. 以文本输出流的形式返回给客户端相关信息(加持了中英文)
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {@SneakyThrows@Overridepublic void onAuthenticationFailure(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse, AuthenticationException e) {httpServletResponse.setContentType(Constant.APPLICATION_JSON_UTF8_VALUE);httpServletResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);PrintWriter out = httpServletResponse.getWriter();if (e instanceof BadCredentialsException) {out.write(JSON.toJSONString(RespJson.error(I18nUtils.getMessage(LoginConstant.PASSWORD_ERROR))));} else {out.write(JSON.toJSONString(RespJson.error(e.getMessage())));}}
}

实现AuthenticationFailureHandler

主要做了如下操作:

  1. 以文本输出流的形式返回给客户端相关信息(加持了中英文)
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {@SneakyThrows@Overridepublic void onAuthenticationFailure(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse, AuthenticationException e) {httpServletResponse.setContentType(Constant.APPLICATION_JSON_UTF8_VALUE);httpServletResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);PrintWriter out = httpServletResponse.getWriter();if (e instanceof BadCredentialsException) {out.write(JSON.toJSONString(RespJson.error(I18nUtils.getMessage(LoginConstant.PASSWORD_ERROR))));} else {out.write(JSON.toJSONString(RespJson.error(e.getMessage())));}}
}

实现AuthenticationSuccessHandler

主要做了如下操作:

  1. 根据上下文中用户信息生成token
  2. 以文本输出流的形式返回给客户端相关信息(加持了中英文)
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {private final IUserService userService;public MyAuthenticationSuccessHandler(IUserService userService) {this.userService = userService;}@SneakyThrows@Overridepublic void onAuthenticationSuccess(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,Authentication authentication) {String token = userService.getToken((LoginUserVo) authentication.getPrincipal());httpServletResponse.setContentType(Constant.APPLICATION_JSON_UTF8_VALUE);httpServletResponse.setStatus(HttpServletResponse.SC_OK);PrintWriter out = httpServletResponse.getWriter();out.write(JSON.toJSONString(RespJson.success(I18nUtils.getMessage(LoginConstant.LOGIN_SUCCESS), token)));}
}

实现AuthenticationSuccessHandler

主要做了如下操作:

  1. HttpServletRequest中获取token
  2. 解析token拿到用户名称
  3. 根据用户名称删除缓存中的用户信息
  4. 根据用户名称删除缓存中的权限信息
  5. 根据用户名称删除缓存中的用户获取token剩余过期时间
  6. 将未过期token加入到黑名单中,过期时间为剩余过期时间
  7. 以文本输出流的形式返回给客户端相关信息(加持了中英文)
@Component
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {@Autowiredprivate RedisUtil redisUtil;@Value("${jwt.authorization}")private String authorization;@Autowiredprivate JwtTokenUtils jwtTokenUtils;@SneakyThrows@Overridepublic void onLogoutSuccess(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,Authentication authentication) {//获取tokenString token = httpServletRequest.getHeader(authorization);//获取token中的信息Claims allClaimsFromToken = jwtTokenUtils.getAllClaimsFromToken(token);//删除redis中用户信息redisUtil.delete(RedisConstant.USER.concat(CommonConstant.COLON).concat(allClaimsFromToken.getSubject()));//删除redis中权限信息redisUtil.delete(RedisConstant.URL.concat(CommonConstant.COLON).concat(allClaimsFromToken.getSubject()));//获取当前token剩余毫秒数long expiration = jwtTokenUtils.getExpirationTimeMillisFromToken(token);//添加token黑名单redisUtil.setEx(RedisConstant.EXPIRATION_TOKEN.concat(CommonConstant.COLON).concat(token),JSON.toJSONString(allClaimsFromToken),expiration,TimeUnit.MILLISECONDS);//返回提示httpServletResponse.setContentType(Constant.APPLICATION_JSON_UTF8_VALUE);PrintWriter out = httpServletResponse.getWriter();out.write(JSON.toJSONString(RespJson.success(I18nUtils.getMessage(LoginConstant.LOGOUT_SUCCESS))));}
}

继承OncePerRequestFilter

主要做了如下操作:

  1. 通过HttpServletRequest获取token
  2. 验证token是否为空
  3. 解析token获取用户名称
  4. 验证用户名称是否为空
  5. 验证上下文中认证信息是否存在
  6. 验证token是否有效
  7. 验证token是否在黑名单中
  8. 调用loadUserByUsername()函数,校验用户是否有效
  9. 整理用户信息,回填验证信息到上下文中
@Component
public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {@Autowiredprivate JwtTokenUtils jwtTokenUtils;@Value("${jwt.authorization}")private String authorization;@Autowiredprivate IUserService userService;@Autowiredprivate RedisUtil redisUtil;@SneakyThrows@Overrideprotected void doFilterInternal(HttpServletRequest request,@NonNull HttpServletResponse response,@NonNull FilterChain chain) {//获取请求携带的tokenfinal String token = request.getHeader(authorization);//验证token是否为空if (StringUtils.isNotBlank(token)) {//解析token中的用户名String username = jwtTokenUtils.getUsernameFromToken(token);//验证用户名称是否为空//验证上下文中认证信息是否存在//验证token是否有效//验证token是否在黑名单中if (StringUtils.isNotBlank(username) &&SecurityContextHolder.getContext().getAuthentication() == null &&jwtTokenUtils.validateToken(token, username) &&!redisUtil.hasKey(RedisConstant.EXPIRATION_TOKEN.concat(CommonConstant.COLON).concat(token))) {//校验用户并返回用户信息UserDetails userDetails = userService.loadUserByUsername(username);//回填验证信息到上下文中MyAuthenticationToken authentication = new MyAuthenticationToken(userDetails, userDetails.getAuthorities(), token);authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));SecurityContextHolder.getContext().setAuthentication(authentication);}}//继续执行chain.doFilter(request, response);}
}

总结

我们为了实现多种登陆方式,重写了springsecurity的认证拦截器中的方法Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response),包装了带有自定义参数的AbstractAuthenticationToken.java,自定义了身份验证接口AuthenticationProvider()中的Authentication authenticate(Authentication authentication)方法,完成了多种登录方式的整合。
至此,完成了springboot+springsecurity+jwt的整合。


相关文章:

Spring Security + Jwt 集成实现登录

文章目录 前言Maven 相关依赖配置文件自定义springsecurity相关认证流程继承WebSecurityConfigurerAdapter继承AbstractAuthenticationToken继承AbstractAuthenticationProcessingFilter实现AuthenticationProvider实现UserDetailsService实现AccessDeniedHandler实现Authentic…...

yolov5 用自己的数据集进行训练

在训练之前先要按照一定目录格式准备数据&#xff1a; VOC标签格式转yolo格式并划分训练集和测试集_爱钓鱼的歪猴的博客-CSDN博客 目录 1、修改数据配置文件 2、修改模型配置文件 3、训练 1、修改数据配置文件 coco.yaml 拷贝data/scripts/coco.yaml文件&#xff0c; pa…...

1951-2023最新中国基础地理信息,包括水系、行政区、DEM高程、气象站经纬位置、土地利用,这些数据获取方法介绍

水系&#xff1a; 流域内所有河流、湖泊等各种水体组成的水网系统&#xff0c;称作水系。其中&#xff0c;水流最终流入海洋的称作外流水系&#xff0c;如太平洋水系、北冰洋水系&#xff1b;水流最终流入内陆湖泊或消失于荒漠之中的&#xff0c;称作内流水系。 [1] 流域面积的…...

CAD处理控件Aspose.CAD功能演示:在 C#中以编程方式搜索 DWG 图形文件中的文本

Aspose.CAD 是一个独立的类库&#xff0c;以加强 Java应用程序处理和渲染CAD图纸&#xff0c;而不需要AutoCAD或任何其他渲染工作流程。该CAD类库允许将DWG&#xff0c; DWT&#xff0c; DWF&#xff0c; DWFX&#xff0c; IFC&#xff0c; PLT&#xff0c; DGN&#xff0c; OB…...

实验二十、压控电压源二阶 LPF 幅频特性的研究

一、题目 研究压控电压源二阶低通滤波电路品质因数 Q Q Q 对频率特性的影响。 二、仿真电路 电路如图1所示。集成运放采用 LM324AJ&#xff0c;其电源电压为 15V。 图 1 压控电压源二阶低通滤波电路幅频特性的测试 图1\,\,压控电压源二阶低通滤波电路幅频特性的测试 图1压控…...

类和对象【C++】【中篇】

目录 一、类的6个默认成员函数 1、构造函数 2、析构函数 3、拷贝构造函数 4、赋值重载函数 二、赋值运算符重载 一、类的6个默认成员函数 注意&#xff1a;默认成员函数不能在类外面定义成全局函数。因为类里没有的话会自动生成&#xff0c;就会产生冲突。 1、构造函数…...

2.SpringBoot运维实用篇

SpringBoot运维实用篇 ​ 基础篇发布以后&#xff0c;看到了很多小伙伴在网上的留言&#xff0c;也帮助超过100位小伙伴解决了一些遇到的问题&#xff0c;并且已经发现了部分问题具有典型性&#xff0c;预计将有些问题在后面篇章的合适位置添加到本套课程中&#xff0c;作为解…...

【c++】浅讲引用

【c】浅讲引用 前言引用定义作用做输出型参数引用作返回值总结 关于引用的权限 结尾 前言 博主开始细学c和linux了 这次就带来浅学了的引用。 引用 定义 引用不是在内存中开辟一个新空间的新变量 类似于给变量取别名&#xff0c;和取别名的对象在空间中公用一个对象 例&#…...

CSS布局基础(文字[行内<块>]与行内[块]垂直对齐方式 文字溢出显示省略号)

文字[行内<块>]与行内[块]垂直对齐方式 文字[行内<块>]与行内[块]垂直对齐方式概述图片底部空隙问题 文字溢出显示省略号单行文字多行文字 文字[行内<块>]与行内[块]垂直对齐方式 概述 vertical-align: top | middle| bottom | baseline(默认) | sub | sup…...

AI自动写文章_免费在线原创文章生成器

自动写文章生成器 自动写文章生成器是一种利用人工智能和自然语言处理技术&#xff0c;帮助用户快速生成文章的工具。该软件可以根据用户的需求和选择&#xff0c;自动生成符合要求的文章&#xff0c;无需手动编写和修改。 自动写文章生成器的主要功能包括以下几个方面&#…...

Java阶段二Day15

Java阶段二Day15 文章目录 Java阶段二Day15复习前日知识点对象数据类型注入数组类型注入集合类型的注入p命名空间引入外部属性文件 基于XML管理beanbean的作用域bean的生命周期代码演示生命周期后置处理器处理展示基于XML的自动装配 基于注解管理bean开启组件扫描使用注解定义B…...

从月薪3000到月薪20000,自动化测试应该这样学...

绝大多数测试工程师都是从功能测试做起的&#xff0c;工作忙忙碌碌&#xff0c;每天在各种业务需求学习和点点中度过&#xff0c;过了好多年发现自己还只是一个功能测试工程师。 随着移动互联网的发展&#xff0c;从业人员能力的整体进步&#xff0c;软件测试需要具备的能力要…...

Python魔法方法 单例模式

前言 本文介绍一下python中常用的魔法方法以及面向对象中非常重要的单例模式。 魔法方法 python中一切皆对象&#xff0c;因为python是面向对象的编程语言。python给类和对象提供了大量的内置方法&#xff0c;这些内置方法也称魔法方法。这些魔法方法总是在某种条件下自动触…...

计算机网络基础知识(三)—— 什么是OSI七层模型?

文章目录 00 | &#x1f6f8;发展史&#x1f6f8;01 | &#x1f6f8;OSI七层参考模型&#x1f6f8;02 | &#x1f6f8;OSI七层参考模型的信息流向&#x1f6f8; OSI七层模型是Open Systems Interconnection Reference Model的缩写&#xff0c;是由国际标准化组织&#xff08;IS…...

Python(符号计算常微分方程)谐振子牛顿运动方程

牛顿运动方程 牛顿运动方程可以写成以下形式 F d p d t m d v d t m d 2 r d t 2 \mathbf{F}\frac{d \mathbf{p}}{d t}m \frac{d \mathbf{v}}{d t}m \frac{d^2 \mathbf{r}}{d t^2} Fdtdp​mdtdv​mdt2d2r​ 恒力问题 具有恒定力的问题意味着恒定的加速度。 典型的例子是…...

OpenCL编程指南-1.2OpenCL基本概念

OpenCL概念基础 面向异构平台的应用都必须完成以下步骤&#xff1a; 1&#xff09;发现构成异构系统的组件。 2&#xff09;探查这些组件的特征&#xff0c;使软件能够适应不同硬件单元的特定特性。 3&#xff09;创建将在平台上运行的指令块&#xff08;内核)。 4&#xff09…...

使用 ChatGPT 辅助学习——为自己找一个老师

我们每个人都有许多标签&#xff0c;例如高中生、成绩中等、文科&#xff0c;根据这些标签我和其他拥有相同标签的人分配了相同的教程、班级和老师&#xff0c;这可以带来效率上的提升&#xff0c;因为同一份教程、老师就可以服务几十上百人&#xff0c;而无须为每个人定制&…...

MySQL基础(二十一)用户与权限管理

1. 用户管理 1.1 登录MySQL服务器 启动MySQL服务后&#xff0c;可以通过mysql命令来登录MySQL服务器&#xff0c;命令如下&#xff1a; mysql –h hostname|hostIP –P port –u username –p DatabaseName –e "SQL语句"-h参数后面接主机名或者主机IP&#xff0c…...

程序员的下一个风口

面对近一年的裁员潮&#xff0c;以及 GPT 出现带来的 AI 颠覆潮流&#xff0c;各种话题出现&#xff1a;「前端已死」、「后端已死」、「Copy/Paste 程序员将被 AI 取代」。程序员行业是否还有发展空间&#xff1f; 这一两年的就业机会是因为经济衰落周期内造成的&#xff0c;不…...

Android 自定义View 之 简易输入框

简易输入框 前言正文① 构造方法② XML样式③ 测量④ 绘制1. 绘制方框2. 绘制文字 ⑤ 输入1. 键盘布局2. 键盘接口3. 键盘弹窗4. 显示键盘5. 相关API 四、使用自定义View五、源码 前言 在日常工作开发中&#xff0c;我们时长会遇到各种各样的需求&#xff0c;不部分需求是可以通…...

【kafka】Golang实现分布式Masscan任务调度系统

要求&#xff1a; 输出两个程序&#xff0c;一个命令行程序&#xff08;命令行参数用flag&#xff09;和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽&#xff0c;然后将消息推送到kafka里面。 服务端程序&#xff1a; 从kafka消费者接收…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止

<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet&#xff1a; https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...

如何将联系人从 iPhone 转移到 Android

从 iPhone 换到 Android 手机时&#xff0c;你可能需要保留重要的数据&#xff0c;例如通讯录。好在&#xff0c;将通讯录从 iPhone 转移到 Android 手机非常简单&#xff0c;你可以从本文中学习 6 种可靠的方法&#xff0c;确保随时保持连接&#xff0c;不错过任何信息。 第 1…...

Neo4j 集群管理:原理、技术与最佳实践深度解析

Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

Java入门学习详细版(一)

大家好&#xff0c;Java 学习是一个系统学习的过程&#xff0c;核心原则就是“理论 实践 坚持”&#xff0c;并且需循序渐进&#xff0c;不可过于着急&#xff0c;本篇文章推出的这份详细入门学习资料将带大家从零基础开始&#xff0c;逐步掌握 Java 的核心概念和编程技能。 …...

Python+ZeroMQ实战:智能车辆状态监控与模拟模式自动切换

目录 关键点 技术实现1 技术实现2 摘要&#xff1a; 本文将介绍如何利用Python和ZeroMQ消息队列构建一个智能车辆状态监控系统。系统能够根据时间策略自动切换驾驶模式&#xff08;自动驾驶、人工驾驶、远程驾驶、主动安全&#xff09;&#xff0c;并通过实时消息推送更新车…...

Python竞赛环境搭建全攻略

Python环境搭建竞赛技术文章大纲 竞赛背景与意义 竞赛的目的与价值Python在竞赛中的应用场景环境搭建对竞赛效率的影响 竞赛环境需求分析 常见竞赛类型&#xff08;算法、数据分析、机器学习等&#xff09;不同竞赛对Python版本及库的要求硬件与操作系统的兼容性问题 Pyth…...

在 Visual Studio Code 中使用驭码 CodeRider 提升开发效率:以冒泡排序为例

目录 前言1 插件安装与配置1.1 安装驭码 CodeRider1.2 初始配置建议 2 示例代码&#xff1a;冒泡排序3 驭码 CodeRider 功能详解3.1 功能概览3.2 代码解释功能3.3 自动注释生成3.4 逻辑修改功能3.5 单元测试自动生成3.6 代码优化建议 4 驭码的实际应用建议5 常见问题与解决建议…...

flow_controllers

关键点&#xff1a; 流控制器类型&#xff1a; 同步&#xff08;Sync&#xff09;&#xff1a;发布操作会阻塞&#xff0c;直到数据被确认发送。异步&#xff08;Async&#xff09;&#xff1a;发布操作非阻塞&#xff0c;数据发送由后台线程处理。纯同步&#xff08;PureSync…...

ThreadLocal 源码

ThreadLocal 源码 此类提供线程局部变量。这些变量不同于它们的普通对应物&#xff0c;因为每个访问一个线程局部变量的线程&#xff08;通过其 get 或 set 方法&#xff09;都有自己独立初始化的变量副本。ThreadLocal 实例通常是类中的私有静态字段&#xff0c;这些类希望将…...