一、登录验证时,Spring Security怎么帮我们查的用户信息

  • 之前说个SysLoginService有登录验证的方法
/**
     * 登录验证
     * 
     * @param username 用户名
     * @param password 密码
     * @param code 验证码
     * @param uuid 唯一标识
     * @return 结果
     */
    public String login(String username, String password, String code, String uuid)
    {
        boolean captchaOnOff = configService.selectCaptchaOnOff();
        // 验证码开关
        if (captchaOnOff)
        {   //校验验证码
            validateCaptcha(username, code, uuid);
        }
        // 用户验证
        Authentication authentication = null;
        try
        {
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            authentication = authenticationManager
                    .authenticate(new UsernamePasswordAuthenticationToken(username, password));
            //UsernamePasswordAuthenticationToken [Principal=com.ruoyi.common.core.domain.model.LoginUser@748cc4df, Credentials=[PROTECTED], Authenticated=true, 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());
            }
        }
        //记录登录信息=====新增系统登录日志
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));

        LoginUser loginUser = (LoginUser) authentication.getPrincipal();//com.ruoyi.common.core.domain.model.LoginUser@748cc4df

        //记录登录信息=====修改用户基本信息(最后IP、时间)
        recordLoginInfo(loginUser.getUserId());
        // 生成token
        return tokenService.createToken(loginUser);
    }

今天主要说的是其中的
authentication = authenticationManager .authenticate(new UsernamePasswordAuthenticationToken(username, password)); 这一段代码怎么(也就是spring security怎么帮我们查的用户名、密码及用户信息)的【执行流程】===通过DEBUG来仔细看看

二、开始

  • 第一次进入
  • 若依微服务跳过登录 若依登录验证_spring boot

  • 走到后面的其中一个方法determineUsername确定用户名,方法位于【抽象用户详细信息身份验证提供者org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider
private String determineUsername(Authentication authentication) {
		return (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();
	}
  • 可以看到这个方法是三目,如果authentication.getPrincipal() == null【用户名不是空】,则getName()
  • 上面这个方法在哪里使用呢
  • 若依微服务跳过登录 若依登录验证_spring boot_02

  • 检索用户方法retrieveUser
  • 若依微服务跳过登录 若依登录验证_用户信息_03

  • 之后走到若依自己的用户服务com.ruoyi.framework.web.service.UserDetailsServiceImpl
  • 若依微服务跳过登录 若依登录验证_spring boot_04

()!!!到这里就没什么好说的了,懂得都懂。

  • 接着user变量就有了数据
  • 若依微服务跳过登录 若依登录验证_用户信息_05


  • 若依微服务跳过登录 若依登录验证_用户信息_06

  • 也可以看看上面,我们之前说的先查username,和没有走缓存
  • 若依微服务跳过登录 若依登录验证_若依微服务跳过登录_07

  • 在查询结束后,还会有一些认证preAuthenticationChecks、preAuthenticationChecks、additionalAuthenticationChecks
  • 若依微服务跳过登录 若依登录验证_用户信息_08

  • 最后返回【创建成功认证】createSuccessAuthentication
  • 若依微服务跳过登录 若依登录验证_spring boot_09

  • createSuccessAuthentication方法内部
  • 若依微服务跳过登录 若依登录验证_spring boot_10

  • 创建认证
  • 若依微服务跳过登录 若依登录验证_若依微服务跳过登录_11

  • 代码中的官方注解
// 确保我们返回用户提供的原始凭据, 
// 因此即使使用编码密码,后续尝试也能成功。 
// 还要确保我们返回原始的 getDetails(),以便将来缓存到期后的 
// 身份验证事件包含详细信息

三、最后说下一个常用且常见的方法getPrincipal()

位于org.springframework.security.authentication.UsernamePasswordAuthenticationToken

若依微服务跳过登录 若依登录验证_用户名_12

  • 这就是getPrincipal()的数据了,就是拿user
  • 若依微服务跳过登录 若依登录验证_ci_13


  • 若依微服务跳过登录 若依登录验证_若依微服务跳过登录_14

  • 然后,挂一张依赖图看看
  • 若依微服务跳过登录 若依登录验证_用户信息_15

四、补充上面检索用户信息【走自己的方法】

  • com.ruoyi.framework.web.service.UserDetailsServiceImpl 这个服务实现,实现自己的方法去数据查用户信息,并且给LoginUser实体补充权限菜单
/**
 * 用户验证处理
 *
 * @author ruoyi
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService
{
    private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);

    @Autowired
    private ISysUserService userService;

    @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("登录用户:" + username + " 不存在");
        }
        else if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
        {
            log.info("登录用户:{} 已被删除.", username);
            throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
        }
        else if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
        {
            log.info("登录用户:{} 已被停用.", username);
            throw new ServiceException("对不起,您的账号:" + username + " 已停用");
        }

        return createLoginUser(user);
    }

    public UserDetails createLoginUser(SysUser user)
    {
        /**user来自数据库
         * LoginUser登录中用户需要的信息
         * 这里使用了userId、deptId、整个用户信息user、用户对应权限【即用户对应的菜单】
         */
        return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));
    }
}
  • 关键点在于public class UserDetailsServiceImpl implements UserDetailsService这实现了Spring Security的接口
  • 之前说的时候,经常看到的LoginUser也是实现了Spring SecurityUserDetails
  • 在登录时,使用的实体是专门准备的LoginUser

实体挂一份在这

public class LoginUser implements UserDetails
{
    private static final long serialVersionUID = 1L;

    /**
     * 用户ID
     */
    private Long userId;

    /**
     * 部门ID
     */
    private Long deptId;

    /**
     * 用户唯一标识
     */
    private String token;

    /**
     * 登录时间
     */
    private Long loginTime;

    /**
     * 过期时间
     */
    private Long expireTime;

    /**
     * 登录IP地址
     */
    private String ipaddr;

    /**
     * 登录地点
     */
    private String loginLocation;

    /**
     * 浏览器类型
     */
    private String browser;

    /**
     * 操作系统
     */
    private String os;

    /**
     * 权限列表
     */
    private Set<String> permissions;

    /**
     * 用户信息
     */
    private SysUser user;
    //...其他方法省略,感兴趣的去项目中看吧
}

那么,就到这里了。\(^o^)/~!!!