How to pass parameters from custom annotation to WebSecurityConfigurer in library - spring-boot

Hi we are building custom spring security library
we need to pass {"/v1","/v2"} paths through #EnableMySpringSecurity(excludePaths = {"/v1","/v2"}) which is present in the main project to library websecurity so we can ignore those endpoints from security
#EnableMySpringSecurity(excludePaths = {"/v1","/v2"})
#EnableWebMvc
public class WebAppConfiguration extends BaseWebAppConfiguration {
Websecurity Configuration from custom JAR
#EnableWebSecurity(debug = true)
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public static class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web){
web.ignoring().antMatchers(excludePaths );
How to pass values that are passed from #EnableMYSpringSecurity to the webSecuirty web.ignoring.antMatchers
our annotation configuration
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface EnableMySpringSecurity {
String[] excludePaths() default {};
}
I have tried ApplicationStartupListener but problem with this is, it is initialized after websecuirty configuration
public class ApplicationStartupListener implements
ApplicationListener<ContextRefreshedEvent> {
private ApplicationContext context;
private EnableMySSAnnotationProcessor processor;
public ApplicationStartupListener(ApplicationContext context,
EnableMySSAnnotationProcessor processor) {
this.context = context;
this.processor = processor;
}
#Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
Optional<EnableMySpringSecurity> annotation =
context.getBeansWithAnnotation(EnableMySpringSecurity.class).keySet().stream()
.map(key -> context.findAnnotationOnBean(key, EnableMySpringSecurity.class))
.findFirst();
annotation.ifPresent(enableMySpringSecurity-> processor.process(enableMySpringSecurity));
}
}

One way you can do this is with the #Import annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#Import(MyWebSecurityConfiguration.class)
#EnableWebSecurity
public #interface EnableMyWebSecurity {
String[] paths() default [];
}
and then the ImportAware interface:
#Configuration
public class MyWebSecurityConfiguration implements ImportAware {
private String[] paths;
#Bean
WebSecurityCustomizer paths() {
return (web) -> web.ignoring().antMatchers(paths);
}
#Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
EnableMyWebSecurity annotation = importMetadata
.getAnnotations().get(EnableMyWebSecurity.class).synthesize();
this.paths = annotations.paths();
}
}
Note, by the way, that when you exclude paths, Spring Security cannot add security headers as part of the response. If you want those endpoints to be protected by Spring Security, but public, then consider instead:
#Configuration
public class MyWebSecurityConfiguration implements ImportAware {
private String[] paths;
#Bean
#Order(1)
SecurityFilterChain paths(HttpSecurity http) {
http
.requestMatchers((requests) -> requests.antMatchers(paths))
.authorizeRequests((authorize) -> authorize
.anyRequest().permitAll()
);
return http.build();
}
#Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
EnableMyWebSecurity annotation = importMetadata
.getAnnotations().get(EnableMyWebSecurity.class).synthesize();
this.paths = annotations.paths();
}
}
The benefit of the second approach is that Spring Security won't require authentication, but will add secure response headers.

