海滨擎蟹

Spring Secuirty 使用手机号、短信验证码登录(身份认证)

前言

对于登录方式,PC 端后台一般会选择账号密码登录,如果是 ToC 的服务,也会支持手机号+短信验证码的登录方式,甚至如果有 APP,还可以支持扫码登录。当然还有腾讯系、阿里系的应用喜欢的三方授权登录。

移动端(国内)通常的应用都会优先选择手机号+短信验证码的方式,即使这样商家会有额外的短信成本负担。比如现在的微信手机号快速认证(所谓“一键登录”)组件已经开始堂而皇之的收费了,跟短信收费也差不太多,一条 3 分钱。说是该组件手机号认证也是有费用的,现在转移到商家头上。支付宝暂时没有发现这种情况。关键是,对于共享共电宝这种快速响应的场景,你还不得不使用它的快速认证服务,不然要先手机号获取验证码,步骤会多,用户体验会差。当然也是提供了手机号+验证码的登录方式就是了,用不用看用户自己。

Redis 实现

其实手机号+短信验证码登录之前做过一次,只不过实现方式不太一样。资产项目因为使用了微服务,有独立的用户中心。对于微服务不太了解,用户中心的代码也没有办法说改就改。所以当时就想着绕过现有的用户认证体系,再建一套独立的认证,最好还要简单一点的。资产 ERP 项目使用的是 华夏 ERP,里面刚好有一套账号认证以及 redis 管理 token,认证身份的方法。

管伊佳ERP(华夏ERP) UserController.java


    @Resource
    private UserService userService;

    ...

    @Resource
    private RedisService redisService;

    private static String SUCCESS = "操作成功";
    private static String ERROR = "操作失败";

    @PostMapping(value = "/login")
    @ApiOperation(value = "登录")
    public BaseResponseInfo login(@RequestBody UserEx userParam, HttpServletRequest request)throws Exception {
        BaseResponseInfo res = new BaseResponseInfo();
        try {
            userService.validateCaptcha(userParam.getCode(), userParam.getUuid());
            Map<String, Object> data = userService.login(userParam.getLoginName().trim(), userParam.getPassword().trim(), request);
            res.code = 200;
            res.data = data;
        } catch (BusinessRunTimeException e) {
            throw new BusinessRunTimeException(e.getCode(), e.getMessage());
        } catch(Exception e){
            logger.error(e.getMessage(), e);
            res.code = 500;
            res.data = "用户登录失败";
        }
        return res;
    }

主要需要关注两个服务 UserServiceRedisServiceUserService 主要实现登录的逻辑,RedisService 则是负责 token、captcha 等内容的管理(CRUD)。

接入存在的问题是,redis 配置序列化可能会重复或被覆盖(RedisService 中设置了序列化器)。如果已配置,可以直接注释掉序列化器的设置部分。

SpringBoot 调用 RedisTemplate 存储列表 redisTemplate.opsForList().rightPushAll(K var1, V... var2) 报错:xxxUser cannot be cast to java.lang.String

Spring Security 实现

在若依前后端分离版本里没有找到手机号、短信验证码登录的集成方法,所以只能求助于百度。

【Spring Security系列】如何用Spring Security集成手机验证码登录?五分钟搞定!

再结合百度 AI 给出的结果,主要需要新增、修改以下几个文件:

最后的 xxxAuthenticationFilter 是走过滤器处理,而非正常的控制器。有点类似 Laravel 框架中的中间件,拦截特定的请求做判断处理。也就是说,在这里可以使用过滤器做登录处理,也可以走正常请求控制器处理

首先是创建 SmsAuthenticationProvider - sms 身份认证方法提供者 和 SmsAuthenticationToken - 身份令牌信息(这里对应手机号、短信验证码)。

SmsAuthenticationProvider.java

package com.ruoyi.framework.manager.provider;

import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.utils.StringUtils;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;

public class SmsAuthenticationProvider implements AuthenticationProvider {

    private final UserDetailsService userDetailsService;

    private final RedisCache redisCache;

    public SmsAuthenticationProvider(UserDetailsService userDetailsService, RedisCache redisCache) {
        this.userDetailsService = userDetailsService;
        this.redisCache = redisCache;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

//        SmsAuthenticationToken token = (SmsAuthenticationToken) authentication;
        String phoneNumber = (String) authentication.getPrincipal(); // 手机号
        String code = (String) authentication.getCredentials(); // 验证码

        // 验证验证码是否正确
        Integer codeValid = validateSmsCode(phoneNumber, code);
        if (codeValid == 1) {
            throw new BadCredentialsException("验证码已过期.");
        }

        if (codeValid == 2) {
            throw new BadCredentialsException("验证码错误.");
        }

        // 加载用户详情
        UserDetails userDetails = userDetailsService.loadUserByUsername(phoneNumber);
        if (userDetails == null) {
            throw new BadCredentialsException("未找到对应的用户,请联系平台工作人员");
        }

        // 创建已认证的Authentication
        return new SmsAuthenticationToken(userDetails, null, userDetails.getAuthorities());
    }

