若依--自定义loadUserByUsername参数入参

若依--自定义loadUserByUsername参数入参

前言

在使用若依的Security的登录认证时,默认只能使用用户名去查询sysUser,当我需要额外的参数去查询用户数据时,只能将用户名和额外参数组成json或者特定字符拼接,然后在UserDetailsServiceImplloadUserByUsername方法自定义查询数据。但是似乎不太优雅。

代码如下

SysLoginService

登录service层的代码,使用自定义的CustomUsernamePasswordAuthenticationToken,调用clientLoginAuthProvider自定义实现类的认证;

......
    @Resource
    private ClientLoginAuthProvider clientLoginAuthProvider;
public String appLogin(String username, String password, Long schoolId) {
        // 登录前置校验
        loginPreCheck(username, password);
        // 用户验证
        Authentication authentication = null;
        //userService.checkStudent(username);
        try
        {
            CustomUsernamePasswordAuthenticationToken authenticationToken = new CustomUsernamePasswordAuthenticationToken(username, password,schoolId);
            //UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
            AuthenticationContextHolder.setContext(authenticationToken);
            // 该方法会去调用ClientUserDetailsService.loadUserByUsername
            authentication = clientLoginAuthProvider.authenticate(authenticationToken);
        }
        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();
        recordLoginInfo(loginUser.getUserId());
        // 生成token
        return tokenService.createToken(loginUser);
    }
......

一、CustomUsernamePasswordAuthenticationToken

public class CustomUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken {
    private final Long schoolId;

    public CustomUsernamePasswordAuthenticationToken(Object principal, Object credentials, Long schoolId) {
        super(principal, credentials);
        this.schoolId = schoolId;
    }

    public Long getSchoolId() {
        return schoolId;
    }
}

二、自定义ClientUserDetailsService 以及实现类ClientUserDetailsServiceImpl

接口:

public interface ClientUserDetailsService{
    UserDetails loadUserByUsername(String username,Long SchoolId) throws UsernameNotFoundException;
}

实现类:

@Service("clientUserDetailsService")
public class ClientUserDetailsServiceImpl implements ClientUserDetailsService{
    private static final Logger log = LoggerFactory.getLogger(ClientUserDetailsServiceImpl.class);
    @Resource
    private SysUserMapper sysUserMapper;
    @Autowired
    private SysPasswordService passwordService;
    @Autowired
    private SysPermissionService permissionService;
    @Override
    public UserDetails loadUserByUsername(String username, Long schoolId) throws UsernameNotFoundException {
        // 此处即可自定义查询用户
        SysUser user = sysUserMapper.getStudentByUserName(username,schoolId);
        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 + " 已停用");
        }

        passwordService.validate(user);

        return createLoginUser(user);
    }

    public UserDetails createLoginUser(SysUser user)
    {
        return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));
    }
}

三、ClientLoginAuthProvider

内容和DaoAuthenticationProvider中的一样 只是添加了ClientUserDetailsService,在retrieveUser方法中调用我们自己的实现方法

public class ClientLoginAuthProvider extends AbstractUserDetailsAuthenticationProvider {


    private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";

    private PasswordEncoder passwordEncoder;

    /**
     * The password used to perform {@link PasswordEncoder#matches(CharSequence, String)}
     * on when the user is not found to avoid SEC-2056. This is necessary, because some
     * {@link PasswordEncoder} implementations will short circuit if the password is not
     * in a valid format.
     */
    private volatile String userNotFoundEncodedPassword;

    private ClientUserDetailsService userDetailsService;

    private UserDetailsPasswordService userDetailsPasswordService;

    public ClientLoginAuthProvider(ClientUserDetailsService clientUserDetailsService) {
        this.userDetailsService = clientUserDetailsService;
        setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
    }

    @Override
    @SuppressWarnings("deprecation")
    protected void additionalAuthenticationChecks(UserDetails userDetails,
                                                  UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        if (authentication.getCredentials() == null) {
            this.logger.debug("Failed to authenticate since no credentials provided");
            throw new BadCredentialsException(this.messages
                    .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        }
        String presentedPassword = authentication.getCredentials().toString();
        if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
            this.logger.debug("Failed to authenticate since password does not match stored value");
            throw new BadCredentialsException(this.messages
                    .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        }
    }

    @Override
    protected void doAfterPropertiesSet() {
        Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
    }

    @Override
    protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        CustomUsernamePasswordAuthenticationToken auth = (CustomUsernamePasswordAuthenticationToken) authentication;

        //多个参数
        UserDetails loadedUser = userDetailsService.loadUserByUsername(username,auth.getSchoolId());

        if (loadedUser == null) {
            throw new InternalAuthenticationServiceException(
                    "UserDetailsService returned null, which is an interface contract violation");
        }
        return loadedUser;
    }

    @Override
    protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,
                                                         UserDetails user) {
        boolean upgradeEncoding = this.userDetailsPasswordService != null
                && this.passwordEncoder.upgradeEncoding(user.getPassword());
        if (upgradeEncoding) {
            String presentedPassword = authentication.getCredentials().toString();
            String newPassword = this.passwordEncoder.encode(presentedPassword);
            user = this.userDetailsPasswordService.updatePassword(user, newPassword);
        }
        return super.createSuccessAuthentication(principal, authentication, user);
    }

    private void prepareTimingAttackProtection() {
        if (this.userNotFoundEncodedPassword == null) {
            this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);
        }
    }

    private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {
        if (authentication.getCredentials() != null) {
            String presentedPassword = authentication.getCredentials().toString();
            this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);
        }
    }

    /**
     * Sets the PasswordEncoder instance to be used to encode and validate passwords. If
     * not set, the password will be compared using
     * {@link PasswordEncoderFactories#createDelegatingPasswordEncoder()}
     * @param passwordEncoder must be an instance of one of the {@code PasswordEncoder}
     * types.
     */
    public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
        Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
        this.passwordEncoder = passwordEncoder;
        this.userNotFoundEncodedPassword = null;
    }

    protected PasswordEncoder getPasswordEncoder() {
        return this.passwordEncoder;
    }

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

    protected ClientUserDetailsService getUserDetailsService() {
        return this.userDetailsService;
    }

    public void setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) {
        this.userDetailsPasswordService = userDetailsPasswordService;
    }

}

四、SecurityConfig

然后在SecurityConfig中添加ClientUserDetailsService注入,并且把ClientLoginAuthProvider注入到容器中

@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
    ......
    @Resource
    private ClientUserDetailsService clientUserDetailsService;
    
    ......

    @Bean
    public ClientLoginAuthProvider clientLoginAuthProvider(){
        ClientLoginAuthProvider provider = new ClientLoginAuthProvider(clientUserDetailsService);
        provider.setPasswordEncoder(bCryptPasswordEncoder());
        return provider;
    }

}

然后我们原来的接口能登录,新添加的接口也能登录了。

posted @ 2023-08-01 22:56  哔~哔~哔  阅读(506)  评论(0编辑  收藏  举报