概述
在某些情况下后台需要对用户进行角色的变更一般会使用基于数据库的认证,修改用户在数据库中的角色信息,但是若用户在登录的情况下,后台变更数据库中的角色信息后用户还是能够访问接口,因为用户的权限还在Session中保存着,若不修改其中的角色信息则无法动态的改变权限,所以在修改数据库中的角色信息的同时还需要修改Session中的角色信息。
一、创建Session管理类
由于当前会话不能获取其他会话的Session的信息,所以无法修改保存在Session中的角色信息,我们能够自定义一个全局的Session管理容器GlobalSessionContext。
GlobalSessionContext
@Component
public class GlobalSessionContext {
// 用户id与与会话之间映射
private final HashMap<Integer, HttpSession> sessionMap = new HashMap<>();
// 会话注册器
@Resource
private EnhanceSessionRegistry enhanceSessionRegistry;
synchronized void add(HttpSession session) {
if (session != null) {
User user = (User) enhanceSessionRegistry.getSessionInformation(session.getId()).getPrincipal();
if (user != null) {
sessionMap.put(user.getId(), session);
System.out.println("添加session成功 " + sessionMap.size() + " ");
}
}
}
synchronized void delete(HttpSession session) {
if (session != null) {
User user = (User) enhanceSessionRegistry.getSessionInformation(session.getId()).getPrincipal();
if (user != null && user.isLogout()) {
// 恢复标记使真正退出时可以被删除
user.setLogout(true);
sessionMap.remove(user.getId());
System.out.println("删除session成功 " + sessionMap.size() + " ");
}
}
}
public HttpSession getSessionByUserId(Integer id) {
return sessionMap.get(id);
}
}
添加自定义的会话注册器
EnhanceSessionRegistry
/**
* 自定义会话注册器
*/
@Component
public class EnhanceSessionRegistry extends SessionRegistryImpl {
/**
* 获得用户Session信息
*
* @param user 用户信息
*/
private List<SessionInformation> getSessionInformationList(User user) {
// 获取父类会话注册器Session主体
List<Object> users = this.getAllPrincipals();
for (Object principal : users) {
if (principal instanceof User) {
final User loggedUser = (User) principal;
if (user.getId().equals(loggedUser.getId())) {
// 返回该用户全部Session信息
return this.getAllSessions(principal, false);
}
}
}
return null;
}
/**
* 若存在用户已登录对当前登录用户下线
*
* @param user 用户信息
*/
public void invalidateSession(User user) {
List<SessionInformation> sessionsInfo = this.getSessionInformationList(user);
if (sessionsInfo != null) {
for (SessionInformation sessionInformation : sessionsInfo) {
// 由于账户被顶自定义的全局Session在添加新的Session时会根据用户id覆盖之前的Session
// 同时之前的Session被系统删除监听器会同步删除新添加的Session会导致全局Session信息丢失
// 设置logout变量作为标记使全局Session删除时判断是否删除对应session
User oldUser = (User) sessionInformation.getPrincipal();
oldUser.setLogout(false);
// 会话过期
sessionInformation.expireNow();
}
}
}
}
在用户类中添加标记变量
public class User implements UserDetails {
private boolean logout = true;
public boolean isLogout() {
return logout;
}
public void setLogout(boolean logout) {
this.logout = logout;
}
...
}
添加自定义Session监听器类
@WebListener
public class SessionListener implements HttpSessionAttributeListener {
@Resource
private GlobalSessionContext globalSessionContext;
public void attributeAdded(HttpSessionBindingEvent se) {
System.out.println("添加session");
globalSessionContext.add(se.getSession());
}
public void attributeRemoved(HttpSessionBindingEvent se) {
System.out.println("删除session");
globalSessionContext.delete(se.getSession());
}
public void attributeReplaced(HttpSessionBindingEvent se) {
}
}
在SpringBoot启动类上添加@ServletComponentScan,@WebListener注解就自动注册了
自定义身份验证类实现Authentication接口
public class EnhanceAuthentication implements Authentication {
// 身份信息
private Authentication authentication;
// 角色信息
private Collection<? extends GrantedAuthority> authorities;
public EnhanceAuthentication(Authentication authentication) {
this.authentication = authentication;
this.authorities = authentication.getAuthorities();
}
public void delRole(String role) {
User user = (User) getPrincipal();
user.getRoles().removeIf(r -> r.getName().equals(role));
authorities = user.getAuthorities();
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public Object getCredentials() {
return authentication.getCredentials();
}
@Override
public Object getDetails() {
return authentication.getDetails();
}
@Override
public Object getPrincipal() {
return authentication.getPrincipal();
}
@Override
public boolean isAuthenticated() {
return authentication.isAuthenticated();
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
authentication.setAuthenticated(isAuthenticated());
}
@Override
public String getName() {
return authentication.getName();
}
}
在configure(HttpSecurity http)方法中添加
/**
* 单点登录
*/
// 会话信息过期处理
@Resource
private SessionInformationExpiredStrategyImpl sessionInformationExpiredStrategy;
// 会话注册器
@Resource
private EnhanceSessionRegistry enhanceSessionRegistry;
/**
* @param http http安全处理
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
...
.and()
.sessionManagement()
.maximumSessions(1)
// 会话注册器
.sessionRegistry(enhanceSessionRegistry)
// 会话信息过期处理
.expiredSessionStrategy(sessionInformationExpiredStrategy);
}
SessionInformationExpiredStrategyImpl
/**
* 会话信息过期处理
*/
@Component
public class SessionInformationExpiredStrategyImpl implements SessionInformationExpiredStrategy {
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent sessionInformationExpiredEvent) throws IOException {
StatusMessage message = new StatusMessage();
message.setMsg("登录信息过期,可能是由于同一用户尝试多次登录!");
message.setStatus(200);
message.callback(sessionInformationExpiredEvent.getResponse());
}
}
编写修改权限接口访问正常接口
@RequestMapping("/delrole")
public boolean delRole(Integer id, String role) {
HttpSession session = globalSessionContext.getSessionByUserId(id);
boolean flag = false;
if (session != null) {
// TODO 删除数据库角色
// 获得Spring Security上下文
SecurityContextImpl securityContextImpl = (SecurityContextImpl) session.getAttribute("SPRING_SECURITY_CONTEXT");
// 使用自定义身份验证继承Authentication类实现角色增删功能
EnhanceAuthentication auth = new EnhanceAuthentication(securityContextImpl.getAuthentication());
auth.delRole("ROLE_" + role);
securityContextImpl.setAuthentication(auth);
flag = true;
}
return flag;
}
![](https://img.haomeiwen.com/i18713780/6aa9e7fafba24207.png)
网友评论