Spring Security + Jwt 集成实现登录
文章目录
- 前言
- Maven 相关依赖
- 配置文件
- 自定义springsecurity相关认证流程
- 继承WebSecurityConfigurerAdapter
- 继承AbstractAuthenticationToken
- 继承AbstractAuthenticationProcessingFilter
- 实现AuthenticationProvider
- 实现UserDetailsService
- 实现AccessDeniedHandler
- 实现AuthenticationEntryPoint
- 实现AuthenticationFailureHandler
- 实现AuthenticationFailureHandler
- 实现AuthenticationSuccessHandler
- 实现AuthenticationSuccessHandler
- 继承OncePerRequestFilter
- 总结
前言
主要实现了如下登录方式:
- 用户名+密码+验证码登录
- 邮箱+验证码登录
Maven 相关依赖
主要引入了如下依赖:
- spring-boot-starter-security依赖
- 为了解决警告排除了kotlin相关的三个依赖包
- 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>
配置文件
主要配置如下:
- 配置了jwt生成token秘钥
- 配置了jwt的token过期时间,单位为毫秒
- 配置了header中携带token的key值
jwt: #jwt参数配置secret: secretexpiration: 14400000authorization: Authorization
自定义springsecurity相关认证流程
继承WebSecurityConfigurerAdapter
主要做了如下操作:
- 重写了安全框架全局配置
configureGlobal(AuthenticationManagerBuilder auth)
方法
- 指定了密码加密规则
- 关闭了隐藏用户找不到异常功能
- 放开了http防火墙,不对非法json请求拦截
- 重写了http安全配置
configure(HttpSecurity http)
方法
- 禁用csr攻击保护
- 禁用form表单登录
- 禁用页面缓存
- 自定义异常拦截处理
- 身份验证失败
MyAuthenticationEntryPoint()
异常拦截器- 访问被拒绝
MyAccessDeniedHandler()
异常拦截器- 会话创建策略无状态(不使用session)
- 授权请求配置
- 接口请求
UsernamePasswordAuthenticationFilter()
拦截器之前添加令牌验证过滤器- 登出成功
MyLogoutSuccessHandler()
拦截器- 添加自定义身份验证适配器
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
主要做了如下操作:
- 重新定义了principal概念
- 增加了验证码字段code
- 增加了验证码key字段uuid
- 登录类型type
- 自定义带参的构造函数
- 新增的参数对应的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
主要做了如下操作:
- 从HttpServletRequest获取到相关参数
- 整理验证参数实体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
主要做了如下操作:
- 实现了认证方法
authenticate(Authentication authentication)
- 通过Authentication获取登录相关信息
- 验证登录类型是否为空
- 如果是用户名密码验证码登录则执行
defaultLogin()
- 如果是邮箱验证码登录则执行
emailLogin()
函数- 调用
loadUserByUsername()
函数,校验用户是否有效- 整理用户信息,回填验证信息到上下文中
- 创建了用户名密码验证码登录私有化函数
defaultLogin()
- 通过uuid获取缓存中验证码
- 验证输入uuid和输入的验证码是否为空
- 验证缓存中是否存在uuid对应的验证码
- 验证输入的验证码和缓存中的验证码是否一致
- 通过用户名称获取用户信息
- 验证用户是否存在
- 验证密码是否正确
- 返回用户名称或者对应的错误代码
- 创建了邮箱-验证码登录私有化函数
emailLogin()
- 通过uuid获取缓存中验证码
- 验证输入uuid和输入的验证码是否为空
- 验证缓存中是否存在uuid对应的验证码
- 验证输入的验证码和缓存中的验证码是否一致
- 通过邮箱获取用户信息
- 验证用户是否存在
- 返回用户名称或者对应的错误代码
- 实现了
supports(Class<?> authentication)
方法
- 指定自定义身份验证方法
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
主要做了如下操作:
- 实现了
loadUserByUsername(String username)
方法
- 对输入的username(错误代码或者是用户名)进行匹配
- 验证码为空
- 验证码失效
- 验证码错误
- 用户不存在
- 密码错误
- 获取缓存中的用户信息
- 校验缓存中是否存在用户信息
- 不存在则通过用户名称获取用户信息
- 封装为
LoginUserVo()
实体- 验证用户名密码错误
- 验证用户过期
- 验证用户锁定
- 验证用户凭证过期
- 验证用户禁用
- 返回用户信息
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
主要做了如下操作:
- 以文本输出流的形式返回给客户端相关信息(加持了中英文)
@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
主要做了如下操作:
- 以文本输出流的形式返回给客户端相关信息(加持了中英文)
@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
主要做了如下操作:
- 以文本输出流的形式返回给客户端相关信息(加持了中英文)
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
主要做了如下操作:
- 以文本输出流的形式返回给客户端相关信息(加持了中英文)
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
主要做了如下操作:
- 根据上下文中用户信息生成token
- 以文本输出流的形式返回给客户端相关信息(加持了中英文)
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
主要做了如下操作:
- HttpServletRequest中获取token
- 解析token拿到用户名称
- 根据用户名称删除缓存中的用户信息
- 根据用户名称删除缓存中的权限信息
- 根据用户名称删除缓存中的用户获取token剩余过期时间
- 将未过期token加入到黑名单中,过期时间为剩余过期时间
- 以文本输出流的形式返回给客户端相关信息(加持了中英文)
@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
主要做了如下操作:
- 通过HttpServletRequest获取token
- 验证token是否为空
- 解析token获取用户名称
- 验证用户名称是否为空
- 验证上下文中认证信息是否存在
- 验证token是否有效
- 验证token是否在黑名单中
- 调用
loadUserByUsername()
函数,校验用户是否有效- 整理用户信息,回填验证信息到上下文中
@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 用自己的数据集进行训练
在训练之前先要按照一定目录格式准备数据: VOC标签格式转yolo格式并划分训练集和测试集_爱钓鱼的歪猴的博客-CSDN博客 目录 1、修改数据配置文件 2、修改模型配置文件 3、训练 1、修改数据配置文件 coco.yaml 拷贝data/scripts/coco.yaml文件, pa…...

