SpringSecurity6.x整合手机短信登录授权

embedded/2024/9/25 0:44:21/

前言:如果没有看过我的这篇文章的Springboot3.x.x使用SpringSecurity6(一文包搞定)_springboot3整合springsecurity6-CSDN博客需要看下,大部分多是基于这篇文章的基础上实现的。

明确点我们的业务流程:

  1. 需要有一个发送短信的接口,可结合阿里云的sms
  2. 需要一个短信登录接口,参数为手机号和验证码
  3. 结合到Security做用户认证 
  4. 最终返回token

了解业务流程后我们,就可以开始动手了!

验证码接口

验证码这里我只展示业务逻辑,后续会发文字专门说明Springboot如何调用阿里的SMS发现短信。 

控制层

java">   /*** 获取手机验证码* @return*/@Operation(summary = "获取手机验证码")@GetMapping("/sendCode")public Result sendCode(@RequestParam(name = "phone") String phone){return userService.sendCode(phone);}

impl

java">   @Overridepublic Result sendCode(String phone) {//采用正则校验手机号boolean phoneInvalid = RegexUtils.isPhoneInvalid(phone);if (phoneInvalid) {return Result.fail(500, "手机格式错误");}//设置校验60秒if (stringRedisTemplate.hasKey(RedisConstants.PHONE_CHECK_KEY + phone)) {Long expire = stringRedisTemplate.getExpire(RedisConstants.PHONE_CHECK_KEY + phone, TimeUnit.SECONDS);return Result.fail(500, "请耐心等待" + expire + "秒");}//采用工具类生成随机验证码int number = RandomUtil.randomInt(100000, 999999);String code = String.valueOf(number);//TODO 后续改为第三方短信平台try {smsUtils.sendSms(phone, code);stringRedisTemplate.opsForValue().set(RedisConstants.LOGIN_CODE_KEY + phone, code, RedisConstants.LOGIN_CODE_TTL, TimeUnit.SECONDS);
//            设置倒计时间stringRedisTemplate.opsForValue().set(RedisConstants.PHONE_CHECK_KEY + phone, code, RedisConstants.PHONE_CHECK_TTL, TimeUnit.SECONDS);log.info("验证码:{}", code);} catch (Exception e) {e.printStackTrace();return Result.fail(500, "发送失败,请联系管理员~");}return Result.success("请在五分钟内输入验证码~");}

上面的我们将验证码存到redis且设置了时效,那么业务的第一不我们就完成了。

短信登陆接口

java">   @Operation(summary = "手机号登录接口")@PostMapping("/phoneLogin")public Result phoneLogin(@RequestParam(name = "phone")String phone,@RequestParam(name = "code")String code){return userService.phoneLogin(new LoginDto().setPhone(phone).setCode(code));}

这里为了在Security好取参数我们这里用Param传参数。实现类我们直接返回成功即可,因为结合Security后请求不会进到方法体里面。为什么呢?我们后面来解释。

问题1:为什么手机短信接口结合Security后不会进度lmpl层方法体里面呢?

结合Security实现认证

在这里之前我们需要了解Security里面的几个类:

  1. UsernamePasswordAuthenticationToken
  2. UsernamePasswordAuthenticationFilter
  3. AuthenticationProvider

1.UsernamePasswordAuthenticationToken

UsernamePasswordAuthenticationToken首先我们需要了解这哥们是干嘛用的,我们看下官网是怎么解释的表单登录(Form Login) :: Spring Security Reference

a86ec7278b674e748906d06154a52e90.png

 上图中他说会从UsernamePasswordAuthenticationFilter这个过滤器中去拿账号密码,给UsernamePasswordAuthenticationToken 那么我们就可以猜测下这东西应该是存的是账号和密码有点相当于实体。

6e71e22a1fc74234b1ed476402ae2421.png

我们来看下源码:

