How can I Autoload Spring Security when modified from screen

Mohinuddin Luhar :

I am working on a RBAC model for my Ecommerce website. In this, there will be multiple clients who will be registering to my website. For each client there will be different database created.

For each database. There will be User, Role and Permission table. There will be an admin who can add user and assign role to user. Also the admin of the account can add role and modify role. This changes should be done once the clients update from panel. And will be reflected after user re-login into panel.

Now the Spring boot Security Configuration is loaded at the time of startup of server. How can I apply security to user runtime.

I had created a RBAC project in my local. Created model for Users, Roles and Permissions. I had a SecurityConfig class defines the security for user in system. Code is as below

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());
    }
}

In this I want the roles and permission coming from database. If the user is logged-in, then security for the user should be loaded automatically. If there is any change in Role or permission by Admin. The same should be reflected to user, once the user re-login into the app.

Edited (19 Sep 2019): UserDetailsServiceImpl is as below.

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);
    }

}

I had added UserService class which is fetching the User object from the database. User object is having relationship with Role. Role is having relationship with permission. Please find the UserServiceImpl code as below

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);
    }
}
Mohinuddin Luhar :

I had got the solution to this problem. I have to implement the spring's FilterInvocationSecurityMetadataSource and AccessDecisionManager

Below is my FilterInvocationSecurityMetadataSource that returns 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);
    }
}

The return type of MyFilterSecurityMetadataSource is used by MyAccessDecisionManager class that implements AccessDecisionManager. Below is the code for 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;
    }

}

The above code will decide, Should the user have access to a particular url or not. I had handled it with Allow/Deny ConfigAttribute.

Finally is my SecurityConfig Class code that provides the implementation of WebSecurityConfigurerAdapter. Below is the code for 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());
    }

}

This implementation helps me to dynamically permit user to access url.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=164284&siteId=1