之前有讨论过security空间基于内存修改密码的,因为绝大部分应用的用户及其用户信息都是存到数据库当中的,所以,修改密码肯定也是基于数据库操作的。
其实基于内存的修改密码和基于数据库的修改密码原理基本上是一样的,只不过UserDetailService的实现类已经ChangePassword()方法的实现方式不同而已。在这里重新提一遍修改密码的原理(基于数据库):
我们知道基于内存的用户数据是存到内存的一个Map中的,但是无论存到内存中还是存到数据库中,当用户登录的时候都是调用一个UserDetailService接口的方法loadUserByUsername来把User查出来,然后放到SecurityContextHolder(security框架的用户凭证的维护中心)来维护的。
我们知道它的原理之后,就很容易发现,基于内存和基于数据库的不同就是UserDetailService接口的实现类不同,loadUserByUsername的实现方法不同,所以我们实现密码修改时changePassword的实现方法也会不同,因为security的这两种情况只是中间那部分不同而已,所以我们关注的是UserDetailService接口的实现。上一节一节比对了基于内存认证和基于数据库认证的异同。这里就直接比较修改密码的异同。
首先,我们需要了解基于数据库密码修改的步骤:
1、用户数据存到数据库当中,肯定用修改数据库中的用户密码。
2、因为用户第一次登录使用的是旧密码,所以security框架中拥有的当前用户的凭证还是旧的,所以我们修改了密码之后要更新security框架中的用户凭证,而这个用户凭证是交给security框架中的securityContextHolder来维护的。所以我们修改了密码之后,只要创建一个新的用户凭证更新到色粗ContextHolder中,那么基于数据库修改密码的操作就真正的完成了。
因为这里提到securityContextHolder这个类,所以在这里有必要说明一下这个类:
SecurityContextHolder是security框架中的提供的类,它是里面维护了security的上下文信息,已经登录用户的权限,用户凭证等等信息。ps:为什么当前登录用户信息已经那么多用户同时登录时,securityContextHolder不仅不会丢失这些信息而且能把每一个用户的信息分得很清楚,是因为securityContextHolder是维护在线程中的,每个用户登录之后拥有的行程都是不同的,拥有的session也是不同的,所以securityContextHolder能准确无误的管理好各个用户的用户信息与用户凭证。
下面我们要实现基于用户修改密码,第一步,继承UserDetailService接口,声明我们需要的方法:
package service;
import java.util.HashSet;
import java.util.Set;
import javax.annotation.Resource;
import model.Users;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserCache;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.core.userdetails.cache.NullUserCache;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import dao.BaseCollectionDao;
import dao.BaseEntityDao;
public interface HibernateUserDetailsService extends UserDetailsService
{
public void changePassword(String username,String newpassword,String oldpassword);
}
第二步,实现上面定义的接口:
package service.impl;
import java.util.HashSet;
import java.util.Set;
import javax.annotation.Resource;
import model.Users;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserCache;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.core.userdetails.cache.NullUserCache;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import service.HibernateUserDetailsService;
import dao.BaseCollectionDao;
import dao.BaseEntityDao;
@Service("hibernateUserDetailsService")
public class HibernateUserDetailsServiceImpl implements HibernateUserDetailsService {
private BaseCollectionDao baseCollectionDao;
private BaseEntityDao baseEntityDao;
private UserCache userCache = new NullUserCache();
@Resource(name="baseCollectionDao")
public void setBaseCollectionDao(BaseCollectionDao baseCollectionDao)
{
this.baseCollectionDao = baseCollectionDao;
}
@Resource(name="baseEntityDao")
public void setBaseEntityDao(BaseEntityDao baseEntityDao) {
this.baseEntityDao = baseEntityDao;
}
@Transactional
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException
{
Users users = baseCollectionDao.findUniqueByProperty(Users.class, "username", username);
if(users == null)
throw new UsernameNotFoundException("用户" + username + " 不存在!!!");
String[] roles = users.getRoles().split(",");
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
for(String role : roles)
{
authorities.add(new SimpleGrantedAuthority(role));
}
return new User(users.getUsername(), users.getPassword(), authorities);
}
@Transactional
public void changePassword(String username,String newpassword,String oldpassword)
{
Authentication currentuser=SecurityContextHolder.getContext().getAuthentication();
if(currentuser==null)
{
// This would indicate bad coding somewhere
throw new AccessDeniedException("Can't change password as no Authentication object found in context " +
"for current user.");
}
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String username2=((UserDetails)principal).getUsername();
Users users=baseCollectionDao.findUniqueByProperty(Users.class, "username", username2);
if(users.getPassword().equals(oldpassword))
{
users.setPassword(newpassword);
baseEntityDao.update(users);
SecurityContextHolder.getContext().setAuthentication(createNewAuthentication(currentuser, newpassword));
userCache.removeUserFromCache(username);
}
}
public Authentication createNewAuthentication(Authentication currentAuth, String newPassword)
{
UserDetails user = loadUserByUsername(currentAuth.getName());
UsernamePasswordAuthenticationToken newAuthentication =
new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
newAuthentication.setDetails(currentAuth.getDetails());
return newAuthentication;
}
}
由上面的代码可以看到,当我们完成了第一步把新密码更新到数据库之后,我们需要创建一个新的凭证Authentication,并且更新到SecurityContextHolder,因为security框架在匹配当前用户的信息以及用户权限,都是从SecurityContextHolder里面拿到这个Authentication凭证,然后再拿到相应的信息。创建新凭证的过程很简单,相信大家很容易看懂。最后一步是移除用户缓存里面的缓存的旧数据。
做好上面那几步,security基于数据库的密码修改功能就真正完成了。