java">public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;private final Object principal;private Object credentials;/**
*任何希望创建构造函数的代码都可以安全地使用此构造函数
*<code>用户名密码认证令牌</code>,作为{@link#isAuthenticated()}
*将返回<code>false</code>。
*
*/public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {super(null);this.principal = principal;this.credentials = credentials;setAuthenticated(false);}/**
*此构造函数只能由<code>AuthenticationManager</code>或
*<code>AuthenticationProvider</code>满足以下条件的实现
*生成一个可信的(即{@link#isAuthenticated()}=<code>true</code>)
*身份验证令牌。
*@param主体
*@param凭据
*@param权限
*/public UsernamePasswordAuthenticationToken(Object principal, Object credentials,Collection<? extends GrantedAuthority> authorities) {super(authorities);this.principal = principal;this.credentials = credentials;super.setAuthenticated(true); // must use super, as we override}/**
*任何希望创建工厂方法的代码都可以安全地使用此工厂方法
*未经身份验证的<code>用户名密码认证令牌</code>。
*@param主体
*@param凭据
*@返回用户名密码认证令牌,结果为false isAuthenticated()
*
*@自5.7以来
*/public static UsernamePasswordAuthenticationToken unauthenticated(Object principal, Object credentials) {return new UsernamePasswordAuthenticationToken(principal, credentials);}/**
*任何希望创建工厂方法的代码都可以安全地使用此工厂方法
*经过身份验证的<code>用户名密码身份验证令牌</code>。
*@param主体
*@param凭据
*@返回用户名密码认证令牌,结果为true isAuthenticated()
*
*@自5.7以来
*/public static UsernamePasswordAuthenticationToken authenticated(Object principal, Object credentials,Collection<? extends GrantedAuthority> authorities) {return new UsernamePasswordAuthenticationToken(principal, credentials, authorities);}@Overridepublic Object getCredentials() {return this.credentials;}@Overridepublic Object getPrincipal() {return this.principal;}@Overridepublic void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {Assert.isTrue(!isAuthenticated,"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");super.setAuthenticated(false);}@Overridepublic void eraseCredentials() {super.eraseCredentials();this.credentials = null;}}

通过上面我们确认了principal前面是账号,credentials就是密码,返回的是一个Authentication,因为他继承了AbstractAuthenticationToken

c3509a5b237a4c1eba988e2360a7a5a1.png

2.UsernamePasswordAuthenticationFilter

上面我们就提到UsernamePasswordAuthenticationFilter这个过滤器了,他主要是拦截账号密码封装整UsernamePasswordAuthenticationToken交给AuthenticationManager去做认证的,我是怎么知道的呢?我们来看下重要源码:

java">public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {// 表单参数键的常量public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";// 默认的认证请求URL和HTTP方法private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login", "POST");// 用于存储用户名和密码参数名的字段private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;// 标志是否只允许POST请求private boolean postOnly = true;// 使用默认请求匹配器的构造函数public UsernamePasswordAuthenticationFilter() {super(DEFAULT_ANT_PATH_REQUEST_MATCHER);}// 使用自定义认证管理器的构造函数public UsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager) {super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager);}@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)throws AuthenticationException {// 如果postOnly为true,检查请求方法是否为POSTif (this.postOnly && !request.getMethod().equals("POST")) {throw new AuthenticationServiceException("不支持的认证方法: " + request.getMethod());}// 从请求中获取用户名String username = obtainUsername(request);username = (username != null) ? username.trim() : "";// 从请求中获取密码String password = obtainPassword(request);password = (password != null) ? password : "";// 创建认证令牌UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password);// 为认证请求设置附加详情setDetails(request, authRequest);// 将认证委托给认证管理器return this.getAuthenticationManager().authenticate(authRequest);}// 从请求中获取用户名的方法protected String obtainUsername(HttpServletRequest request) {return request.getParameter(usernameParameter);}// 从请求中获取密码的方法protected String obtainPassword(HttpServletRequest request) {return request.getParameter(passwordParameter);}// 为认证请求设置附加详情的方法protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {authRequest.setDetails(authenticationDetailsSource.buildDetails(request));}
}
重要看这段汉

我们看到这哥们把UsernamePasswordAuthenticationToken给了AuthenticationManager,那么问题又来了AuthenticationManager是如果完成验证的呢?

3.AuthenticationManage

我们点击源码后发现AuthenticationManager是个接口,接口怎么实现认证呢?不可能的嘛,所以我们找下实现类:

我们重点关注这里类

868ff61b384948778688dd824d58db44.png021651d5450146ba91611c966c9da71c.png

我们发现这哥们是 AuthenticationManager 的实现子类之一,也是我们最常用的一个实现。我们来看下实际做验证的代码,源码重点的部分0760e225c7fc487e9be4e0ba0671e611.png

上图中我们发现循环了AuthenticationProvider provider,说明我们可能出现多个provider,那么AuthenticationProvider是个接口所以主要用于认证的是ProviderManager 子类,如果我们有多种认证方式,那么只依靠一个ProviderManager 本身来实现 authenticate() 接口是完全不够的,所以上面我们看到了循环去调用authenticate()。

