一、问题:
1、spring如何实现权限实时生效
spring security的权限修改后无法实时更新,由于使用的spring security做权限控制,并且在用户登录时,将该时刻用户的权限(authority)读到用户的UserDetails(org.springframework.security.core.userdetails.UserDetails)类中,如果用户不主动重新登录,那么这个写入到session中的类无法刷新。
我自己给出一个权限实时更新的解决办法:
写一个拦截器,拦截器中有一个static的map用于存储哪些用户需要去更新自己的权限,然后在更新权限的接口中往这个static的map喂数据。
模拟使用场景:用户A、B已经登录,用户A修改用户B的权限后,会将用户B加入到static的map中,只要当前的请求的session在map中那么就更新自己的session中的权限。
@Component
public class PermissionFlushInterceptor implements HandlerInterceptor {
/**
* key is the sessionId of the logged in user, value is the username of
* the session.
* when the user's permission is updated, add the username to this map
*/
private static ConcurrentHashMap<String, String>
usersNeedUpdatePermissions = new ConcurrentHashMap();
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String sessionId = request.getSession().getId();
Authentication auth =
SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
if (auth.getPrincipal() instanceof AuthenticationUser) {
AuthenticationUser userDetails = (AuthenticationUser) auth.getPrincipal();
if (usersNeedUpdatePermissions.size() > 0 &&
usersNeedUpdatePermissions.containsKey(sessionId)) {
updatePermissionInSession(auth, userDetails);
usersNeedUpdatePermissions.remove(sessionId);
}
}
}
return true;
}
private void updatePermissionInSession(Authentication auth,
AuthenticationUser authenticationUser) {
String username = authenticationUser.getUsername();
AuthenticationUser newAuthUser =
new AuthenticationUser.Builder(username)
.authorities(AgentContextImpl.getInstance()
.getRoleManager()
.getUserAllPermissions(
username))
.build();
HttpAuthenticationUtils.setAuthentication(newAuthUser);
}
public static ConcurrentHashMap<String, String> getUsersNeedUpdatePermissions() {
return usersNeedUpdatePermissions;
}
}
拦截器需要加到WebMvcConfigurer中,这样前端的每次请求都会走这个拦截器。
-
这里为什么key是sessionId?
因为目前的登录策略设置的是允许同一个用户多浏览器同时登录,所以他们的区别是sessionId不相同,所以精确到sessionId作为key,不使用username,引入sessionId才能够从map中进行remove操作,username区分不了不同的浏览器。 -
AuthenticationUser 是实现了org.springframework.security.core.userdetails.UserDetails接口的自定义的用户信息类。需要重写hashCode和equals方法,重写好像是为了让spring知道只要用户名相同就是同一个对象。
public class AuthenticationUser implements UserDetails {
private String username;
private Set<AuthorityInfo> authorities = new HashSet<>();
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
AuthenticationUser that = (AuthenticationUser) o;
return Objects.equals(username, that.username);
}
@Override
public int hashCode() {
return Objects.hash(username);
}
// other code ...
}
在修改用户角色,和修改角色权限的位置获取到当前系统在登录的用户,将在登录的用户加入在map中,(因为只需要这一部分在登录用户刷新自己的权限就行了)。
2、如何获取在线用户
需要获取当前spring服务的登录用户,哪些用户在登录状态,一个稍微好的博客写的解决办法。
我大致思路和这个类似,也是用
sessionRegistry.getAllPrincipals()
理想的获取方式是:
@Autowired
@Qualifier("sessionRegistry")
private SessionRegistry sessionRegistry;
public Map<String, Integer> getAllLoggedInUsers() {
List<Object> allPrincipals = sessionRegistry.getAllPrincipals();
// key is the username, value is the session count of the username.
Map<String, Integer> usernames = new HashMap<>();
for (Object principal : allPrincipals) {
if (principal instanceof AuthenticationUser) {
AuthenticationUser authenticationUser =
(AuthenticationUser) principal;
List<SessionInformation> allSessions =
sessionRegistry.getAllSessions(authenticationUser,
false);
usernames.put(authenticationUser.getUsername(),
allSessions.size());
}
}
return usernames;
}
spring boot的配置类:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class AgentSecurityConfig extends WebSecurityConfigurerAdapter {
@Value("${server.host.url}")
private String serverHostUrl;
/**
* Security strategy
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/v1/**").authenticated()
.anyRequest().permitAll()
.and()
.formLogin().disable()
.and()
.csrf().disable();
http.sessionManagement()
.sessionAuthenticationStrategy(concurrentSession());
http.addFilterBefore(
new CustomConcurrentSessionFilter(sessionRegistry()),
ConcurrentSessionFilter.class);
}
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Bean
public static ServletListenerRegistrationBean httpSessionEventPublisher() {
return new ServletListenerRegistrationBean(new HttpSessionEventPublisher());
}
@Bean
public CompositeSessionAuthenticationStrategy concurrentSession() {
ConcurrentSessionControlAuthenticationStrategy
concurrentAuthenticationStrategy =
new ConcurrentSessionControlAuthenticationStrategy(
sessionRegistry());
List<SessionAuthenticationStrategy> delegateStrategies =
new ArrayList<SessionAuthenticationStrategy>();
delegateStrategies.add(concurrentAuthenticationStrategy);
delegateStrategies.add(new SessionFixationProtectionStrategy());
delegateStrategies.add(
new RegisterSessionAuthenticationStrategy(sessionRegistry()));
// concurrentAuthenticationStrategy还可以进行一些配置,此处设置的是允许同一个用户多个浏览器同时登录
concurrentAuthenticationStrategy.setMaximumSessions(-1);
return new CompositeSessionAuthenticationStrategy(delegateStrategies);
}
}
在登录成功的先引入策略类:
// 先引入seesion管理的策略类,
@Autowired
@Qualifier("concurrentSession")
private SessionAuthenticationStrategy sessionAuthenticationStrategy;
再调用session策略类的onAuthentication方法,表示认证成功,这一过程就经过了设置的策略,这样能够避免sessionRegistry.getAllPrincipals()为空的情况。
AuthenticationUser authUser =
new AuthenticationUser.Builder(username)
.authorities(AgentContextImpl.getInstance()
.getRoleManager()
.getUserAllPermissions(username))
.build();
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
authUser, null, authUser.getAuthorities());
sessionAuthenticationStrategy.onAuthentication(authentication, request, response);
另一种方式获取在线用户,思想很简单,(未验证)
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
@WebListener
public class SessionListener implements HttpSessionListener{
private int onlineCount = 0;//记录session的数量
/**
* session创建后执行
*/
@Override
public void sessionCreated(HttpSessionEvent se) {
onlineCount++;
System.out.println("【HttpSessionListener监听器】 sessionCreated, onlineCount:" + onlineCount);
//将最新的onlineCount值存起来
se.getSession().getServletContext().setAttribute("onlineCount", onlineCount);
}
/**
* session失效后执行
*/
@Override
public void sessionDestroyed(HttpSessionEvent se) {
if (onlineCount > 0) {
onlineCount--;
}
System.out.println("【HttpSessionListener监听器】 sessionDestroyed, onlineCount:" + onlineCount);
//将最新的onlineCount值存起来
se.getSession().getServletContext().setAttribute("onlineCount", onlineCount);
}
}