若依--自定义loadUserByUsername参数入参
若依--自定义loadUserByUsername参数入参
前言
在使用若依的Security的登录认证时,默认只能使用用户名去查询sysUser,当我需要额外的参数去查询用户数据时,只能将用户名和额外参数组成json或者特定字符拼接,然后在UserDetailsServiceImpl
的loadUserByUsername
方法自定义查询数据。但是似乎不太优雅。
代码如下
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;
}
}
然后我们原来的接口能登录,新添加的接口也能登录了。