了解上面3个类后我们发现这三个类是一个链路,也就是张老演员图了:

453293e06f91467b9b9b2ef13f74f03e.png

上图中我们看到UsernamePasswordAuthenticationToken需要账号密码,那么我们要实现的是手机验证码,如果你重上面看到这里应该知道怎么实现了吧?

手机验证整合Security

思路:

1.既然UsernamePasswordAuthenticationToken的爹是它AbstractAuthenticationToken,那么我们就照猫画虎重写一个

2.读过上面的都知道UsernamePasswordAuthenticationFilter是拦截账号和密码的,而且这孩子还继承了家产AbstractAuthenticationProcessingFilter,那么我们是要拿手机号和验证码的,所以照猫画虎重写一个

3.然后这哥们UsernamePasswordAuthenticationFilter是不是要把UsernamePasswordAuthenticationToken给AuthenticationManager然后AuthenticationManager又是个接口,他需要实现了ProviderManger,调用authenticate验证,那么里面遍历了ProviderManger,那么我们是不是自己做一个ProviderManger让它去调用authenticate验证不就完了?所以要整了类实现下AuthenticationProvider接口。

开整

SmsCodeAuthenticationToken

java">/**这一步的作用是为了替换原有系统的 UsernamePasswordAuthenticationToken 用来做验证** 代码都是从UsernamePasswordAuthenticationToken 里粘贴出来的**/
public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;/*** 在 UsernamePasswordAuthenticationToken 中该字段代表登录的用户名,* 在这里就代表登录的手机号码*/private final Object principal;/*** 构建一个没有鉴权的 SmsCodeAuthenticationToken*/public SmsCodeAuthenticationToken(Object principal) {super(null);this.principal = principal;setAuthenticated(false);}/*** 构建拥有鉴权的 SmsCodeAuthenticationToken*/public SmsCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {super(authorities);this.principal = principal;super.setAuthenticated(true); // must use super, as we override}public Object getCredentials() {return null;}public 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();}
}

这样第一个就完成了 

SmsCodeAuthenticationFilter

java">
/*** 短信登录的鉴权过滤器,模仿 UsernamePasswordAuthenticationFilter 实现
*/
public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {/*** form表单中手机号码的字段name*/public static final String SPRING_SECURITY_FORM_MOBILE_KEY = "phone";private String mobileParameter = SPRING_SECURITY_FORM_MOBILE_KEY;/*** 是否仅 POST 方式*/private boolean postOnly = true;public SmsCodeAuthenticationFilter(AuthenticationManager authenticationManager,AuthenticationSuccessHandler authenticationSuccessHandler,AuthenticationFailureHandler authenticationFailureHandler) {super(new AntPathRequestMatcher(PHONE_LOGIN_PATH, "POST"));setAuthenticationManager(authenticationManager);setAuthenticationSuccessHandler(authenticationSuccessHandler);setAuthenticationFailureHandler(authenticationFailureHandler);}@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {if (postOnly && !request.getMethod().equals("POST")) {throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());}// 电话号码String mobile = request.getParameter("phone");if (StringUtils.isEmpty(mobile)) {throw new CustomException(500,"手机号码不能为空");}return this.getAuthenticationManager().authenticate(new SmsCodeAuthenticationToken(mobile));}protected String obtainUsername(HttpServletRequest request) {StringBuilder data = new StringBuilder();String line;BufferedReader reader;try {reader = request.getReader();while ((null!=(line=reader.readLine()))){data.append(line);}}catch (IOException e){return null;}User user = JSONUtil.toBean(data.toString(), User.class);return user.getPhone();}protected String obtainPassword(HttpServletRequest request) {return request.getParameter(mobileParameter);}
}

 SmsCodeAuthenticationProvider

