介绍
在使用框架自带的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);