[ 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