Overview

Axiomatics provides a Policy Enforcement Point (PEP) Software Development Kit (SDK) for Spring Security. We can use this Spring Security SDK to easily restrict user interface (UI) items based on authorization in Thymeleaf.

What is Thymeleaf?

Thymeleaf is a modern server-side Java template engine for both web and standalone environments that looks more natural than JSP. When using it, we can display HTML correctly in browsers and also use it for static prototypes.

0 – Example

We will build an application with two levels of access for an administrator: junior and senior level access. In this example, a UI element that allows an administrator to access a sensitive page is restricted to junior level administrators.

View and download the code from Github

1 – Project Structure

We have a typical Maven project structure.

2 – Project Dependencies

We have included the various dependencies needed for the Spring Security PEP SDK in our pom.xml as well our project dependencies. To create a project with all the needed dependencies, let’s download the Axiomatics Spring Initialzr, run it on localhost:8080, and choose:

  • JPA
  • Security
  • Thymeleaf
  • Web
  • MySQL
  • Batch

Let’s create the project and then add this dependency as well:

 

<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>

3- Model

Now we define a basic User model and specify a many to many relationship between the User and Role, and join them on the table user_role :

 

@Entity

@Table(name = “user”)

public class User {

        @Id

        @GeneratedValue(strategy = GenerationType.AUTO)

        @Column(name = “user_id”)

        private int id;

        @Column(name = “email”)

        @Email(message = “*Please provide a valid Email”)

        @NotEmpty(message = “*Please provide an email”)

        private String email;

        @Column(name = “password”)

        @Length(min = 5, message = “*Your password must have at least 5 characters”)

        @NotEmpty(message = “*Please provide your password”)

        @Transient

        private String password;

        @Column(name = “name”)

        @NotEmpty(message = “*Please provide your name”)

        private String name;

        @Column(name = “last_name”)

        @NotEmpty(message = “*Please provide your last name”)

        private String lastName;

        @Column(name = “active”)

        private int active;

        @ManyToMany(cascade = CascadeType.ALL)

        @JoinTable(name = “user_role”, joinColumns = @JoinColumn(name = “user_id”), inverseJoinColumns = @JoinColumn(name = “role_id”))

        private Set<Role> roles;

        

        @Column(name =”seniority”)

        private int seniority;

     // getters and setters removed for brevity

We also create a basic Role model:

 

@Entity

@Table(name = “role”)

public class Role {

        @Id

    @GeneratedValue(strategy = GenerationType.AUTO)

        @Column(name=”role_id”)

        private int id;

        @Column(name=”role”)

        private String role;

            // getters and setters removed for brevity

4 – Service

Let’s try to follow the “I” in SOLID principles and implement interface segregation for a UserService:

 

@Service(“userService”)

public class UserServiceImpl implements UserService{

        @Autowired

        private UserRepository userRepository;

        @Autowired

    private RoleRepository roleRepository;

    @Autowired

    private BCryptPasswordEncoder bCryptPasswordEncoder;

        

        @Override

        public User findUserByEmail(String email) {

                return userRepository.findByEmail(email);

        }

        @Override

        public void saveUser(User user) {

                user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));

        user.setActive(1);

        Role userRole = roleRepository.findByRole(“ADMIN”);

        user.setRoles(new HashSet<Role>(Arrays.asList(userRole)));

                userRepository.save(user);

        }

UserRepository and RoleRepository are typical classes that extend JPARepository. We won’t cover this more in our tutorial.

5 – Configuration

5.1 XACMLWebSecurityExpressionRoot

In XACMLWebSecurityExpressionRoot.java we extend the Axiomatics Spring Security SDK class AbstractXACMLWebSecurityExpressionRoot:

 

@Component

@Lazy

public class XACMLWebSecurityExpressionRoot extends AbstractXACMLWebSecurityExpressionRoot {

        static Logger log = LoggerFactory.getLogger(XACMLWebSecurityExpressionRoot.class);

        public XACMLWebSecurityExpressionRoot(Authentication a, FilterInvocation fi) {

                super(a, fi);

        }

        @Autowired

        UserRepository userRepository;

        HttpServletRequest request = super.getRequest();

        String fullRequesrUrl = super.getFullRequestUrl();

        @Override

        public void setDefaultAttributes() {

                Authentication auth = SecurityContextHolder.getContext().getAuthentication();

                attrCatAry.add(“SUBJECT”);

                attrTypeAry.add(“STRING”);

                attrIdAry.add(“com.axiomatics.emailAddress”);

                attrValAry.add(auth.getName());

                Collection<?> authorities = auth.getAuthorities();

                for (Iterator<?> roleIter = authorities.iterator(); roleIter.hasNext();) {

                        GrantedAuthority grantedAuthority = (GrantedAuthority) roleIter.next();

                        attrCatAry.add(“SUBJECT”);

                        attrTypeAry.add(“STRING”);

                        attrIdAry.add(“role”);

                        attrValAry.add(grantedAuthority.getAuthority());

                }

        }

        @Override

