[ BAEL-5804 ] Spring @EnableMethodSecurity annotation (#12980)
* enable method security with custom authorization manager * fix: pmd violation * fix: add user builder, clean code * fix: test name when forbidden, user map variable
This commit is contained in:
parent
b52f7ef200
commit
8a4e8692b9
|
@ -0,0 +1,14 @@
|
||||||
|
package com.baeldung.enablemethodsecurity;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
@EnableWebMvc
|
||||||
|
public class EnableMethodSecurityApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(EnableMethodSecurityApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package com.baeldung.enablemethodsecurity.authentication;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
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.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
|
||||||
|
import com.baeldung.enablemethodsecurity.user.SecurityUser;
|
||||||
|
|
||||||
|
public class CustomUserDetailService implements UserDetailsService {
|
||||||
|
private final Map<String, SecurityUser> userMap = new HashMap<>();
|
||||||
|
|
||||||
|
public CustomUserDetailService(BCryptPasswordEncoder bCryptPasswordEncoder) {
|
||||||
|
userMap.put("user", createUser("user", bCryptPasswordEncoder.encode("userPass"), false, "USER"));
|
||||||
|
userMap.put("admin", createUser("admin", bCryptPasswordEncoder.encode("adminPass"), true, "ADMIN", "USER"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException {
|
||||||
|
return Optional.ofNullable(userMap.get(username))
|
||||||
|
.orElseThrow(() -> new UsernameNotFoundException("User " + username + " does not exists"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private SecurityUser createUser(String userName, String password, boolean withRestrictedPolicy, String... role) {
|
||||||
|
return SecurityUser.builder()
|
||||||
|
.withUserName(userName)
|
||||||
|
.withPassword(password)
|
||||||
|
.withGrantedAuthorityList(Arrays.stream(role)
|
||||||
|
.map(SimpleGrantedAuthority::new)
|
||||||
|
.collect(Collectors.toList()))
|
||||||
|
.withAccessToRestrictedPolicy(withRestrictedPolicy);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package com.baeldung.enablemethodsecurity.authorization;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.aopalliance.intercept.MethodInvocation;
|
||||||
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
|
import org.springframework.security.authentication.AuthenticationTrustResolver;
|
||||||
|
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
|
||||||
|
import org.springframework.security.authorization.AuthorizationDecision;
|
||||||
|
import org.springframework.security.authorization.AuthorizationManager;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
|
||||||
|
import com.baeldung.enablemethodsecurity.services.Policy;
|
||||||
|
import com.baeldung.enablemethodsecurity.services.PolicyEnum;
|
||||||
|
import com.baeldung.enablemethodsecurity.user.SecurityUser;
|
||||||
|
|
||||||
|
public class CustomAuthorizationManager<T> implements AuthorizationManager<MethodInvocation> {
|
||||||
|
private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation methodInvocation) {
|
||||||
|
|
||||||
|
if (hasAuthentication(authentication.get())) {
|
||||||
|
|
||||||
|
Policy policyAnnotation = AnnotationUtils.findAnnotation(methodInvocation.getMethod(), Policy.class);
|
||||||
|
|
||||||
|
SecurityUser user = (SecurityUser) authentication.get()
|
||||||
|
.getPrincipal();
|
||||||
|
|
||||||
|
return new AuthorizationDecision(Optional.ofNullable(policyAnnotation)
|
||||||
|
.map(Policy::value)
|
||||||
|
.filter(policy -> policy == PolicyEnum.OPEN || (policy == PolicyEnum.RESTRICTED && user.hasAccessToRestrictedPolicy()))
|
||||||
|
.isPresent());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AuthorizationDecision(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasAuthentication(Authentication authentication) {
|
||||||
|
return authentication != null && isNotAnonymous(authentication) && authentication.isAuthenticated();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isNotAnonymous(Authentication authentication) {
|
||||||
|
return !this.trustResolver.isAnonymous(authentication);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
package com.baeldung.enablemethodsecurity.configuration;
|
||||||
|
|
||||||
|
import static org.springframework.beans.factory.config.BeanDefinition.ROLE_INFRASTRUCTURE;
|
||||||
|
|
||||||
|
import org.aopalliance.intercept.MethodInvocation;
|
||||||
|
import org.springframework.aop.Advisor;
|
||||||
|
import org.springframework.aop.support.JdkRegexpMethodPointcut;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Role;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.authorization.AuthorizationManager;
|
||||||
|
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
|
||||||
|
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
|
||||||
|
import com.baeldung.enablemethodsecurity.authentication.CustomUserDetailService;
|
||||||
|
import com.baeldung.enablemethodsecurity.authorization.CustomAuthorizationManager;
|
||||||
|
|
||||||
|
@EnableWebSecurity
|
||||||
|
@EnableMethodSecurity
|
||||||
|
@Configuration
|
||||||
|
public class SecurityConfig {
|
||||||
|
@Bean
|
||||||
|
public AuthenticationManager authenticationManager(HttpSecurity httpSecurity, UserDetailsService userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) throws Exception {
|
||||||
|
AuthenticationManagerBuilder authenticationManagerBuilder = httpSecurity.getSharedObject(AuthenticationManagerBuilder.class);
|
||||||
|
authenticationManagerBuilder.userDetailsService(userDetailsService)
|
||||||
|
.passwordEncoder(bCryptPasswordEncoder);
|
||||||
|
return authenticationManagerBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public UserDetailsService userDetailsService(BCryptPasswordEncoder bCryptPasswordEncoder) {
|
||||||
|
return new CustomUserDetailService(bCryptPasswordEncoder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public AuthorizationManager<MethodInvocation> authorizationManager() {
|
||||||
|
return new CustomAuthorizationManager<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Role(ROLE_INFRASTRUCTURE)
|
||||||
|
public Advisor authorizationManagerBeforeMethodInterception(AuthorizationManager<MethodInvocation> authorizationManager) {
|
||||||
|
JdkRegexpMethodPointcut pattern = new JdkRegexpMethodPointcut();
|
||||||
|
pattern.setPattern("com.baeldung.enablemethodsecurity.services.*");
|
||||||
|
return new AuthorizationManagerBeforeMethodInterceptor(pattern, authorizationManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
|
http.csrf()
|
||||||
|
.disable()
|
||||||
|
.authorizeRequests()
|
||||||
|
.anyRequest()
|
||||||
|
.authenticated()
|
||||||
|
.and()
|
||||||
|
.sessionManagement()
|
||||||
|
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public BCryptPasswordEncoder bCryptPasswordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.baeldung.enablemethodsecurity.controller;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import com.baeldung.enablemethodsecurity.services.PolicyService;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class ResourceController {
|
||||||
|
private final PolicyService policyService;
|
||||||
|
|
||||||
|
public ResourceController(PolicyService policyService) {
|
||||||
|
this.policyService = policyService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/openPolicy")
|
||||||
|
public String openPolicy() {
|
||||||
|
return policyService.openPolicy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/restrictedPolicy")
|
||||||
|
public String restrictedPolicy() {
|
||||||
|
return policyService.restrictedPolicy();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.baeldung.enablemethodsecurity.services;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.METHOD;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Target(METHOD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface Policy {
|
||||||
|
PolicyEnum value();
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.baeldung.enablemethodsecurity.services;
|
||||||
|
|
||||||
|
public enum PolicyEnum {
|
||||||
|
RESTRICTED, OPEN
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.baeldung.enablemethodsecurity.services;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class PolicyService {
|
||||||
|
@Policy(PolicyEnum.OPEN)
|
||||||
|
public String openPolicy() {
|
||||||
|
return "Open Policy Service";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Policy(PolicyEnum.RESTRICTED)
|
||||||
|
public String restrictedPolicy() {
|
||||||
|
return "Restricted Policy Service";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
package com.baeldung.enablemethodsecurity.user;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
|
||||||
|
public class SecurityUser implements UserDetails {
|
||||||
|
private String userName;
|
||||||
|
private String password;
|
||||||
|
private List<GrantedAuthority> grantedAuthorityList;
|
||||||
|
private boolean accessToRestrictedPolicy;
|
||||||
|
|
||||||
|
public static SecurityUser builder() {
|
||||||
|
return new SecurityUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SecurityUser withAccessToRestrictedPolicy(boolean restrictedPolicy) {
|
||||||
|
this.accessToRestrictedPolicy = restrictedPolicy;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasAccessToRestrictedPolicy() {
|
||||||
|
return accessToRestrictedPolicy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SecurityUser withGrantedAuthorityList(List<GrantedAuthority> grantedAuthorityList) {
|
||||||
|
this.grantedAuthorityList = grantedAuthorityList;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||||
|
return this.grantedAuthorityList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SecurityUser withPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPassword() {
|
||||||
|
return this.password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SecurityUser withUserName(String userName) {
|
||||||
|
this.userName = userName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUsername() {
|
||||||
|
return this.userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAccountNonExpired() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAccountNonLocked() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCredentialsNonExpired() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package com.baeldung.enablemethodsecurity;
|
||||||
|
|
||||||
|
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.security.test.context.support.WithUserDetails;
|
||||||
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||||
|
import org.springframework.web.context.WebApplicationContext;
|
||||||
|
|
||||||
|
@SpringBootTest(classes = EnableMethodSecurityApplication.class)
|
||||||
|
public class EnableMethodSecurityIntegrationTest {
|
||||||
|
@Autowired
|
||||||
|
private WebApplicationContext context;
|
||||||
|
|
||||||
|
private MockMvc mvc;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setup() {
|
||||||
|
mvc = MockMvcBuilders.webAppContextSetup(context)
|
||||||
|
.apply(springSecurity())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithUserDetails(value = "admin")
|
||||||
|
public void whenAdminAccessOpenEndpoint_thenOk() throws Exception {
|
||||||
|
mvc.perform(get("/openPolicy"))
|
||||||
|
.andExpect(status().isOk());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithUserDetails(value = "admin")
|
||||||
|
public void whenAdminAccessRestrictedEndpoint_thenOk() throws Exception {
|
||||||
|
mvc.perform(get("/restrictedPolicy"))
|
||||||
|
.andExpect(status().isOk());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithUserDetails()
|
||||||
|
public void whenUserAccessOpenEndpoint_thenOk() throws Exception {
|
||||||
|
mvc.perform(get("/openPolicy"))
|
||||||
|
.andExpect(status().isOk());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithUserDetails()
|
||||||
|
public void whenUserAccessRestrictedEndpoint_thenIsForbidden() throws Exception {
|
||||||
|
mvc.perform(get("/restrictedPolicy"))
|
||||||
|
.andExpect(status().isForbidden());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue