Using the Spring PEP SDK with Spring Boot & Thymeleaf for UI Security
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> |
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.