java">*** 短信登陆鉴权 Provider,要求实现 AuthenticationProvider 接口*/
@Configuration
public class SmsCodeAuthenticationProvider implements AuthenticationProvider {@Autowiredprivate UserDetailsService userDetailsService;private StringRedisTemplate stringRedisTemplate;@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken) authentication;String mobile = (String) authenticationToken.getPrincipal();
//       //校验验证码checkSmsCode(mobile);UserDetails userDetails = userDetailsService.loadUserByUsername(mobile);if (userDetails == null) {throw new CustomException(400, "用户不存在");}// 此时鉴权成功后,应当重新 new 一个拥有鉴权的 authenticationResult 返回  role_codeSmsCodeAuthenticationToken authenticationResult = new SmsCodeAuthenticationToken(userDetails, userDetails.getAuthorities());authenticationResult.setDetails(authenticationToken.getDetails());return authenticationResult;}/*** 校验短信验证码** @param mobile*/private void checkSmsCode(String mobile) {boolean phoneInvalid = RegexUtils.isPhoneInvalid(mobile);if (phoneInvalid) {throw new CustomException(400, "手机号格式不正确~");}Boolean isHaveKey = stringRedisTemplate.hasKey(RedisConstants.LOGIN_CODE_KEY + mobile);if (!isHaveKey) {throw new CustomException(401, "验证码失效~");}
//        获取redis进行比较String code = stringRedisTemplate.opsForValue().get(RedisConstants.LOGIN_CODE_KEY + mobile);HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();String inputCode = request.getParameter("code");if (!code.equals(inputCode)) {throw new CustomException(500, "验证码错误~");}}@Overridepublic boolean supports(Class<?> authentication) {// 判断 authentication 是不是 SmsCodeAuthenticationToken 的子类或子接口return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication);}public UserDetailsService getUserDetailsService() {return userDetailsService;}public void setUserDetailsService(UserDetailsService userDetailsService) {this.userDetailsService = userDetailsService;}public StringRedisTemplate getStringRedisTemplate() {return stringRedisTemplate;}public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}/*** 获取请求中的验证码** @param request* @return HttpServletRequest*/protected String obtainCode(HttpServletRequest request) {StringBuilder data = new StringBuilder();String line;BufferedReader reader;try {reader = request.getReader();while ((null != (line = reader.readLine()))) {data.append(line);}} catch (IOException e) {return null;}LoginDto loginDto = JSONUtil.toBean(data.toString(), LoginDto.class);return loginDto.getCode();}
}

 大功告成了,然后我们发现上面的代码使用UserDetail,我们还是来看老演员

09e347a694cc4e89ba500dae6cd3f0e3.png

其实你上面重新的三个就到这了。

注意: 需结合我上篇文章的修改着写

UserDetail

java">@Service
@RequiredArgsConstructor
@Slf4j
public class SysUserDetailsService implements UserDetailsService {private final UserMapper userMapper;private final RoleService roleService;private final MenuService menuService;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//        判断是否是账密密码登录还是手机号登录UserAuthInfo userAuthInfo = null;if (isMobileNO(username)) {userAuthInfo = this.userMapper.getUserNameByPhone(username);
//         如果查询不到那么为手机注册的用户if (Objects.isNull(userAuthInfo)) {userAuthInfo = this.register(username);}} else {userAuthInfo = this.userMapper.getUserAuthInfo(username);}if (userAuthInfo == null) {throw new UsernameNotFoundException("未找到对应账号,请检查输入信息~");}Set<String> roles = userAuthInfo.getRoles();if (CollectionUtil.isNotEmpty(roles)) {Set<String> perms = menuService.listRolePerms(roles);userAuthInfo.setPerms(perms);}return new SysUserDetails(userAuthInfo);}public static boolean isMobileNO(String input) {// 中国大陆手机号正则表达式String regex = "^1[3-9]\\d{9}$";return input.matches(regex);}/*** 手机注册账号*/private UserAuthInfo register(String username) {log.info("手机号用户注册--{}--开始", username);try {User user = this.userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getPhone, username));
//        判断该用户是否存在 不存在添加用户if (Objects.isNull(user)) {user = new User().setPhone(username).setUsername(UUID.randomUUID().toString()).setType(0).setVxAvatar("https://oss.youlai.tech/youlai-boot/2023/05/16/811270ef31f548af9cffc026dfc3777b.gif");
//            //添加用户userMapper.insert(user);//  默认分配权限用户List<Long> roleIdList = new ArrayList<>();roleIdList.add(17L);//用户AssginRoleVo assginRoleVo = new AssginRoleVo().setUserId(Long.parseLong(user.getId())).setRoleIdList(roleIdList);roleService.doAssignRole(assginRoleVo);}}catch (Exception e){log.error("手机号用户注册--{}--异常", username,e);throw new CustomException(500,"手机号用户注册异常,请联系管理员~");}log.info("手机号用户注册--{}--结束", username);return this.userMapper.getUserNameByPhone(username);}}