    public Integer validateSmsCode(String phoneNumber, String code)
    {
        String verifyKey = CacheConstants.SMS_CODE_KEY + StringUtils.nvl(phoneNumber, "");
        String smsCode = redisCache.getCacheObject(verifyKey);
        if (smsCode == null)
        {
            return 1;
        }
        if (!code.equalsIgnoreCase(smsCode))
        {
            return 2;
        }
        redisCache.deleteObject(verifyKey);
        return 0;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return SmsAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

SmsAuthenticationToken.java

package com.ruoyi.framework.manager.provider;

import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;

import java.util.Collection;

public class SmsAuthenticationToken extends AbstractAuthenticationToken {

    private final Object principal; // 手机号
    private Object credentials; // 验证码

    public SmsAuthenticationToken(Object principal, Object credentials) {
        super(null);
        this.principal = principal;
        this.credentials = credentials;
        this.setAuthenticated(false);
    }

    public SmsAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        super.setAuthenticated(true);
    }

    @Override
    public Object getCredentials() {
        return this.credentials;
    }

    @Override
    public Object getPrincipal() {
        return this.principal;
    }

    @Override
    public void eraseCredentials() {
        super.eraseCredentials();
        this.credentials = null;
    }
}

两个类一个继承抽象类,一个实现接口,所以需要重写一些必要的方法。

之后就是将 SmsAuthenticationProvider 实例配置到 SecurityConfig 中的 ProviderManager - 认证提供者的管理器中。

SecurityConfig.java

package com.ruoyi.framework.config;

import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.framework.config.properties.PermitAllUrlProperties;
import com.ruoyi.framework.manager.provider.SmsAuthenticationProvider;
import com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter;
import com.ruoyi.framework.security.filter.SmsAuthenticationFilter;
import com.ruoyi.framework.security.handle.AuthenticationEntryPointImpl;
import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl;
import com.ruoyi.framework.security.handle.SmsAuthenticationFailureHandler;
import com.ruoyi.framework.security.handle.SmsAuthenticationSuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.web.filter.CorsFilter;

/**
 * spring security配置
 * 
 * @author ruoyi
 */
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
@Configuration
public class SecurityConfig
{
    /**
     * 自定义用户认证逻辑
     */
    @Autowired
    private UserDetailsService userDetailsService;
    
    /**
     * 认证失败处理类
     */
    @Autowired
    private AuthenticationEntryPointImpl unauthorizedHandler;

    /**
     * 退出处理类
     */
    @Autowired
    private LogoutSuccessHandlerImpl logoutSuccessHandler;

    /**
     * Sms 认证失败处理累
     */
    @Autowired
    private SmsAuthenticationFailureHandler smsAuthenticationFailureHandler;

    /**
     * Sms 认证成功处理累
     */
    @Autowired
    private SmsAuthenticationSuccessHandler smsAuthenticationSuccessHandler;

    /**
     * token认证过滤器
     */
    @Autowired
    private JwtAuthenticationTokenFilter authenticationTokenFilter;

    /**
     * 跨域过滤器
     */
    @Autowired
    private CorsFilter corsFilter;

    /**
     * 允许匿名访问的地址
     */
    @Autowired
    private PermitAllUrlProperties permitAllUrl;

    @Autowired
    private RedisCache redisCache;

    /**
     * 身份验证实现
     */
    @Bean
    public AuthenticationManager authenticationManager()
    {
        // 账号密码 认证
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userDetailsService);
        daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());
        
        // Sms 认证
        SmsAuthenticationProvider smsAuthenticationProvider = new SmsAuthenticationProvider(userDetailsService, redisCache);
        return new ProviderManager(smsAuthenticationProvider, daoAuthenticationProvider);
    }

