There are many ways to design authority-based access to the APIs using annotations as well as security configurations based on the endpoints.
Annotations:
@Secured
@PreAuthorize
@PostAuthorize
@RolesAllowed
@PreFilter
@PostFilter
In order to use the annotations you need to enable the security configurations as follow
@Configuration
@EnableGlobalMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
}
- The prePostEnabled property enables Spring Security
pre/post
annotations
- The securedEnabled property determines if the
@Secured
annotation should be enabled
- The jsr250Enabled property allows us to use the
@RoleAllowed
annotation
@Secured & @RoleAllowed
Users who have the given role are able to execute the method. The @RoleAllowed
annotation is the JSR-250’s equivalent annotation of the @Secured
annotation.
@Secured({ "ROLE_ADMIN", "ROLE_SUPERADMIN" })
public ResponseEntity<?> save(...) {
...
}
@RolesAllowed({ "ROLE_ADMIN", "ROLE_SUPERADMIN" })
public ResponseEntity<?> save(...) {
...
}
@PreAuthorize & @PostAuthorize
The @PreAuthorize
annotation checks the given expression before entering the method, whereas, the @PostAuthorize
annotation verifies it after the execution of the method and could alter the result.
@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_SUPERADMIN')")
public ResponseEntity<?> save(...) {
...
}
The major difference between @PreAuthorize & @PostAuthorize
and @Secured
is that @Secured
does not support the SpEL (Spring Expression Language). To check more difference you may read more details here
@PreAuthorize("#username == authentication.principal.username")
public String methodX(String username) {
//...
}
@PostAuthorize("#username == authentication.principal.username")
public String methodY(String username) {
//...
}
Here, a user can invoke the methodX only if the value of the argument username is the same as the current principal's username. You can check the other possible SpEL (Spring Expression Language) customization here
You can get the more details from the here
Using configure(HttpSecurity http)
and configure(WebSecurity web)
method.
@EnableWebSecurity
@EnableGlobalMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) {
web
.ignoring()
.antMatchers("/app/**/*.{js,html}")
.antMatchers("/i18n/**")
.antMatchers("/content/**")
.antMatchers("/swagger-ui/**")
.antMatchers("/test/**");
}
@Override
public void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.csrf()
.disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/public/**").permitAll()
.antMatchers("/api/**").hasAuthority(AuthoritiesConstants.USER)
.antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN);
// @formatter:on
}
}
configure(WebSecurity web)
Endpoint used in this method ignores the spring security filters, security features (secure headers, csrf protection etc) are also ignored and no security context will be set and can not protect endpoints for Cross-Site Scripting, XSS attacks, content-sniffing.
configure(HttpSecurity http)
Endpoint used in this method ignores the authentication for endpoints used in antMatchers and other security features will be in effect such as secure headers, CSRF protection, etc.
You can use the hasRole(), hasAnyRole(), hasAuthority(), hasAnyAuthority() methods with the configure(HttpSecurity http)
. Note that with the hasRole(), hasAnyRole() method you don't need to use the ROLE_ prefix while with other two you have to use the ROLE_
To get the difference and usage you may get the details here
You can also create the utils method as follows which might be helpful.
/**
* Get the login of the current user.
*
* @return the login of the current user.
*/
public static Optional<String> getCurrentUserLogin() {
SecurityContext securityContext = SecurityContextHolder.getContext();
return Optional.ofNullable(securityContext.getAuthentication())
.map(authentication -> {
if (authentication.getPrincipal() instanceof UserDetails) {
UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal();
return springSecurityUser.getUsername();
} else if (authentication.getPrincipal() instanceof String) {
return (String) authentication.getPrincipal();
}
return null;
});
}
/**
* Check if a user is authenticated.
*
* @return true if the user is authenticated, false otherwise.
*/
public static boolean isAuthenticated() {
SecurityContext securityContext = SecurityContextHolder.getContext();
return Optional.ofNullable(securityContext.getAuthentication())
.map(authentication -> {
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.addAll(authentication.getAuthorities());
return authorities.stream()
.noneMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(AuthoritiesConstants.ANONYMOUS));
})
.orElse(false);
}
/**
* If the current user has a specific authority (security role).
* <p>
* The name of this method comes from the {@code isUserInRole()} method in the Servlet API.
*
* @param authority the authority to check.
* @return true if the current user has the authority, false otherwise.
*/
public static boolean isCurrentUserInRole(String authority) {
SecurityContext securityContext = SecurityContextHolder.getContext();
return Optional.ofNullable(securityContext.getAuthentication())
.map(authentication -> {
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.addAll(authentication.getAuthorities());
return authorities.stream()
.anyMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(authority));
})
.orElse(false);
}
public static Optional<Authentication> getAuthenticatedCurrentUser() {
log.debug("Request to get authentication for current user");
SecurityContext securityContext = SecurityContextHolder.getContext();
return Optional.ofNullable(securityContext.getAuthentication());
}
UPDATE
@Component("userVerifier")
public class UserVerifier {
public boolean isPermitted(Authentication authentication) {
String PERMITTED_USERNAME = Arrays.asList("abc", "xyz");
return PERMITTED_USERNAME.stream.anyMatch(username -> authentication.getName().equals(username));
}
}
In security configurations we can use configure(HttpSecurity http)
as follow which will invoke the isPermitted()
method.
http
.authorizeRequests()
.antMatchers("/your-endpoint/{id}")
.access("@userVerifier.isPermitted(authentication)")
...
OR using the annotation as follows:
@PreAuthorize("@userVerifier.isPermitted(authentication)")
@PostMapping("{id}")
public ResponseEntity<?> save(Authentication authentication, Principal principal, @PathVariable Integer id, @RequestBody UserNewDTO dto) {
........
}
You may find more details from here and from this blog