【Ruoyi第三方登录】对Ruoyi添加手机登录和邮箱登录的功能

 使用的是2023年若依SpringBoot分离版练习的

 首先,添加两种登录方式的Controller实现

/**
     * 邮箱登录的方法
     *
     * @param loginByEmailBody 登录信息
     * @return 结果
     */
    @PostMapping("/emailLogin")
    public AjaxResult emailLogin(@RequestBody LoginByEmailBody loginByEmailBody) {
        //找到用户
        AjaxResult ajax = AjaxResult.success();
        // 生成令牌
        String token = loginService.loginByEmail(loginByEmailBody.getEmail(), loginByEmailBody.getCode(),
                loginByEmailBody.getUuid());
        ajax.put(Constants.TOKEN, token);
        return ajax;
    }

    /**
     * 手机登录的方法
     *
     * @param LoginByPhoneBody 登录信息
     * @return 结果
     */
    @PostMapping("/phoneLogin")
    public AjaxResult phoneLogin(@RequestBody LoginByPhoneBody loginBody) {
        AjaxResult ajax = AjaxResult.success();
        // 生成令牌
        String token = loginService.loginByPhone(loginBody.getPhone(), loginBody.getCode(),
                loginBody.getUuid());
        ajax.put(Constants.TOKEN, token);
        return ajax;
    }

 接下来以手机登录为例实现

    /**
     * 手机验证码登录验证
     *
     * @param phone 手机号
     * @param code      验证码
     * @param uuid      唯一标识
     * @return 结果
     */
    public String loginByPhone(String phone, String code, String uuid) {
        String userName = userService.selectUserByPhonenumber(phone);
        return otherLogin(userName,code,uuid);
    }

    /**
     * 邮箱验证码登录验证
     *
     * @param email 邮箱号
     * @param code      验证码
     * @param uuid      唯一标识
     * @return 结果
     */
    public String loginByEmail(String email, String code, String uuid) {
        String userName = userService.selectUserByEmail(email);
        // 生成token
        return otherLogin(userName,code,uuid);
    }
public String otherLogin(String userName, String code, String uuid){

        // 验证码校验
        validateCaptcha(userName, code, uuid);
        // 登录前置校验
        otherLoginPreCheck(userName);
        // 用户验证
        Authentication authentication;
        try {
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            SmsCodeAuthenticationToken smsCodeAuthenticationToken = new SmsCodeAuthenticationToken(userName);
            System.out.println(smsCodeAuthenticationToken);
            AuthenticationContextHolder.setContext(smsCodeAuthenticationToken);
            authentication = authenticationManager.authenticate(smsCodeAuthenticationToken);
        }
//        SmsCodeAuthenticationToken [Principal=admin, Credentials=[PROTECTED], Authenticated=false, Details=null, Granted Authorities=[]]
        catch (Exception e) {
            if (e instanceof BadCredentialsException) {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                throw new UserPasswordNotMatchException();
            } 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();
        return tokenService.createToken(loginUser);
    }

 public void otherLoginPreCheck(String username) {
        // 用户名为空 错误
        if (StringUtils.isEmpty(username)) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("not.null")));
            throw new UserNotExistsException();
        }
        // 用户名不在指定范围内 错误
        if (username.length() < UserConstants.USERNAME_MIN_LENGTH
                || username.length() > UserConstants.USERNAME_MAX_LENGTH) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
            throw new UserPasswordNotMatchException();
        }
        // IP黑名单校验
        String blackStr = configService.selectConfigByKey("sys.login.blackIPList");
        if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr())) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("login.blocked")));
            throw new BlackListException();
        }
    }

这里登录前置校验没必要自己写(以下是一开始写的otherLoginPreCheck发现有很多重复代码):

所以还是用若依的但是没有密码:

// 登录前置校验
loginPreCheck(userName,null);

然后改若依代码加上判空:

public void loginPreCheck(String username, String password)
{
// 用户名或密码为空 错误
if (StringUtils.isEmpty(username))
{
        if(password!=null&&StringUtils.isEmpty(password))
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username,Constants.LOGIN_FAIL, MessageUtils.message("not.null")));
        throw new UserNotExistsException();
}
if(password!=null)
// 密码如果不在指定范围内 错误
        if (password.length() < UserConstants.PASSWORD_MIN_LENGTH||password.length() > UserConstants.PASSWORD_MAX_LENGTH)
        {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username,Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                throw new UserPasswordNotMatchException();
        }
......
}

这样一来,只要调用时password为null,就可以实现无密码登录了。

下面继续深入实现:

这是什么?为什么需要实现自己的otherlogin方法而不用若依的login代码?继续看

SmsCodeAuthenticationToken类:

package com.ruoyi.framework.security.sms;

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

import javax.security.auth.Subject;
import java.util.Collection;

/**
 * 短信登录 AuthenticationToken,模仿 UsernamePasswordAuthenticationToken 实现
 */
public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {

    private static final long serialVersionUID = 550L;

    /**
     * 在 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);
    }

    @Override
    public Object getCredentials() {
        return null;
    }

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

    @Override
    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);
    }

    @Override
    public void eraseCredentials() {
        super.eraseCredentials();
    }

    @Override
    public boolean implies(Subject subject) {
        return super.implies(subject);
    }
}

sendEmailMessage和sendPhoneMessage的写法(链接不好找了,我就直接贴代码了):

注:我是使用榛子云短信平台发短信,也可以使用腾讯云(每月100条免费短信),不推荐阿里云(很难通过他的认证发短信)

邮箱开通要配置对应的权限(比如qq要开通stmp)

邮箱还需要在yml中写以下配置

# 邮箱配置
mail:
host: smtp.qq.com
username: xxxxxxxx@qq.com(发送方的邮箱)
password: (根据自己邮箱的密码填)
default-encoding: utf-8

package com.ruoyi.web.controller.tool;

import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.model.LoginByEmailBody;
import com.ruoyi.common.core.domain.model.LoginByPhoneBody;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.uuid.IdUtils;
import com.zhenzi.sms.ZhenziSmsClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@RestController
public class OtherLoginMethods {
    @Value("${spring.mail.username}")
    String from;   // 邮件发送人
    @Autowired
    JavaMailSender mailSender;
    @Autowired
    private RedisCache redisCache;
    @PostMapping("/sendEmailMessage")
    public AjaxResult sendEmailMsg(@RequestBody LoginByEmailBody loginByEmailBody) {
        AjaxResult ajax = AjaxResult.success();
        String subject = "登录验证码";
        String email=loginByEmailBody.getEmail();
        if (StringUtils.isNotNull(email)) {
            String code = ValidateCodeUtils.generateValidateCode(4).toString();
            String context = "欢迎回来!登录验证码为: " + code + ",五分钟内有效,请妥善保管!";
            SimpleMailMessage mailMessage = new SimpleMailMessage();
            mailMessage.setFrom(from);
            mailMessage.setTo(email);
            mailMessage.setSubject(subject);
            mailMessage.setText(context);
            // 真正的发送邮件操作,从 from到 to
            mailSender.send(mailMessage);
            // 保存验证码信息
            String uuid = IdUtils.simpleUUID();
            String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
            System.out.println(verifyKey);
            // 验证码由保存到session 优化为 缓存到Redis中,并且设置验证码的有效时间为 5分钟
            redisCache.setCacheObject(verifyKey, code, 5, TimeUnit.MINUTES);
            ajax.put("codes",code);
            ajax.put("uuid", uuid);
            System.out.println(ajax);
            return ajax;
        }
        return AjaxResult.error("验证码发送失败,请重新输入!");
    }

    @PostMapping("/sendPhoneMessage")
    public AjaxResult sendPhoneMsg(@RequestBody LoginByPhoneBody loginByPhoneBody) {
        AjaxResult ajax = AjaxResult.success();
        String phone=loginByPhoneBody.getPhone();
        if (StringUtils.isNotNull(phone)) {
            String code = ValidateCodeUtils.generateValidateCode(4).toString();
            String appId="你的appId";
            String appSecret="你的appSecret";
            ZhenziSmsClient client = new ZhenziSmsClient("https://sms_developer.zhenzikj.com",appId, appSecret);
            Map<String, Object> params = new HashMap<>();
            params.put("templateId","你的短信模板ID");
            params.put("number", phone);
            params.put("templateParams", new String[]{code, "5分钟"});
            //发送短信
            String result = null;
            try {
                result = client.send(params);
                System.out.println("result = " + result);
                //查看余额,查看当前剩余短信条数
                String balance = client.balance();
                System.out.println("balance = " + balance);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            // 保存验证码信息
            String uuid = IdUtils.simpleUUID();
            String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid;
            // 验证码由保存到session 优化为 缓存到Redis中,并且设置验证码的有效时间为 5分钟
            redisCache.setCacheObject(verifyKey, code, 5, TimeUnit.MINUTES);
            ajax.put("codes",code);
            ajax.put("uuid", uuid);
            System.out.println(ajax);
            return ajax;
        }
        return AjaxResult.error("验证码发送失败,请重新输入!");
    }
}

最后再把若依的SecurityConfig的白名单加上我们的接口路径就行了

// 对于登录login 注册register 验证码captchaImage 允许匿名访问
.antMatchers("/login", "/register", "/captchaImage","/sendEmailMessage","/emailLogin","/phoneLogin", "/captchaTelephone","/sendPhoneMessage").permitAll()

 到这里代码似乎完了,但是,我前后端一共改了30个文件相关代码!接下来是重头戏!!

1)截图处代码

AuthenticationContextHolder.setContext(smsCodeAuthenticationToken);

进入security源码分析 

在AuthenticationContextHolder类有Authentication类的本地线程池

private static final ThreadLocal<Authentication> contextHolder = new ThreadLocal<>();

先对Authentication的两个方法有个印象:

public interface Authentication extends Principal, Serializable {
...
Object getCredentials();
...
Object getPrincipal();
...
}

好,现在思考为什么若依会调用UsernamePasswordAuthenticationToken这个类

但是idea不支持源码跳转,我花了一会功夫确定了是个过滤器实现的:

package com.ruoyi.framework.security.filter;

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
{
@Autowired
private TokenService tokenService;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
LoginUser loginUser = tokenService.getLoginUser(request);
if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())) {
tokenService.verifyToken(loginUser);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}

chain.doFilter(request, response);
}

}

// 添加JWT filter
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

若依实现的JWT验证,在SecurityConfig挂载开启

所以,如果你此刻运行会发现你自定义的类不起作用,这是因为还要把自定义的类挂载到SecurityConfig配置文件中

以下是源代码:

package com.ruoyi.framework.config;


/**
 * spring security配置
 * 
 * @author ruoyi
 */
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
    /**
     * 自定义用户认证逻辑
     */
    @Autowired
    private UserDetailsService userDetailsService;

    /**
     * 认证失败处理类
     */
    @Autowired
    private AuthenticationEntryPointImpl unauthorizedHandler;

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

    /**
     * token认证过滤器
     */
    @Autowired
    private JwtAuthenticationTokenFilter authenticationTokenFilter;
    
    /**
     * 跨域过滤器
     */
    @Autowired
    private CorsFilter corsFilter;

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

    /**
     * 解决 无法直接注入 AuthenticationManager
     *
     * @return
     * @throws Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception
    {
        return super.authenticationManagerBean();
    }

    /**
     * anyRequest          |   匹配所有请求路径
     * access              |   SpringEl表达式结果为true时可以访问
     * anonymous           |   匿名可以访问
     * denyAll             |   用户不能访问
     * fullyAuthenticated  |   用户完全认证可以访问(非remember-me下自动登录)
     * hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问
     * hasAnyRole          |   如果有参数,参数表示角色,则其中任何一个角色可以访问
     * hasAuthority        |   如果有参数,参数表示权限,则其权限可以访问
     * hasIpAddress        |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
     * hasRole             |   如果有参数,参数表示角色,则其角色可以访问
     * permitAll           |   用户可以任意访问
     * rememberMe          |   允许通过remember-me登录的用户访问
     * authenticated       |   用户登录后可访问
     */
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception
    {
        // 注解标记允许匿名访问的url
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();
        permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll());

        httpSecurity
                // CSRF禁用,因为不使用session
                .csrf().disable()
                // 禁用HTTP响应标头
                .headers().cacheControl().disable().and()
                // 认证失败处理类
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                // 基于token,所以不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                // 过滤请求
                .authorizeRequests()
                // 对于登录login 注册register 验证码captchaImage 允许匿名访问
                .antMatchers("/login", "/register", "/captchaImage","/sendEmailMessage","/emailLogin","/phoneLogin", "/captchaTelephone","/sendPhoneMessage").permitAll()
            // 静态资源,可匿名访问
                .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
                .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated()
                .and()
                .headers().frameOptions().disable();
        // 添加Logout filter
        httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
        // 添加JWT filter
        httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        // 添加CORS filter
        httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
        httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
        ;
    }

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

    /**
     * 身份认证接口
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception
    {
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    }
}

 我们发现如下代码:

/**
* 自定义用户认证逻辑
*/
@Autowired
private UserDetailsService userDetailsService;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
}