//    @Bean
//    public SmsAuthenticationFilter smsAuthenticationFilter() throws Exception {
//        SmsAuthenticationFilter filter = new SmsAuthenticationFilter();
//        filter.setAuthenticationManager(authenticationManager());  // 设置认证管理器
//        filter.setAuthenticationSuccessHandler(smsAuthenticationSuccessHandler); // 设置成功处理器
//        filter.setAuthenticationFailureHandler(smsAuthenticationFailureHandler); // 设置失败处理器
//        return filter;
//    }

    /**
     * anyRequest          |   匹配所有请求路径
     * access              |   SpringEl表达式结果为true时可以访问
     * anonymous           |   匿名可以访问
     * denyAll             |   用户不能访问
     * fullyAuthenticated  |   用户完全认证可以访问(非remember-me下自动登录)
     * hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问
     * hasAnyRole          |   如果有参数,参数表示角色,则其中任何一个角色可以访问
     * hasAuthority        |   如果有参数,参数表示权限,则其权限可以访问
     * hasIpAddress        |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
     * hasRole             |   如果有参数,参数表示角色,则其角色可以访问
     * permitAll           |   用户可以任意访问
     * rememberMe          |   允许通过remember-me登录的用户访问
     * authenticated       |   用户登录后可访问
     */
    @Bean
    protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception
    {
        return httpSecurity
            // CSRF禁用,因为不使用session
            .csrf(csrf -> csrf.disable())
            // 禁用HTTP响应标头
            .headers((headersCustomizer) -> {
                headersCustomizer.cacheControl(cache -> cache.disable()).frameOptions(options -> options.sameOrigin());
            })
            // 认证失败处理类
            .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
            // 基于token,所以不需要session
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            // 注解标记允许匿名访问的url
            .authorizeHttpRequests((requests) -> {
                permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll());
                // 对于登录login 注册register 验证码captchaImage 允许匿名访问
                requests.antMatchers("/login", "/register", "/captchaImage", "/api/worker/login", "/api/worker/sendSms").permitAll()
                    // 静态资源,可匿名访问
                    .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
                    .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
                    // 除上面外的所有请求全部需要鉴权认证
                    .anyRequest().authenticated();
            })
            // 添加Logout filter
            .logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler))
            // 添加JWT filter
            .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
//            .addFilterBefore(smsAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
            // 添加CORS filter
            .addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class)
            .addFilterBefore(corsFilter, LogoutFilter.class)
            .build();
    }

    /**
     * 强散列哈希加密实现
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder()
    {
        return new BCryptPasswordEncoder();
    }
}

这是若依框架已有的安全配置,通过 ProviderManager 初始化需要的认证提供者,添加 authenticationManager bean。

当然,百度 AI 中的是通过继承 WebSecurityConfigurerAdapter 重写 configure 方法,来添加认证提供者:


@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Autowired
    private SmsAuthenticationProvider authenticationProvider;
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(authenticationProvider);
    }
 
    // 其他配置...
}

两者添加的方式稍微有点不同,并且 WebSecurityConfigurerAdapter 已经被标注为弃用了,所以优先考虑若依中的安全配置写法。

最后就是控制器、服务调用 authenticationManager

使用控制器

控制器:

    /**
     * 登录方法
     *
     * @param loginBody 登录信息
     * @return 结果
     */
    @PostMapping("/login")
    @ApiOperation(value = "登录方法", notes = "登录方法")
    @RepeatSubmit
    public R<String> login(@RequestBody WorkerLoginBody loginBody)
    {
        // 生成令牌
        String token = loginService.workerLogin(loginBody.getPhoneNumber(), loginBody.getCode());
        return R.ok(token);
    }

服务:

   /**
     * 登录验证
     *
     * @param phoneNumber 用户名
     * @param code 验证码
     * @return 结果
     */
    public String workerLogin(String phoneNumber, String code)
    {
        // 获取 username
        SysUser user = userService.selectUserByPhoneNumber(phoneNumber);
        if (user == null) {
            throw new RuntimeException("账号不存在!");
        }
        // 验证码校验
        validateSmsCode(phoneNumber, code);
        String username = user.getUserName();
        // 用户验证
        Authentication authentication = null;
        try
        {
            // 加载用户详情
            SmsAuthenticationToken authenticationToken = new SmsAuthenticationToken(phoneNumber, code);
            AuthenticationContextHolder.setContext(authenticationToken);
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            authentication = authenticationManager.authenticate(authenticationToken);
        }
        catch (Exception e)
        {
            if (e instanceof BadCredentialsException)
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
                throw new CaptchaException();
            }
            else
            {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
                throw new ServiceException(e.getMessage());
            }
        }
        finally
        {
            AuthenticationContextHolder.clearContext();
        }
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        recordLoginInfo(loginUser.getUserId());
        // 生成token
        return tokenService.createToken(loginUser);
    }

authenticationManager 会根据传入的令牌信息类型,判断调用哪一个认证提供者的 authenticate() 方法。对于返回的 authentication 处理、登录日志、生成 token 都是套用的若依框架中的账号密码登录的默认处理。