1951-2023最新中国基础地理信息,包括水系、行政区、DEM高程、气象站经纬位置、土地利用,这些数据获取方法介绍
水系: 流域内所有河流、湖泊等各种水体组成的水网系统,称作水系。其中,水流最终流入海洋的称作外流水系,如太平洋水系、北冰洋水系;水流最终流入内陆湖泊或消失于荒漠之中的,称作内流水系。 [1] 流域面积的…...

CAD处理控件Aspose.CAD功能演示:在 C#中以编程方式搜索 DWG 图形文件中的文本
Aspose.CAD 是一个独立的类库,以加强 Java应用程序处理和渲染CAD图纸,而不需要AutoCAD或任何其他渲染工作流程。该CAD类库允许将DWG, DWT, DWF, DWFX, IFC, PLT, DGN, OB…...

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

类和对象【C++】【中篇】
目录 一、类的6个默认成员函数 1、构造函数 2、析构函数 3、拷贝构造函数 4、赋值重载函数 二、赋值运算符重载 一、类的6个默认成员函数 注意:默认成员函数不能在类外面定义成全局函数。因为类里没有的话会自动生成,就会产生冲突。 1、构造函数…...

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

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

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

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

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

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

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

计算机网络基础知识(三)—— 什么是OSI七层模型?
文章目录 00 | 🛸发展史🛸01 | 🛸OSI七层参考模型🛸02 | 🛸OSI七层参考模型的信息流向🛸 OSI七层模型是Open Systems Interconnection Reference Model的缩写,是由国际标准化组织(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} Fdtdpmdtdvmdt2d2r 恒力问题 具有恒定力的问题意味着恒定的加速度。 典型的例子是…...

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

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