这启示我们要自定义一个手机号邮箱号验证的用户认证逻辑类,实例化后并挂载

/**
* 自定义用户(手机号验证码)认证逻辑
*/
@Autowired
private OtherUserDetailsService userDetailsServiceByPhone;

/**
* 自定义用户(邮箱验证码)认证逻辑
*/
@Autowired
private OtherUserDetailsService userDetailsServiceByEmail; 

SmsCodeByPhoneAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeByPhoneAuthenticationProvider();
smsCodeAuthenticationProvider.setUserDetailsService(userDetailsServiceByPhone);
SmsCodeByEmailAuthenticationProvider smsCodeAuthenticationProvider2 = new SmsCodeByEmailAuthenticationProvider();
smsCodeAuthenticationProvider2.setUserDetailsService(userDetailsServiceByEmail);

// 添加CORS filter
httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class)
.authenticationProvider(smsCodeAuthenticationProvider)
.authenticationProvider(smsCodeAuthenticationProvider2);

 OtherUserDetailsService接口如下:

package com.ruoyi.framework.security.core;

import org.springframework.security.core.userdetails.UserDetails;

public interface OtherUserDetailsService {
UserDetails otherLoadUser(String o, int num) throws OtherLoginNotFoundException;
}

 该接口有2个用法和一个实现:

  手机同理只展示邮箱的用法:


package com.ruoyi.framework.security.sms;

import com.ruoyi.framework.security.core.OtherUserDetailsService;
import org.springframework.security.authentication.AuthenticationProvider;
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 SmsCodeByEmailAuthenticationProvider implements AuthenticationProvider {
    private OtherUserDetailsService userDetailsService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken) authentication;

        String email = (String) authenticationToken.getPrincipal();
        UserDetails userDetails = userDetailsService.otherLoadUser(email,1);

        // 此时鉴权成功后,应当重新 new 一个拥有鉴权的 authenticationResult 返回
        SmsCodeAuthenticationToken authenticationResult = new SmsCodeAuthenticationToken(userDetails, userDetails.getAuthorities());
        authenticationResult.setDetails(authenticationToken.getDetails());

        return authenticationResult;
    }


    @Override
    public boolean supports(Class<?> authentication) {
        // 判断 authentication 是不是 SmsCodeAuthenticationToken 的子类或子接口
        return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication);
    }

    public OtherUserDetailsService getUserDetailsService() {
        return userDetailsService;
    }

    public void setUserDetailsService(OtherUserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }
}

接下来是UserDetailsServiceImpl实现UserDetailsService的otherLoadUser方法。

对otherLoadUser(String username,int num)重载UserDetailsServiceImpl类的loadUserByUsername方法,通过设定开关值num使邮箱手机号这些无密码登录的方式不检验密码

package com.ruoyi.framework.web.service;

/**
 * 用户验证处理
 *
 * @author ruoyi
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService, OtherUserDetailsService
{
    private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);

    private int num=0;

    @Autowired
    private ISysUserService userService;
    
    @Autowired
    private SysPasswordService passwordService;

    @Autowired
    private SysPermissionService permissionService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
    {
        SysUser user = userService.selectUserByUserName(username);
        if (StringUtils.isNull(user))
        {
            log.info("登录用户:{} 不存在.", username);
            throw new ServiceException(MessageUtils.message("user.not.exists"));
        }
        else if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
        {
            log.info("登录用户:{} 已被删除.", username);
            throw new ServiceException(MessageUtils.message("user.password.delete"));
        }
        else if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
        {
            log.info("登录用户:{} 已被停用.", username);
            throw new ServiceException(MessageUtils.message("user.blocked"));
        }

        if (num==0) passwordService.validate(user);
        num=0;
        return createLoginUser(user);
    }
    @Override
    public UserDetails otherLoadUser(String username,int num) throws UsernameNotFoundException
    {
        this.num=num;
        return loadUserByUsername(username);
    }
    public UserDetails createLoginUser(SysUser user)
    {
        return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));
    }
}

此时我们回个头来说明Authentication的两个方法。我们知道,

UsernamePasswordAuthenticationToken(Object principal, Object credentials)

而UsernamePasswordAuthenticationToken是实现了接口Authentication的方法

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

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

以及

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

实际上你会发现,principal就是username,credentials就是password,所以对于不需要密码登录的方式简单的不用credentials就行了吗?

还记得 if (num==0) passwordService.validate(user);吗?这段代码的部分源码如下:

Authentication usernamePasswordAuthenticationToken = AuthenticationContextHolder.getContext();
String username = usernamePasswordAuthenticationToken.getName();
String password = usernamePasswordAuthenticationToken.getCredentials().toString();

它使用了 getCredentials!所以唯一使用了credentials的地方我们用num值“关掉”,则credentials就可以不用实现了。

最后,这只是后端代码,前端也有白名单和接口文件要改,我就先不展示了吧(懒),此时已经可以Postman测试通了。

  • 19
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值