附上发送验证码的方法:

   public void sendSmsCode(String phoneNumber) {
        // 获取 username
        SysUser user = userService.selectUserByPhoneNumber(phoneNumber);
        if (user == null) {
            throw new RuntimeException("账号不存在!");
        }
        String smsTemplate = "您的验证码为${code},10分钟内有效。";
        String sign = "【xxx】"; //短信签名
        String code = RandomUtils.randomInt(4);
        Map<String, String> valuesMap = new HashMap();
        valuesMap.put("code", StringUtils.isNotEmpty(code) ? code : "8888");//手机验证码
        StringSubstitutor sub = new StringSubstitutor(valuesMap);
        String content = sub.replace(smsTemplate);
        try {
            // 发送短信
            smsService.sendSms(phoneNumber, content, "xx", sign);
        } catch (Exception e) {
            throw new RuntimeException("发送失败!");
        } finally {
            // 增加发送日志
        }
        // 存储验证码
        String verifyKey = CacheConstants.SMS_CODE_KEY + StringUtils.nvl(phoneNumber, "");
        redisCache.setCacheObject(verifyKey, code, 10 * 60, TimeUnit.SECONDS);
    }

短信模版字符串使用了字符变量替换方法,需要 commons-text 依赖包,如果没有,也可以直接字符串拼接。

使用过滤器

如果想要使用过滤器来处理,就需要将上面安全配置中关于 filter 部分注释去掉。.addFilterBefore(smsAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) 是将 sms 过滤器添加到用户名密码认证过滤器前面,不过测试发现,不加也不影响两种不同路由请求的正确处理。

SmsAuthenticationFilter.java

package com.ruoyi.framework.security.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.framework.manager.provider.SmsAuthenticationToken;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;

public class SmsAuthenticationFilter extends AbstractAuthenticationProcessingFilter {


    public static final String PHONE_KEY = "phoneNumber";  // 手机号字段
    public static final String CAPTCHA_KEY = "code";  // 验证码字段

    private boolean postOnly = true;
    private final ObjectMapper objectMapper = new ObjectMapper();

    public SmsAuthenticationFilter() {
        super("/api/worker/login"); // 拦截短信验证码登录请求
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }

        String phone;
        String captcha;

        try {
            // 读取请求体中的 JSON 数据并解析
            Map<String, String> requestBody = objectMapper.readValue(request.getInputStream(), Map.class);
            phone = requestBody.get(PHONE_KEY);  // 获取手机号
            captcha = requestBody.get(CAPTCHA_KEY);  // 获取验证码
        } catch (IOException e) {
            throw new AuthenticationServiceException("Failed to parse authentication request body", e);
        }

        if (phone == null) {
            phone = "";
        }

        if (captcha == null) {
            captcha = "";
        }

        phone = phone.trim();

        // 创建验证请求的 Token
        SmsAuthenticationToken authRequest = new SmsAuthenticationToken(phone, captcha);

        return this.getAuthenticationManager().authenticate(authRequest);
    }

    public void setPostOnly(boolean postOnly) {
        this.postOnly = postOnly;

    }

}

此外,还有认证成功和认证失败的处理类。如果不加,失败会抛出异常会走到公共的认证失败处理类,成功会返回 / 请求内容。

{
  "timestamp": "2024-09-25T15:57:32.185+08:00",
  "status": 401,
  "error": "Unauthorized",
  "message": "Unauthorized",
  "path": "/api/worker/login"
}

SmsAuthenticationSuccessHandler.java

package com.ruoyi.framework.security.handle;

import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.framework.manager.AsyncManager;
import com.ruoyi.framework.manager.factory.AsyncFactory;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;

@Component
public class SmsAuthenticationSuccessHandler implements AuthenticationSuccessHandler, Serializable {

    private static final long serialVersionUID = 1L;

    @Autowired
    private TokenService tokenService;

    @Autowired
    private ISysUserService userService;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginUser.getUsername(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
        recordLoginInfo(loginUser.getUserId());
        // 生成token
        ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.success("success", tokenService.createToken(loginUser))));
    }

    /**
     * 记录登录信息
     *
     * @param userId 用户ID
     */
    public void recordLoginInfo(Long userId)
    {
        SysUser sysUser = new SysUser();
        sysUser.setUserId(userId);
        sysUser.setLoginIp(IpUtils.getIpAddr());
        sysUser.setLoginDate(DateUtils.getNowDate());
        userService.updateUserProfile(sysUser);
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
        AuthenticationSuccessHandler.super.onAuthenticationSuccess(request, response, chain, authentication);
    }
}

SmsAuthenticationFailureHandler.java

package com.ruoyi.framework.security.handle;

import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.exception.user.CaptchaException;
import com.ruoyi.common.utils.ServletUtils;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;

/**
 * 认证失败处理类 返回未授权
 * 
 * @author ruoyi
 */
@Component
public class SmsAuthenticationFailureHandler implements AuthenticationFailureHandler, Serializable
{
    private static final long serialVersionUID = 1L;

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        int code = HttpStatus.ERROR;
        String msg = exception.getMessage();
        ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg)));
    }
}

当前页面是本站的「Google AMP」版。查看和发表评论请点击:完整版 »