The solution provided #jzheaux works
There is one more solution - is to use application context getBeansWithAnnoation
#EnableWebSecurity(debug = true)
#Configuration
#Order(2147483640)
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private ApplicationContext appContext;
#Override
public void configure(WebSecurity web){
Map<String,Object> beanMap = this.appContext.getBeansWithAnnotation(EnableMYSpringSecurity.class);
if(!beanMap.isEmpty()){
EnableMYSpringSecurityanno = (EnableMYSpringSecurity) this.appContext.findAnnotationOnBean(beanMap.keySet()
.iterator()
.next(),EnableMYSpringSecurity.class);
String[] permitPaths = anno.excludePaths();
Arrays.stream(permitPaths).forEach(System.out::println);
}

Related

ServerHttpSecurity bean not found

I have a Security config class that has a SecurityWebFilterChain bean in it. This bean requires a ServerHttpSecuirty instance but spring says that it cannot find any beans of that type though there is one created in the external library (org.springframework.security.config.annotation.web.reactive.ServerHttpSecurityConfiguration). I have seen this issue on a github page and they said try a different version but I am using spring boot 2.4.5 so it should work.
My Security Config class:
#Configuration
public class SecurityConfig {
#Bean
SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http,
JwtTokenProvider tokenProvider,
ReactiveAuthenticationManager reactiveAuthenticationManager) {
final String TAG_SERVICES = "/api/**";
return http.csrf(ServerHttpSecurity.CsrfSpec::disable)
.httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)
.authenticationManager(reactiveAuthenticationManager)
.securityContextRepository(NoOpServerSecurityContextRepository.getInstance())
.authorizeExchange(it -> it
.pathMatchers(HttpMethod.POST, TAG_SERVICES).hasAnyRole("USER","ADMIN")
.pathMatchers(HttpMethod.PUT, TAG_SERVICES).hasAnyRole("USER","ADMIN")
.pathMatchers(HttpMethod.GET, TAG_SERVICES).hasAnyRole("USER","ADMIN")
.pathMatchers(HttpMethod.DELETE, TAG_SERVICES).hasAnyRole("USER","ADMIN")
.pathMatchers(TAG_SERVICES).authenticated()
.anyExchange().permitAll()
)
.addFilterAt(new JwtTokenAuthenticationFilter(tokenProvider), SecurityWebFiltersOrder.HTTP_BASIC)
.build();
}
}
My application class
#ConfigurationPropertiesScan
#SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
public class TestPlatformBackendApplication {
public static void main(String[] args) {
SpringApplication.run(TestPlatformBackendApplication.class, args);
}
}
External Library Bean:
#Bean({"org.springframework.security.config.annotation.web.reactive.HttpSecurityConfiguration.httpSecurity"})
#Scope("prototype")
ServerHttpSecurity httpSecurity() {
ServerHttpSecurityConfiguration.ContextAwareServerHttpSecurity http = new ServerHttpSecurityConfiguration.ContextAwareServerHttpSecurity();
return http.authenticationManager(this.authenticationManager()).headers().and().logout().and();
}
As Toerktumlare recommended in the comments (1, 2) I added #EnableWebFluxSecurity to my security config:
#Configuration
#EnableWebFluxSecurity
public class SecurityConfig {
But I also added the following to my exclude in the #SpringBootApplication annotation.
#ConfigurationPropertiesScan
#SpringBootApplication(exclude={DataSourceAutoConfiguration.class, SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class})
public class TestPlatformBackendApplication {
public static void main(String[] args) {
SpringApplication.run(TestPlatformBackendApplication.class, args);
}
}

Spring Boot app requires a bean annotated with #Primary to start

I'm seeing the following message on a Spring Boot app startup:
> *************************** APPLICATION FAILED TO START
> ***************************
>
> Description:
>
> Field oauthProps in com.example.authservice.AuthorizationServerConfig
> required a single bean, but 2 were found:
> - OAuthProperties: defined in file [/Users/simeonleyzerzon/abc/spring-security/spring-security-5-oauth-client/auth-service/target/classes/com/example/authservice/config/OAuthProperties.class]
> - kai-com.example.authservice.config.OAuthProperties: defined in null
>
>
> Action:
>
> Consider marking one of the beans as #Primary, updating the consumer
> to accept multiple beans, or using #Qualifier to identify the bean
> that should be consumed
I'm wondering what's causing the duplication of that bean and how one can remove it without the necessity of using the #Primary annotation? Not sure where the kai-com package(?) from the above is coming from.
Here's the bean in question:
package com.example.authservice.config;
//#Primary
#Component
#ConfigurationProperties(prefix="kai")
#Setter #Getter
public class OAuthProperties {
private String[] redirectUris;
private String clientId;
private String clientSecret;
private final Token token = new Token();
#Setter #Getter
public static class Token{
private String value;
private String type="";
}
}
and the app/config, etc.:
package com.example.authservice;
import ...
#SpringBootApplication
public class AuthServiceApplication {
public static void main(String[] args) {
SpringApplication.run(AuthServiceApplication.class, args);
}
}
#Controller
class MainController {
#GetMapping("/")
String index() {
return "index";
}
}
#RestController
class ProfileRestController {
#GetMapping("/resources/userinfo")
Map<String, String> profile(Principal principal) {
return Collections.singletonMap("name", principal.getName());
}
}
#Configuration
#EnableResourceServer
class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/resources/**")
.authorizeRequests()
.mvcMatchers("/resources/userinfo").access("#oauth2.hasScope('profile')");
}
}
#Configuration
#EnableAuthorizationServer
#EnableConfigurationProperties(OAuthProperties.class)
class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired private OAuthProperties oauthProps;
private final AuthenticationManager authenticationManager;
AuthorizationServerConfig(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient(oauthProps.getClientId())
.secret(oauthProps.getClientSecret())
.authorizedGrantTypes("authorization_code")
.scopes("profile")
.redirectUris(oauthProps.getRedirectUris());
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(this.authenticationManager);
if (oauthProps.getToken().getType().equals("jwt")) {
endpoints.tokenStore(this.tokenStore()).accessTokenConverter(jwtAccessTokenConverter());
}else {
endpoints.tokenEnhancer(eapiTokenEnhancer());
}
}
TokenEnhancer eapiTokenEnhancer() {
return new TokenEnhancer() {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
DefaultOAuth2AccessToken result = new DefaultOAuth2AccessToken(accessToken);
result.setValue(oauthProps.getToken().getValue());
return result;
}
};
}
#Bean
JwtAccessTokenConverter jwtAccessTokenConverter() {
KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new ClassPathResource(".keystore-oauth2-demo"), //keystore
"admin1234".toCharArray()); //storepass
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setKeyPair(factory.getKeyPair("oauth2-demo-key")); //alias
return jwtAccessTokenConverter;
}
#Bean
TokenStore tokenStore() {
return new JwtTokenStore(this.jwtAccessTokenConverter());
}
}
#Service
class SimpleUserDetailsService implements UserDetailsService {
private final Map<String, UserDetails> users = new ConcurrentHashMap<>();
SimpleUserDetailsService() {
Arrays.asList("josh", "rob", "joe")
.forEach(username -> this.users.putIfAbsent(
username, new User(username, "pw", true, true, true, true, AuthorityUtils.createAuthorityList("USER","ACTUATOR"))));
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return this.users.get(username);
}
}
#Configuration
#EnableWebSecurity
class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin();
}
}
Eclipse too seems to be only aware of a single instance of the bean:
When using #EnableConfigurationProperties with #ConfigurationProperties you will get a bean named <prefix>-<fqn>, the kai-com.example.authservice.config.OAuthProperties. (See also the reference guide).
When the #ConfigurationProperties bean is registered that way, the bean has a conventional name: <prefix>-<fqn>, where <prefix> is the environment key prefix specified in the #ConfigurationProperties annotation and <fqn> is the fully qualified name of the bean. If the annotation does not provide any prefix, only the fully qualified name of the bean is used.
The bean name in the example above is acme-com.example.AcmeProperties. (From the Reference Guide).
The #Component will lead to another registration of the bean with the regular name of the classname with a lowercase character. The other instance of your properties.
the #EnableConfigurationProperties annotation is also automatically applied to your project so that any existing bean annotated with #ConfigurationProperties is configured from the Environment. You could shortcut MyConfiguration by making sure AcmeProperties is already a bean, as shown in the following example: (From the Reference Guide).
The key here is that #EnableConfigurationProperties is already globally applied and processes any bean annotated with #ConfigurationProperties.
So basically you where mixing the 2 ways of using #ConfigurationProperties and Spring Boot 2 now prevents that misuse. This way you write better code (and reduce the memory footprint and performance slightly).
So either remove the #Component or remove the #EnableConfigurationProperties, either way will work.
The following change (removing of #EnableConfigurationProperties) seems to help relieving the need for the #Primary annotation:
#Configuration
#EnableAuthorizationServer
//#EnableConfigurationProperties(OAuthProperties.class)
class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired private OAuthProperties oauthProps;
Perhaps someone can describe the internal Spring mechanics of secondary bean creation (and its namespace/package assignment) by that annotation which seemingly causes the collision with the #Autowired one, or point me to the appropriate documentation of this behavior.

unable to use #AspectJ with Spring-Apache CXF services

I am new to spring and am working on a rest service written using Spring and Apache CXF with Java Configurations. I have the following rest service.
#Path("/release/")
#Component
#RestService
#Consumes({ MediaType.APPLICATION_JSON })
#Produces({ MediaType.APPLICATION_JSON })
#Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ReleaseResource extends AbstractService implements IResource {
#Override
#CustomLogger
#GET
public Response get() {
//Some Logic
return Response.ok("Success!!").build();
}
}
I have created an aspect using #AspectJ for logging. However, the aspect is not working on the services written in CXF. I did a bit of searching in net and found that Spring needs proxy beans for the aspects to work. Then I tried few approaches such as
Making the service class implement an interface
Using CGLIB library and scope proxy mode TARGET_CLASS
Extending a class with method
#Override
public void setMessageContext(MessageContext context) {
this.context = context;
}
But none of them worked.
Any idea if it is possible to run the aspect around the services?
If yes, can someone please tell me how to.
I have read that this can be achieved by bytecode weaving the aspectj manually instead of using spring aspectj autoproxy (not sure how to do it though). Can someone tell me if this is a good option and how to do it?
EDIT:
Sorry for the incomplete info provided. Attaching the other classes
#Aspect
#Configuration
public class LoggerAspect {
#Pointcut(value = "execution(* *(..))")
public void anyPublicMethod() {
}
#Around("anyPublicMethod() && #annotation(CustomLogger)")
public Object logAction(ProceedingJoinPoint pjp, CustomLogger customLogger) throws Throwable {
//Log Some Info
return pjp.proceed();
}
}
Web Initializer class:
#Configuration
public class WebInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.addListener(new ContextLoaderListener(createWebAppContext()));
addApacheCxfServlet(servletContext);
}
private void addApacheCxfServlet(ServletContext servletContext) {
CXFServlet cxfServlet = new CXFServlet();
ServletRegistration.Dynamic appServlet = servletContext.addServlet("CXFServlet", cxfServlet);
appServlet.setLoadOnStartup(1);
appServlet.addMapping("/*");
}
private WebApplicationContext createWebAppContext() {
AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
appContext.register(TestConfig.class);
return appContext;
}
}
Config Class:
#Configuration
#ComponentScan(basePackages = "com.my.package")
#EnableAspectJAutoProxy(proxyTargetClass = true)
public class TestConfig {
private static final String RESOURCES_PACKAGE = "com.my.package";
#ApplicationPath("/")
public class JaxRsApiApplication extends Application {
}
#Bean(destroyMethod = "shutdown")
public SpringBus cxf() {
return new SpringBus();
}
#Bean
public JacksonJsonProvider jacksonJsonProvider() {
return new JacksonJsonProvider();
}
#Bean
public LoggerAspect getLoggerAspect() {
return new LoggerAspect();
}
#Bean
IResource getReleaseResource() {
return new ReleaseResource();
}
#Bean
#DependsOn("cxf")
public Server jaxRsServer(ApplicationContext appContext) {
JAXRSServerFactoryBean factory = RuntimeDelegate.getInstance().createEndpoint(jaxRsApiApplication(),
JAXRSServerFactoryBean.class);
factory.setServiceBeans(restServiceList(appContext));
factory.setProvider(jacksonJsonProvider());
return factory.create();
}
private List<Object> restServiceList(ApplicationContext appContext) {
return RestServiceBeanScanner.scan(appContext, TestConfig.RESOURCES_PACKAGE);
}
#Bean
public JaxRsApiApplication jaxRsApiApplication() {
return new JaxRsApiApplication();
}
}
RestServiceBeanScanner class
public final class RestServiceBeanScanner {
private RestServiceBeanScanner() {
}
public static List<Object> scan(ApplicationContext applicationContext, String... basePackages) {
GenericApplicationContext genericAppContext = new GenericApplicationContext();
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(genericAppContext, false);
scanner.addIncludeFilter(new AnnotationTypeFilter(RestService.class));
scanner.scan(basePackages);
genericAppContext.setParent(applicationContext);
genericAppContext.refresh();
List<Object> restResources = new ArrayList<>(
genericAppContext.getBeansWithAnnotation(RestService.class).values());
return restResources;
}
}

Mocking beans in spring context using Spring Boot

I'm using Spring Boot 1.3.2, and I notice problem, ComponentScan in my test class is not working. And I want to mock some of Spring Beans. Is spring boot blocking ComponentScan?
Test config class:
#Configuration
#ComponentScan(value = {"myapp.offer", "myapp.image"})
public class TestEdge2EdgeConfiguration {
#Bean
#Primary
public OfferRepository offerRepository() {
return mock(OfferRepository.class);
}
}
Test class:
#ContextConfiguration(classes=TestEdge2EdgeConfiguration.class, loader=AnnotationConfigContextLoader.class)
public class OfferActionsControllerTest extends AbstractTestNGSpringContextTests {
#Autowired
private OfferRepository offerRepository;
#Autowired
private OfferActionsController offerActionsController;
#BeforeMethod
public void setUp(){
MockitoAnnotations.initMocks(this);
}
#Test
public void saveOffer() {
//given
BDDMockito.given(offerRepository.save(any(Offer.class))).willReturn(new Offer());
//when
ResponseEntity<Offer> save = offerActionsController.save(new Offer());
//then
org.springframework.util.Assert.notNull(save);
}
}
Solution of this problem
Test config class, it's important to exclude SpringBootApplicationConfigutation and add #ComponentScan:
#ComponentScan(basePackages = "com.example", excludeFilters =
#ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,
value = {SpringBootApplicationConfigutation.class, MyDao.class}
)
)
#Configuration
public class TestEdge2EdgeConfiguration {
#Bean
public OfferRepository offerRepository() {
return mock(OfferRepository.class);
}
}
And test:
#SpringApplicationConfiguration(classes=TestEdge2EdgeConfiguration.class)
public class OfferActionsControllerTest extends AbstractTestNGSpringContextTests {
#Autowired
private OfferRepository offerRepository;
#Autowired
private OfferActionsController offerActionsController;
#BeforeMethod
public void resetMock() {
Mockito.reset(offerRepository);
}
#Test
public void saveOffer() {
//given
BDDMockito.given(offerRepository.save(any(Offer.class))).willReturn(new Offer());
//when
ResponseEntity<Offer> save = offerActionsController.save(new Offer());
//then
org.springframework.util.Assert.notNull(save);
}
}

Issues with a Spring #Configurable class not getting its dependencies autowired

I am trying to configure Load Time Weaving for my Spring Boot app to properly autowire dependencies on a #Configurable java class.
Here is my configuration/main class:
package com.bignibou;
#Configuration
#EnableAutoConfiguration(exclude = { SecurityAutoConfiguration.class, ThymeleafAutoConfiguration.class, FlywayAutoConfiguration.class })
#EnableSpringConfigured
#EnableLoadTimeWeaving(aspectjWeaving = AspectJWeaving.ENABLED)
#ComponentScan
public class Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
Here is how I start the application (my gradle build renamed the spring-instrument jar):
java -javaagent:build/lib/springinstrument.jar -jar myapp.jar
Here is the #Configurable class that does not get its dependencies autowired:
package com.bignibou.converter;
#Configurable
public class StringToDayToTimeSlotConverter implements Converter<String, DayToTimeSlot> {
#Autowired
private DayToTimeSlotRepository dayToTimeSlotRepository;//NOT AUTOWIRED!!
#Override
public DayToTimeSlot convert(String id) {
return dayToTimeSlotRepository.findOne(Long.parseLong(id));//NPE HERE!!
}
}
Here is where the class is instantiated (with new):
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = { "com.bignibou.controller" }, useDefaultFilters = false, includeFilters = { #Filter(type = FilterType.ANNOTATION, value = Controller.class),
#Filter(type = FilterType.ANNOTATION, value = ControllerAdvice.class) })
#Import(ApplicationControllerAdvice.class)
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {
...
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new DayToTimeSlotToStringConverter());
registry.addConverter(new StringToDayToTimeSlotConverter());//INSTANTIATED HERE!
registry.addConverter(new LanguageToStringConverter());
registry.addConverter(new StringToLanguageConverter());
registry.addConverter(new AddressToStringConverter());
registry.addConverter(new StringToAddressConverter());
super.addFormatters(registry);
}
Can anyone please help figure out why StringToDayToTimeSlotConverter's dependencies are not autowired?
Very old question with at least a suggestion for a solution that I will turn into an answer so that the question can be "closed". Turn StringToDayToTimeSlotConverter into a Bean as follows:
#Bean
public class StringToDayToTimeSlotConverter implements Converter<String, DayToTimeSlot> {
#Autowired
private DayToTimeSlotRepository dayToTimeSlotRepository;
#Override
public DayToTimeSlot convert(String id) {
return dayToTimeSlotRepository.findOne(Long.parseLong(id));
}
}
Inject all available converters in WebMvcConfiguration as follows:
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = { "com.bignibou.controller" }, useDefaultFilters = false, includeFilters = { #Filter(type = FilterType.ANNOTATION, value = Controller.class),
#Filter(type = FilterType.ANNOTATION, value = ControllerAdvice.class) })
#Import(ApplicationControllerAdvice.class)
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {
#Autowired
List<Converter> converters;
#Override
public void addFormatters(FormatterRegistry registry) {
for (Converter converter : converter) {
registry.addConverter(converter);
}
super.addFormatters(registry);
}
}

Resources