Java spring security 自定义登录逻辑实现

介绍

在使用框架自带的Security的登录认证时,默认只能使用用户名去查询,如果有业务需要其他字段也需要进行查询,只能采用根据用户名去找到对应的数据。

自定义鉴权接口CustomUsernamePasswordAuthenticationToken

/**
 * @author wuzhenyong
 * ClassName:CustomUsernamePasswordAuthenticationToken.java
 * date:2024-07-29 13:46
 * Description: 定义自定义鉴权类
 */
public class CustomUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken {
    private final Integer clientId;

    public CustomUsernamePasswordAuthenticationToken(Object principal, Object credentials, Integer clientId) {
        super(principal, credentials);
        this.clientId = clientId;
    }

    public Integer getClientId() {
        return clientId;
    }
}

自定义UserDetailsService用户查询

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

/**
 * @author wuzhenyong
 * ClassName:CustomerUserDetailsService.java
 * date:2024-07-29 13:52
 * Description:
 */
public interface CustomerUserDetailsService {
    UserDetails loadUserByUsername(String username, Integer clientId) throws UsernameNotFoundException;
}

实现类,复制原有自定义的就可以,多加一个参数

import com.astar.common.core.domain.entity.SysUser;
import com.astar.common.core.domain.model.LoginUser;
import com.astar.common.enums.UserStatus;
import com.astar.common.exception.ServiceException;
import com.astar.common.utils.MessageUtils;
import com.astar.common.utils.StringUtils;
import com.astar.framework.web.service.SysPasswordService;
import com.astar.framework.web.service.SysPermissionService;
import com.astar.framework.web.service.UserDetailsServiceImpl;
import com.astar.system.service.ISysUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

/**
 * @author wuzhenyong
 * ClassName:CustomerUserDetailsServiceImpl.java
 * date:2024-07-29 13:53
 * Description:
 */
@Service("clientUserDetailsService")
public class CustomerUserDetailsServiceImpl implements CustomerUserDetailsService{
    private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);

    @Autowired
    private ISysUserService userService;

    @Autowired
    private SysPasswordService passwordService;

    @Autowired
    private SysPermissionService permissionService;

    @Override
    public UserDetails loadUserByUsername(String username, Integer clientId) throws UsernameNotFoundException
    {
        SysUser user = userService.selectUserByUserNameAndClientId(username, clientId);
        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"));
        }

        passwordService.validate(user);

        return createLoginUser(user);
    }

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

增加ClientLoginAuthProvider

里面加入我们的自定义登录用户client

import io.jsonwebtoken.lang.Assert;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
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.UserDetailsPasswordService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;

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 CustomerUserDetailsService userDetailsService;
 
    private UserDetailsPasswordService userDetailsPasswordService;
 
    public ClientLoginAuthProvider(CustomerUserDetailsService 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.getClientId());
 
        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(CustomerUserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }
 
    protected CustomerUserDetailsService getUserDetailsService() {
        return this.userDetailsService;
    }
 
    public void setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) {
        this.userDetailsPasswordService = userDetailsPasswordService;
    }
 
}

修改security拦截器,增加我们自定义的登录逻辑

/**
 * spring security配置
 * 
 * @author astar
 */
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
    @Autowired
    private CustomerUserDetailsService customerUserDetailsService;


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

在登录接口使用我们的自定义登录信息

注入CustomUsernamePasswordAuthenticationToken
登录接口内替换成新自定义的登录逻辑

 CustomUsernamePasswordAuthenticationToken authenticationToken = new CustomUsernamePasswordAuthenticationToken(username, password, Integer.parseInt(clientId));
            AuthenticationContextHolder.setContext(authenticationToken);
            authentication = clientLoginAuthProvider.authenticate(authenticationToken);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一个小浪吴啊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值