当前位置: 首页 > 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;不部分需求是可以通…...

7.4.分块查找

一.分块查找的算法思想&#xff1a; 1.实例&#xff1a; 以上述图片的顺序表为例&#xff0c; 该顺序表的数据元素从整体来看是乱序的&#xff0c;但如果把这些数据元素分成一块一块的小区间&#xff0c; 第一个区间[0,1]索引上的数据元素都是小于等于10的&#xff0c; 第二…...

Java 8 Stream API 入门到实践详解

一、告别 for 循环&#xff01; 传统痛点&#xff1a; Java 8 之前&#xff0c;集合操作离不开冗长的 for 循环和匿名类。例如&#xff0c;过滤列表中的偶数&#xff1a; List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...

【入坑系列】TiDB 强制索引在不同库下不生效问题

文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...

uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖

在前面的练习中&#xff0c;每个页面需要使用ref&#xff0c;onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入&#xff0c;需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...

基于Flask实现的医疗保险欺诈识别监测模型

基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施&#xff0c;由雇主和个人按一定比例缴纳保险费&#xff0c;建立社会医疗保险基金&#xff0c;支付雇员医疗费用的一种医疗保险制度&#xff0c; 它是促进社会文明和进步的…...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?

论文网址&#xff1a;pdf 英文是纯手打的&#xff01;论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误&#xff0c;若有发现欢迎评论指正&#xff01;文章偏向于笔记&#xff0c;谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...

【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)

可以使用Sqliteviz这个网站免费编写sql语句&#xff0c;它能够让用户直接在浏览器内练习SQL的语法&#xff0c;不需要安装任何软件。 链接如下&#xff1a; sqliteviz 注意&#xff1a; 在转写SQL语法时&#xff0c;关键字之间有一个特定的顺序&#xff0c;这个顺序会影响到…...

MODBUS TCP转CANopen 技术赋能高效协同作业

在现代工业自动化领域&#xff0c;MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步&#xff0c;这两种通讯协议也正在被逐步融合&#xff0c;形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...

在Ubuntu中设置开机自动运行(sudo)指令的指南

在Ubuntu系统中&#xff0c;有时需要在系统启动时自动执行某些命令&#xff0c;特别是需要 sudo权限的指令。为了实现这一功能&#xff0c;可以使用多种方法&#xff0c;包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法&#xff0c;并提供…...

HBuilderX安装(uni-app和小程序开发)

下载HBuilderX 访问官方网站&#xff1a;https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本&#xff1a; Windows版&#xff08;推荐下载标准版&#xff09; Windows系统安装步骤 运行安装程序&#xff1a; 双击下载的.exe安装文件 如果出现安全提示&…...