I don't think you can do this solely in the WebSecurityConfigurerAdapter
but here is a similar set-up that takes advantage of Spring Security and demonstrates how to add the access checks to controller methods.
Additional pom.xml
dependencies:
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
...
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
...
(The latter only if you're using Thymeleaf.)
The WebSecurityConfigurerAdapter
:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@NoArgsConstructor @Log4j2
public class WebSecurityConfigurerImpl extends WebSecurityConfigurerAdapter {
@Autowired private UserDetailsService userDetailsService;
@Autowired private PasswordEncoder passwordEncoder;
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
@Override
public void configure(WebSecurity web) {
web.ignoring()
.antMatchers("/css/**", "/js/**", "/images/**",
"/webjars/**", "/webjarsjs");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().anyRequest().permitAll()
.and().formLogin().loginPage("/login").permitAll()
.and().logout().permitAll();
}
}
In the web MVC @Controller
, anyone can read an article (whether logged-in or not):
@RequestMapping(method = { GET }, value = { "/article/{slug}/" })
@PreAuthorize("permitAll()")
public String article(Model model, @PathVariable String slug) {
...
}
But only AUTHORs may use the preview feature:
@RequestMapping(method = { GET }, value = { "/preview/" })
@PreAuthorize("hasAuthority('AUTHOR')")
public String preview(Model model) {
...
}
@RequestMapping(method = { POST }, value = { "/preview/" })
@PreAuthorize("hasAuthority('AUTHOR')")
public String previewPOST(Model model,
Principal principal, HttpSession session,
HttpServletRequest request,
@Valid PreviewForm form, BindingResult result) {
...
}
This is also supported in the Thymeleaf template where the menu is conditionally displayed if the user is an AUTHOR.
<li th:ref="navbar-item" sec:authorize="hasAuthority('AUTHOR')">
<button th:text="'Author'"/>
<ul th:ref="navbar-dropdown">
<li><a th:text="'Preview'" th:href="@{/preview/}"/></li>
</ul>
</li>
And handling Login/Logout menus to demonstrate other available security predicates:
<li th:ref="navbar-item" sec:authorize="!isAuthenticated()">
<a th:text="'Login'" th:href="@{/login}"/>
</li>
<li th:ref="navbar-item" sec:authorize="isAuthenticated()">
<button sec:authentication="name"/>
<ul th:ref="navbar-dropdown">
<li><a th:text="'Change Password'" th:href="@{/password}"/></\
li>
<li><a th:text="'Logout'" th:href="@{/logout}"/></li>
</ul>
</li>
(The rest are implementation details that may be helpful for illustration but not necessarily specific to your question. Specifically, I suggest this is where your logic would be for "dynamic' groups.)
The UserDetailsService
implementation relies on JpaRepository
implementations and sets the user's grants:
@Service
@NoArgsConstructor @ToString @Log4j2
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired private CredentialRepository credentialRepository;
@Autowired private AuthorRepository authorRepository;
@Autowired private SubscriberRepository subscriberRepository;
@Override
@Transactional(readOnly = true)
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = null;
Optional<Credential> credential =
credentialRepository.findById(username);
if (credential.isPresent()) {
HashSet<GrantedAuthority> set = new HashSet<>();
subscriberRepository.findById(username)
.ifPresent(t -> set.add(new SimpleGrantedAuthority("SUBSCRIBER")));
authorRepository.findById(username)
.ifPresent(t -> set.add(new SimpleGrantedAuthority("AUTHOR")));
user = new User(username, credential.get().getPassword(), set);
} else {
throw new UsernameNotFoundException(username);
}
return user;
}
}
And an example of one of JpaRepository
:
@Repository
@Transactional(readOnly = true)
public interface AuthorRepository extends JpaRepository<Author,String> {
public Optional<Author> findBySlug(String slug);
}