从屏幕修改时如何自动加载 Spring Security
Posted
技术标签:
【中文标题】从屏幕修改时如何自动加载 Spring Security【英文标题】:How can I Autoload Spring Security when modified from screen 【发布时间】:2020-01-19 07:27:19 【问题描述】:我正在为我的电子商务网站开发 RBAC 模型。在这种情况下,将有多个客户将注册到我的网站。每个客户端都会创建不同的数据库。
对于每个数据库。将有用户、角色和权限表。将有一个管理员可以添加用户并为用户分配角色。帐户的管理员也可以添加角色和修改角色。一旦客户端从面板更新,应进行此更改。并且会在用户重新登录面板后体现出来。
现在 Spring boot 安全配置是在服务器启动时加载的。如何将安全性应用于用户运行时。
我在本地创建了一个 RBAC 项目。为用户、角色和权限创建模型。我有一个 SecurityConfig 类定义系统中用户的安全性。代码如下
package com.rhv.config;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import com.rhv.um.model.Role;
import com.rhv.um.service.RoleService;
import com.rhv.util.Utill;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private RoleService roleService;
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder()
return new BCryptPasswordEncoder();
@Override
protected void configure(HttpSecurity http) throws Exception
http.authorizeRequests()
.antMatchers("/registration/**").permitAll();
http.authorizeRequests()
.antMatchers(HttpMethod.GET, "/um/roles", "/um/users/\\d+",
"/um/users", "/um/permission").hasAuthority("Administrator");
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login").permitAll();
@Bean
public AuthenticationManager customAuthenticationManager() throws Exception
return authenticationManager();
@Autowired
private void configureGlobal(AuthenticationManagerBuilder auth) throws Exception
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
在此我想要来自数据库的角色和权限。如果用户已登录,则应自动加载用户的安全性。如果管理员的角色或权限有任何变化。一旦用户重新登录应用程序,同样应该反映给用户。
已编辑(2019 年 9 月 19 日):UserDetailsServiceImpl 如下。
package com.rhv.um.service.impl;
import java.util.HashSet;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.rhv.um.model.Role;
import com.rhv.um.model.User;
import com.rhv.um.service.UserService;
@Service
public class UserDetailsServiceImpl implements UserDetailsService
@Autowired
private UserService userService;
@Override
@Transactional(readOnly = true)
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
User user = userService.findByUsername(username);
if (user == null)
throw new UsernameNotFoundException("Username does not exists: " + username);
Set<GrantedAuthority> grantedAuthorities = new HashSet<GrantedAuthority>();
for (Role role : user.getRoles())
grantedAuthorities.add(new SimpleGrantedAuthority(role.getName()));
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(),
grantedAuthorities);
我添加了从数据库中获取用户对象的 UserService 类。用户对象与角色有关系。角色与权限有关系。请在下面找到 UserServiceImpl 代码
package com.rhv.um.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import com.rhv.um.model.User;
import com.rhv.um.repository.UserRepository;
import com.rhv.um.service.UserService;
@Service
public class UserServiceImpl implements UserService
@Autowired
UserRepository userRepository;
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
@Override
public void save(User user)
user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
userRepository.save(user);
@Override
public List<User> findAll()
return userRepository.findAll();
@Override
public User findByUsername(String username)
return userRepository.findByUsername(username);
【问题讨论】:
你的userDetailService
是什么?
请检查。我添加了 UserDetailsServiceImpl
您好@chaoluo,感谢您的回复。如 UserDetailsServiceImpl 中所述,角色会自动从用户的 GrantedAuthority 添加和删除。但是将权限添加到角色时不会反映在运行时。因此,我想实现这一目标。例如:我创建了一个名为“Operation Admin”的角色,并在其中添加了 10 个权限(如“p1”、“p2”、..“p10”)。现在我已经为用户分配了“操作管理员”角色(比如“John Doe”)。因此,一旦用户重新登录系统,该权限和角色应该反映给用户。这就是我想要实现的目标
正如您在我的安全配置类中看到的那样。权限和角色在配置中是硬编码的。但我希望它动态地从数据库中获取它们并自动创建配置。代码如下: http.authorizeRequests() .antMatchers(HttpMethod.GET, "/um/roles", "/um/users/\\d+", "/um/users", "/um/permission") .hasAuthority("管理员");
请看here
【参考方案1】:
我已经找到了解决这个问题的方法。我必须实现spring的FilterInvocationSecurityMetadataSource和AccessDecisionManager
下面是我的 FilterInvocationSecurityMetadataSource,它返回 Allow/Deny of ConfigAttribute。
package com.rhv.um.filter;
import java.util.Collection;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import com.rhv.RegistrationApplication;
public class MyFilterSecurityMetadataSource implements FilterInvocationSecurityMetadataSource
public List<ConfigAttribute> getAttributes(Object object)
FilterInvocation fi = (FilterInvocation) object;
HttpServletRequest request = fi.getRequest();
HttpMethod httpMethod = HttpMethod.valueOf(fi.getRequest().getMethod());
// Bypassing Security check for /js, /css and /images url
if (new AntPathRequestMatcher("/js/**").matches(request)
|| new AntPathRequestMatcher("/css/**").matches(request)
|| new AntPathRequestMatcher("/images/**").matches(request)
|| new AntPathRequestMatcher("/login").matches(request)
|| new AntPathRequestMatcher("/").matches(request))
return SecurityConfig.createList(new String[] "Allow" );
Collection<? extends GrantedAuthority> authorities = SecurityContextHolder.getContext().getAuthentication()
.getAuthorities();
try
for (GrantedAuthority grantedAuthority : authorities)
if(grantedAuthority.toString().equalsIgnoreCase("Administrator"))
return SecurityConfig.createList(new String[] "Allow" );
for(String allowedUrl : RegistrationApplication.permissions.get(grantedAuthority.toString()).get(httpMethod))
if(new AntPathRequestMatcher(allowedUrl).matches(request))
return SecurityConfig.createList(new String[] "Allow" );
catch (Exception e)
return SecurityConfig.createList(new String[] "Deny" );
return SecurityConfig.createList(new String[] "Deny" );
public Collection<ConfigAttribute> getAllConfigAttributes()
return null;
public boolean supports(Class<?> clazz)
return FilterInvocation.class.isAssignableFrom(clazz);
MyFilterSecurityMetadataSource 的返回类型由实现 AccessDecisionManager 的 MyAccessDecisionManager 类使用。下面是 MyAccessDecisionManager 的代码
package com.rhv.um.filter;
import java.util.Collection;
import java.util.Iterator;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
public class MyAccessDecisionManager implements AccessDecisionManager
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException
if (configAttributes == null || configAttributes.size() == 0)
return;
Iterator<ConfigAttribute> ite = configAttributes.iterator();
if(ite.next().toString().equalsIgnoreCase("Allow"))
return;
else
throw new AccessDeniedException("Access is denied");
@Override
public boolean supports(ConfigAttribute attribute)
return false;
@Override
public boolean supports(Class<?> clazz)
return false;
上面的代码将决定,用户是否可以访问特定的 url。我已经使用 Allow/Deny ConfigAttribute 处理了它。
最后是我的 SecurityConfig 类代码,它提供了 WebSecurityConfigurerAdapter 的实现。下面是SecurityConfig的代码
package com.rhv.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import com.rhv.um.filter.MyAccessDecisionManager;
import com.rhv.um.filter.MyFilterSecurityMetadataSource;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter
@Autowired
private UserDetailsService userDetailsService;
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder()
return new BCryptPasswordEncoder();
@Override
protected void configure(HttpSecurity http) throws Exception
http.authorizeRequests()
.anyRequest().authenticated()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>()
public <O extends FilterSecurityInterceptor> O postProcess(
O fsi)
FilterInvocationSecurityMetadataSource newSource = new MyFilterSecurityMetadataSource();
fsi.setSecurityMetadataSource(newSource);
fsi.setAccessDecisionManager(new MyAccessDecisionManager());
return fsi;
)
.and()
.formLogin()
.loginPage("/login").permitAll()
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login").permitAll();
@Bean
public AuthenticationManager customAuthenticationManager() throws Exception
return authenticationManager();
@Autowired
private void configureGlobal(AuthenticationManagerBuilder auth) throws Exception
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
这个实现帮助我动态地允许用户访问 url。
【讨论】:
以上是关于从屏幕修改时如何自动加载 Spring Security的主要内容,如果未能解决你的问题,请参考以下文章
Spring Boot在开发时实现热部署(开发时修改文件保存后自动重启应用)