从屏幕修改时如何自动加载 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 日):UserDetailsS​​erviceImpl 如下。

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是什么? 请检查。我添加了 UserDetailsS​​erviceImpl 您好@chaoluo,感谢您的回复。如 UserDetailsS​​erviceImpl 中所述,角色会自动从用户的 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在开发时实现热部署(开发时修改文件保存后自动重启应用)

如何让 vuefire 显示加载屏幕?

如何在 IDEA Intellij 上使用 Spring-boot 进行自动重新加载

从 Firebase 获取数据时如何显示加载屏幕

安卓屏幕旋转时,禁止Activity重新加载

如何自动重新加载集合视图?