        public void uiDecisionSetDefaultAttributes() {

                Authentication auth = SecurityContextHolder.getContext().getAuthentication();

                attrCatAry.add(“SUBJECT”);

                attrTypeAry.add(“INTEGER”);

                attrIdAry.add(“com.axiomatics.seniority”);

                Integer userId = null;

                try {

                        userId = userRepository.findByEmail(auth.getName()).getSeniority();

                } catch (Exception e) {

                        log.info(e.toString());

                }

                attrValAry.add(userId);

        }

        @Override

        public void urlDecisionSetDefaultAttributes() {

                // TODO Add default attributes for decisions related to URL access

        }

}

The setDefaultAttributes() is a method called by all XACML decision instances. So, we can use this to set attribute details that are generic to the decision scope. For instance, if we were using the XACMLDecisionURL method, which is a method within the Spring Security PEP SDK as well, the attributes defined in setDefaultAttributes() would be added for that decision as well.

For the UI authorization decision we are going to make, we add an attribute with the Id “com.axiomatics.seniority”.

For our example, there are two levels of seniority: 1 and 2. They are integer values. 1 is a junior level and 2 is a senior level.

5.3 XACMLWebSecurityExpressionHandler

Here we extend the Spring Security class DefaultWebSecurityExpressionHandler:

 

@Component

public class XACMLWebSecurityExpressionHandler extends DefaultWebSecurityExpressionHandler {

        @Autowired

        ApplicationContext applicationContext;

        

        

    private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();

    protected SecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, FilterInvocation fi) {

        WebSecurityExpressionRoot root = (XACMLWebSecurityExpressionRoot) applicationContext.getBean(“XACMLWebSecurityExpressionRoot”, authentication, fi);

        root.setPermissionEvaluator(getPermissionEvaluator());        

        root.setTrustResolver(trustResolver);                                        

        root.setRoleHierarchy(getRoleHierarchy());                                

        return root;

    }

}

As we know, Spring Security uses expression based security. SecurityExpressionOperations is the Standard interface for expression root objects used with expression-based security. What we are doing here is creating the XACMLWebSecurityExpressionRoot bean in the createSecurityExpression method and passing in the Authentication and FilterInvocation objects. The Authentication and FilterInvocation objects are handled in the background by Spring Security for us.

5.4 SecurityConfiguration

Here we have SecurityConfiguration.java, an extension of WebSecurityConfigurerAdapter:

 

// certain code removed for brevity

        @Override

        protected void configure(HttpSecurity http) throws Exception {

                

                http.

                        authorizeRequests()

                                .antMatchers(“/”).permitAll()

                                .antMatchers(“/login”).permitAll()

                                .antMatchers(“/registration”).permitAll()

                                .antMatchers(“/admin/**”).hasAuthority(“ADMIN”).anyRequest()

                                .authenticated().and().csrf().disable().formLogin()

                                .loginPage(“/login”).failureUrl(“/login?error=true”)

                                .defaultSuccessUrl(“/admin/home”)

                                .usernameParameter(“email”)

                                .passwordParameter(“password”)

                                .and().logout()

                                .logoutRequestMatcher(new AntPathRequestMatcher(“/logout”))

                                .logoutSuccessUrl(“/”).and().exceptionHandling()

                                .accessDeniedPage(“/access-denied”);

        }

// other code removed for brevity

Let’s take note of how we are securing the URL path /admin/**. We can see that we have required that all users must have the authority “ADMIN” to access this path.

6 – Controller

We create a typical controller class that’s annotated with @Controller and configure modelAndView for our /admin/home mapping:

 

        @RequestMapping(value=”/admin/home”, method = RequestMethod.GET)

        public ModelAndView home(){

                ModelAndView modelAndView = new ModelAndView();

                Authentication auth = SecurityContextHolder.getContext().getAuthentication();

                User user = userService.findUserByEmail(auth.getName());

                modelAndView.addObject(“userName”, “Welcome ” + user.getName() + ” ” + user.getLastName() + ” (” + user.getEmail() + “)”);

                modelAndView.addObject(“adminMessage”,”Content Available Only for Users with Admin Role”);

                modelAndView.setViewName(“admin/home”);

                return modelAndView;

        }

Here we define a custom a welcome message for each user and a message to be displayed for admins.

7 – Thymeleaf

In the Thymeleaf page for admin/home, we use the XACMLDecisionUI(…) method that is part of the Axiomatics Spring Security PEP SDK:

 

        <div class=”jumbotron” style=”background-color: green” sec:authorize=”XACMLDecisionUI(‘secretmessage’)”>

                                        <p style=”color:white” align=”center”>Message only available to senior admins (seniority == 2).</p>

                                </div>

XACMLDecisionUI(…) can take varying number of parameters, but in this case we chose the version that takes only one parameter, which is XACMLDecisionUI(java.lang.String uiElement). So, “secretmessage” is really an arbitrary value that we configure for the attribute resource_id in Axiomatics Service Manager (ASM).

8 – Conclusion

Thymeleaf and Spring Boot work seamlessly together and the Axiomatics Spring Security SDK does too with little configuration.

 

 

As mentioned, the code is available on Github.



Leave a Reply

Your email address will not be published. Required fields are marked *