 最后一步加上配置文件即可

SecurityConfig

java">
/*** Spring Security 权限配置** @author cws*/
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {// 自定义未认证处理类private final MyAuthenticationEntryPoint authenticationEntryPoint;// 自定义无权限访问处理类@Resourceprivate final MyAccessDeniedHandler accessDeniedHandler;// Redis操作模板@Autowiredprivate final RedisTemplate<String, Object> redisTemplate;@Autowiredprivate SmsCodeAuthenticationProvider smsCodeAuthenticationProvider;@Autowiredprivate SysUserDetailsService userDetailsService;private final Filter globalSpringSecurityExceptionHandler = new CustomSecurityExceptionHandler();@AutowiredLoginFailHandler loginFailHandler;@AutowiredLoginSuccessHandler loginSuccessHandler;@Resourceprivate StringRedisTemplate stringRedisTemplate;/*** 配置Spring Security过滤器链。** @param http HttpSecurity对象,用于构建安全配置* @return 构建好的SecurityFilterChain对象* @throws Exception 配置过程中可能抛出的异常*/@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http,AuthenticationManager authenticationManager) throws Exception {http.authorizeHttpRequests(requestMatcherRegistry ->// 配置请求授权规则//登录路径公开访问requestMatcherRegistry.requestMatchers(SecurityConstants.LOGIN_PATH,SecurityConstants.LOGOUT_PATH,SecurityConstants.VERIFY_TREE_PATH,SecurityConstants.GET_PHONE_CODE_PATH).permitAll()// 其他所有请求都需要认证.anyRequest().authenticated())// 禁用Session创建.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))// 配置异常处理.exceptionHandling(httpSecurityExceptionHandlingConfigurer ->httpSecurityExceptionHandlingConfigurer// 设置未认证处理入口.authenticationEntryPoint(authenticationEntryPoint)// 设置无权限访问处理.accessDeniedHandler(accessDeniedHandler))// 禁用CSRF保护.csrf(AbstractHttpConfigurer::disable).formLogin(AbstractHttpConfigurer::disable).httpBasic(AbstractHttpConfigurer::disable).logout(AbstractHttpConfigurer::disable).sessionManagement(AbstractHttpConfigurer::disable).csrf(AbstractHttpConfigurer::disable)// requestCache用于重定向,前后端分析项目无需重定向,requestCache也用不上.requestCache(cache -> cache.requestCache(new NullRequestCache()));//添加手机号登陆过滤器SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();smsCodeAuthenticationProvider.setUserDetailsService(userDetailsService);smsCodeAuthenticationProvider.setStringRedisTemplate(stringRedisTemplate);SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter(new ProviderManager(List.of(smsCodeAuthenticationProvider)),loginSuccessHandler,loginFailHandler);http.addFilterBefore(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);// JWT 校验过滤器http.addFilterBefore(new JwtValidationFilter(redisTemplate), UsernamePasswordAuthenticationFilter.class);// 其他未知异常. 尽量提前加载。http.addFilterBefore(globalSpringSecurityExceptionHandler, SecurityContextHolderFilter.class);// 构建并返回过滤器链return http.build();}/*** 不走过滤器链的放行配置*/@Beanpublic WebSecurityCustomizer webSecurityCustomizer() {// 忽略指定路径的安全检查return (web) -> web.ignoring().requestMatchers("/api/v1/auth/captcha","/webjars/**","/doc.html","/swagger-resources/**","/v3/api-docs/**","/swagger-ui/**","/swagger-ui.html","/ws/**","/ws-app/**");}/*** 密码编码器*/@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}/*** 手动注入AuthenticationManager,用于处理认证和授权请求。** @param authenticationConfiguration 认证配置对象* @return AuthenticationManager对象* @throws Exception 配置过程中可能抛出的异常*/@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {// 获取认证管理器实例return authenticationConfiguration.getAuthenticationManager();}}

认证成功或者失败的处理器

java">@Component
public class LoginSuccessHandler extendsAbstractAuthenticationTargetUrlRequestHandler implements AuthenticationSuccessHandler {@Autowiredprivate ApplicationEventPublisher applicationEventPublisher;;public LoginSuccessHandler() {this.setRedirectStrategy(new RedirectStrategy() {@Overridepublic void sendRedirect(HttpServletRequest request, HttpServletResponse response, String url)throws IOException {// 更改重定向策略,前后端分离项目,后端使用RestFul风格,无需做重定向// Do nothing, no redirects in REST}});}@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,Authentication authentication) throws IOException {Object principal = authentication.getPrincipal();if (principal == null || !(principal instanceof UserDetails)) {throw new CustomException(500, "登陆认证成功后,authentication.getPrincipal()返回的Object对象必须是:UserLoginInfo!");}String token= JwtUtils.generateToken(authentication);LoginVo loginVo = new LoginVo();loginVo.setTokenType("Bearer ");loginVo.setAccessToken(token);// 虽然APPLICATION_JSON_UTF8_VALUE过时了,但也要用。因为Postman工具不声明utf-8编码就会出现乱码response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);PrintWriter writer = response.getWriter();writer.print(JSONUtil.toJsonStr(Result.success(loginVo)));writer.flush();writer.close();}
}@Component
public class LoginFailHandler implements AuthenticationFailureHandler {@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,AuthenticationException exception) throws IOException, ServletException {String errorMessage = exception.getMessage();response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);PrintWriter writer = response.getWriter();writer.print(JSONUtil.toJsonStr(Result.fail(errorMessage)));writer.flush();writer.close();}
}

总结:重点在于理解上面的三个类及整个Security的认证流程即可实现。

关注后面更新接入第三方做认证的案例及阿里云短信服务~~~

 


http://www.ppmy.cn/embedded/116362.html

相关文章

解决uniapp开发的app,手机预览,上下滑动页面,页面出现拉伸,抖动的效果问题,

在pages.json文件里“globalStyle”下面的"app-plus"里加入"bounce": "none"即可 "app-plus": { "bounce": "none", //关闭窗口回弹效果 }

什么是后端开发 ?

后端&#xff0c;亦称“服务器端开发”。同样&#xff0c;在后端服务器和浏览器或应用程序之间存储网站、应用数据和中间媒介的服务器都属于后端。也可以这么说&#xff0c;在应用程序或网站的屏幕上看不到的所有东西都是前端的后端。那么后端开发的基本流程是什么呢&#xff1…

mysql学习教程,从入门到精通,SQL 更新数据(UPDATE 语句)(17)

1、SQL 更新数据&#xff08;UPDATE 语句&#xff09; SQL UPDATE 需要指定要更新的表、要修改的列以及新值&#xff0c;并且通常会通过WHERE子句来指定哪些行需要被更新。下面是一个简单的示例&#xff0c;说明如何使用UPDATE语句。 假设我们有一个名为employees的表&#xf…

开关磁阻电机(SRM)系统的matlab性能仿真与分析

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 4.1 SRM的基本结构 4.2 SRM的电磁关系 4.3 SRM的输出力矩 5.完整工程文件 1.课题概述 开关磁阻电机(SRM)系统的matlab性能仿真与分析&#xff0c;对比平均转矩vs相电流&#xff0c;转矩脉动vs相电流&a…

Linux_openEuler_24.03部署Oracle 19c部署安装实测验证(无图形桌面-RPM模式)

前言&#xff1a; 近期对openeuler有点兴趣&#xff0c;顺带在做个开发数据仓项目&#xff0c;那就正好安装个环境做个调测&#xff0c;做个记录放上来做个备录给到大家参考。 openEuler 24.03 LTS&#xff1a;四大升级&#xff0c; 首个AI原生开源操作系统正式发布 openEuler …

Linux 开发工具(vim、gcc/g++、make/Makefile)+【小程序:进度条】-- 详解

目录 一、Linux软件包管理器 - yum&#xff08;ubuntu用apt代替yum&#xff09;1、Linux下安装软件的方式2、认识 yum3、查找软件包4、安装软件5、如何实现本地机器和云服务器之间的文件互传 二、Linux编辑器 - vim1、vim 的基本概念2、vim 下各模式的切换3、vim 命令模式各命令…

开源 AI 智能名片链动 2+1 模式 O2O 商城小程序在社群活动中的应用与时机选择

摘要&#xff1a;本文探讨了开源 AI 智能名片链动 21 模式 O2O 商城小程序在社群经济中的重要性&#xff0c;着重分析了如何借助该小程序适时举办大型活动以维持和引爆社群活跃度。通过对活动时机选择的研究&#xff0c;强调了针对社群用户量身定制活动时机的必要性&#xff0c…

oracle 插入date日期类型的数据、插入从表中查出的数据,使用表中的默认数据

date sysdate to_date 插入从表中查出的数据 方式一 方式二 或者指定列名称 下边这个案例的前提是指定列插入&#xff0c;如果不指定&#xff0c;则也是默认的