MySQL基础(二十一)用户与权限管理
1. 用户管理 1.1 登录MySQL服务器 启动MySQL服务后,可以通过mysql命令来登录MySQL服务器,命令如下: mysql –h hostname|hostIP –P port –u username –p DatabaseName –e "SQL语句"-h参数后面接主机名或者主机IP,…...

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

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

SpringMVC的基础知识
创建SpringMVC项目 SpringMVC项目其实和SpingBoot项目差不多,就多引入了一个SpringWeb项目而已拉 可以看这篇博客,创建的就是一个SpringMVC项目--创建项目の博客 SpringMVC是啥 Spring是啥相信大家都了解 啥是MVC呢?MVC是Model View Controller的缩写 我们分开看这三个词Model…...

OpenPCDet系列 | 4.2 DataAugmentor点云数据增强模块解析
文章目录 DataAugmentor模块解析1. gt_sampling2. random_world_flip3. random_world_rotation4. random_world_scaling5. limit_period DataAugmentor模块解析 在pointpillars算法中,具体的数据增强方法配置是在yaml中的DATA_CONFIG.DATA_AUGMENTOR进行配置&#…...

精准测试之过程与实践 | 京东云技术团队
作者:京东工业 宛煜昕 一、怎样的技术 •百度百科: 精准测试是一套计算机测试辅助分析系统。 精准测试的核心组件包含的软件测试示波器、用例和代码的双向追溯、智能回归测试用例选取、覆盖率分析、缺陷定位、测试用例聚类分析、测试用例自动生成系统…...
类ChatGPT逐行代码解读(1/2):从零实现Transformer、ChatGLM-6B
前言 最近一直在做类ChatGPT项目的部署 微调,关注比较多的是两个:一个LLaMA,一个ChatGLM,会发现有不少模型是基于这两个模型去做微调的,说到微调,那具体怎么微调呢,因此又详细了解了一下微调代…...

车道线检测
前言 目前,车道线检测技术已经相当成熟,主要应用在自动驾驶、智能交通等领域。下面列举一些当下最流行的车道线检测方法: 基于图像处理的车道线检测方法。该方法是通过图像处理技术从摄像头传回的图像中提取车道线信息的一种方法,…...

云渲染靠谱吗,使用云渲染会不会被盗作品?
云渲染靠谱吗、安全吗?如果使用 云渲染会不会被盗作品......Renderbus瑞云渲染作为一个正经的云渲染平台,也时不时会收到这类疑问,首先,瑞云渲染是肯定靠谱的,各位可以放心使用。另外小编也将在本篇教你如何辨别云渲染平台是否安全…...

什么是FPGA?关于FPGA基础知识 一起来了解FPGA lattice 深力科 MachXO3系列 LCMXO3LF-9400C-5BG256C
什么是FPGA?关于FPGA基础知识 一起来了解FPGA lattice 深力科 MachXO3系列 LCMXO3LF-9400C-5BG256C FPGA基础知识:FPGA是英文Field-Programmable Gate Array的缩写,即现场可编程门阵列,它是在PAL、GAL、CPLD等可编程器…...

有什么好用的云渲染?
在CG制作流程中,离线渲染一直是必要且耗时的环节。你的场景越复杂,渲染出现问题的可能性就越大,尤其是当你独自工作,没有人给你建议的时候,灯光、模型、场景任何一个环节渲染时出现问题都可能让你焦头烂额,…...

什么是医学影像PACS系统?PACS系统功能有哪些?作用有哪些?对接哪些设备?业务流程是什么?
一、什么是医学影像PACS系统 PACS:为Picture Archive and CommunicationSystem的缩写,是图象归档和通讯系统。PACS系统应用在医院影像科室的系统,主要的任务就是把日常产生的各种医学影像(包括核磁,CT,超声…...

分布式缓存:什么是它以及为什么需要它?
前言 随着网络的快速发展,分布式应用变得越来越普遍。这种类型的应用程序需要访问多个组件和服务,而这些组件可能分散在不同的物理位置上。在这种情况下,由于网络通信的高延迟和低带宽,性能问题变得尤为明显。为解决这一问题&…...