I created a spring boot app with a controller layer, a service layer and a persistance layer. And i used spring security with JWT to provide the login process and to manage http sessions. I injected the service in the controller as follows :
#RestController
#ComponentScan(basePackages ={"com.example.service"})
#RequestMapping("/releve")
#CrossOrigin(origins = "http://192.168.1.13:4200")
public class ReleveController {
#Autowired
ReleveService releveService;
#Autowired
#Qualifier("authenticationManagerBean")
AuthenticationManager authenticationManager;
#Autowired
PasswordEncoder encoder;
#PreAuthorize("hasRole('ADMIN')")
#GetMapping(value="/listNiveaux")
public List<Niveau> listNiveaux() {
return releveService.listNiveaux();
}
#PreAuthorize("hasRole('ADMIN')")
#PostMapping(value="/modifNiveaux",consumes = "application/json")
public void modifNiveaux(#RequestBody Niveau niveau) {
releveService.modifNiveaux(niveau);
}
#PreAuthorize("hasRole('ADMIN')")
#PostMapping(value="/delNiveaux",consumes = "application/json")
public void delNiveaux(#RequestBody Niveau niveau) {
releveService.delNiveaux(niveau);
}
#PreAuthorize("hasRole('ADMIN')")
#PostMapping(value="/sauvegardeNiveau")
public void sauvegardeNiveau() {
releveService.sauvegardeNiveau();
}
}
Here are the annotations that i used in my service :
#Service(value = "ReleveService")
#Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
#EnableJpaRepositories(basePackages="com.example.releve.repository", entityManagerFactoryRef="entityManagerFactory")
public class ReleveServiceImpl implements ReleveService {
...
}
The problem is that despite the fact that the scope is session, the service ( or the bean ) is reinstanciated in every REST request : it does not get destroyed however, i used #PostConstruct and #PreDestroy to verify that it does not have the same behavior as the request scope.
How do i solve this ?
PS : i use Angular for the front-end app and there is another controller class for the login web service.
Related
I am new at spring MVC framework and i am currently working in a web application that uses a session scoped bean to control some data flow.
I can access these beans in my application context using #Autowired annotation without any problem in the controllers. The problem comes when I use a class in service layer that does not have any request mapping (#RequestMapping, #GetMapping nor #PostMapping) annotation.
When I try to access the application context directly or using #Autowired or even the #Resource annotation the bean has a null value.
I have a configuration class as follow:
#Configuration
#EnableAspectJAutoProxy
#EnableJpaRepositories(repositoryFactoryBeanClass = EnversRevisionRepositoryFactoryBean.class, basePackages = "com.quantumx.nitididea.NITIDideaweb.repository")
public class AppConfig implements WebMvcConfigurer {
#Bean (name = "lastTemplate")
#SessionScope
public LastTemplate getlastTemplate() {
return new LastTemplate();
}
//Some extra code
}
The POJO class is defined as :
public class LastTemplate {
private Integer lastId;
public LastTemplate(){
}
public Integer getLastId() {
return lastId;
}
public void setLastId(Integer lastId) {
this.lastId = lastId;
}
}
The I have a Test class that is annotated as service and does not have any request mapping annotated method:
//#Controller
#Service
public class Test {
// #Autowired
// private ApplicationContext context;
// #Autowired
#Resource(name = "lastTemplate")
public LastTemplate lastTemplate;
// #Autowired
// public void setLastTemplate(LastTemplate lastTemplate) {
// this.lastTemplate = lastTemplate;
// }
public Test() {
}
// #RequestMapping("/test")
public String testing() {
// TemplateForma last = (TemplateForma) context.getBean("lastInsertedTemplate");
// System.out.println(last);
System.out.println(lastTemplate);
// System.out.println(context.containsBean("lastTemplate"));
// System.out.println(context.getBean("lastTemplate"));
System.out.println("Testing complete");
return "Exit from testing method";
// return "/Messages/Success";
}
}
As you can see, there is a lot of commented code to show all the ways i have been trying to access my application context, using an Application context dependency, autowiring, declaring a resource and trying with a request mapping. The bean is null if no controller annotation and request mapping method is used and throws a java null pointer exception when I use the context getBean() methods.
Finally I just test my class in a controller that i have in my app:
#RequestMapping("/all")
public String showAll(Model model) {
Test test = new Test();
test.testing();
return "/Administrator/test";
}
Worth to mention that I also tried to change the scope of the bean to a Application scope and singleton, but it not worked. How can access my application context in a service class without mapping a request via controller?
Worth to mention that I also tried to change the scope of the bean to a Application scope and singleton, but it not worked
It should have worked in this case.
How can access my application context in a service class without mapping a request via controller?
Try one of these :-
#Autowired private ApplicationContext appContext;
OR
Implement ApplicationContextAware interface in the class where you want to access it.
Edit:
If you still want to access ApplicationContext from non spring managed class. Here is the link to article which shows how it can be achieved.
This page gives an example to get spring application context object with in non spring managed classes as well
What worked for me is that session scoped bean had to be removed in the application configuration declaration and moved to the POJO definition as follows:
#Component
#SessionScope
public class LastTemplate {
private Integer lastId;
public LastTemplate(){
}
public Integer getLastId() {
return lastId;
}
public void setLastId(Integer lastId) {
this.lastId = lastId;
}
}
The I just call the bean using #Autowired annotation.
Is there a way to #Inject/#Autowired a SessionAttribute into the #Service layer directly without passing it through a #Controller?
I'm looking for something like this:
#Autowired
#SessionAttribute("userprincipal")
UserPrincipal principal;
Possible Solution:
#Configuration
public class ApplicationConfig {
#Bean
#Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public UserPrincipal sessionUserPrincipal() {
// not sure here the user does not exist at creation of bean
}
}
My solution, hopefully this will save someone else some time.
Caution: The injected dependency is hidden, this will cause problems if used outside a session. Use Optional<T> if this is the case and handle internally. If you share your code your team will not be aware of the required dependency.
Testing: When testing you will be required to provide the session bean for #Autowired functionality.
Session Bean Class:
public class SessionUserPrincipal implements Serializable {
private static final long serialVersionUID = 1L;
private UserPrincipal principal;
public SessionUserPrincipal() {}
// mutator methods omitted
}
return Optional<T> if session attribute is not guarantied to be available
Add Bean to Context:
#Configuration
public class WebServletContextConfiguration implements WebMvcConfigurer {
#Bean
#Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public SessionUserPrincipal sessionUserPrincipal() {
return new SessionUserPrincipal();
}
}
Add RequestContextListener to web.xml
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
this is a requirement for the code below to work. It exposes state necessary to implement session scope. By default that state is exposed by DispatcherServlet, so it's not available before request enters DispatcherServlet (Spring Security filters). You will get an exception if you try to #Autowire a session bean before its available.
Add session attribute to session #Bean on successful authentication.
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
#Autowired SessionUserPrincipal sessionUserPrincipal;
#Override
public void onAuthenticationSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException
{
// find/get userprincipal code omitted
sessionUserPrincipal.setPrincipal(userprincipal);
}
}
Use session bean:
#Service
public class DefaultSomeService implements SomeService {
#Autowired private SessionUserPrincipal sessionUserPrincipal;
}
I am trying to call an oauth API, this call gets made by my main API internally,
I have created a resttemplate class -
#EnableOAuth2Client
#Configuration
public class MonkeyRestTemplate {
#Autowired
Environment env;
public OAuth2RestTemplate oauth2RestTemplate() {
ClientCredentialsResourceDetails clientCredentialsResourceDetails = new ClientCredentialsResourceDetails();
clientCredentialsResourceDetails.setAccessTokenUri(env.getRequiredProperty("monkey.api.accessToken.url"));
clientCredentialsResourceDetails.setClientId(env.getRequiredProperty("monkey.api.client.id"));
clientCredentialsResourceDetails.setClientSecret(env.getRequiredProperty("monkey.api.client.secret"));
return new OAuth2RestTemplate(clientCredentialsResourceDetails);
}
}
and here's my controller class -
#RestController
#RequestMapping("/monkeyinfo")
public class MonkeyApiService {
#Autowired
private MonkeyRestTemplate oauth2RestTemplate;
#Autowired
private Environment env;
#RequestMapping(value = "/oauth2", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE, headers="Accept=application/json" )
public MonkeyMain getProducts(#RequestBody String holder) {
ResponseEntity<MonkeyMain> forEntity = oauth2RestTemplate.oauth2RestTemplate().getForEntity(env.getProperty("monkey.bananainfo.POST.uri"),
MonkeyMain.class);
System.out.println(forEntity.getBody());
return forEntity.getBody();
}
}
MonkeyMain.class is my main model class to Marshall/Unmarshall Json body.
but when this API gets called, I am getting following warning with 403 forbidden Status -
Warning RequestContextHolder has a NULL RequestContext, therefore we are returning a NullRequestContext, therefore not all features may work as desired.
org.springframework.security.authentication.InsufficientAuthenticationException: Full authentication is required to access this resource
Please guide.
I am looking at some old example I have in the workspace. I can't see how is the
autowiring done as there is no #Autowired. Spring boot + facebook default configurations.
#Controller
#RequestMapping("/")
public class HelloController {
private Facebook facebook;
private ConnectionRepository connectionRepository;
public HelloController(Facebook facebook, ConnectionRepository connectionRepository) {
this.facebook = facebook;
this.connectionRepository = connectionRepository;
}
#GetMapping
public String helloFacebook(Model model) {
System.out.println("we are here!!!");
if (connectionRepository.findPrimaryConnection(Facebook.class) == null) {
return "redirect:/connect/facebook";
}
PagedList<Post> feed = facebook.feedOperations().getFeed();
model.addAttribute("feed", feed);
return "hello";
}
}
It works perfect but how these beans autowire themselves without the #Autowired? Are they autowired as a field or in the constructor?
With spring boot 1.4+ constructors are automatically autowired
https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-spring-beans-and-dependency-injection.html
In this controller you have not given any annotation #Autowired and may be you have the interface in the controller so you dont need to give the annotation and further also check in the service layer #Service annotation is there or not if you have not given #Service you are not able to use #Autowired also.
I'm building an application using Spring Data Rest, Spring Boot and Spring Security. I need to use #Secured annotations on methods and I've configured Spring Security in the following way:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// #formatter:off
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.securityContext().securityContextRepository(securityContextRepository())
.and()
.exceptionHandling()
.accessDeniedPage(RestPath.Errors.ROOT + RestPath.Errors.FORBIDDEN)
.and()
.csrf().disable();
}
// #formatter:on
#Bean
public SecurityContextRepository securityContextRepository() {
return new ApiUserSecurityContextRepository();
}
#Bean
public UserDetailsService userDetailsService() {
return new ApiUserDetailsService();
}
#Bean
public AuthenticationManager authenticationManager() throws Exception {
return new ProviderManager(Collections.singletonList(authenticationProvider()));
}
#Bean
public AuthenticationProvider authenticationProvider() throws Exception {
final DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService());
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
This type of configuration works well for regular MVC controllers and returns 403 when I try to access them. For example, the following controller security works:
#ResponseBody
#RequestMapping(value = RestPath.Configs.SLASH_TEST, method = RequestMethod.GET, produces = MediaTypes.HAL_JSON_VALUE)
#Secured({"ROLE_USER"})
public ResponseEntity test(#RequestParam(value = RestParam.DB_TEST, required = false) final boolean dbTest) throws ApplicationAvailabilityException {
final AppTestData appTestData = configService.testAppAvailability(dbTest);
return ResponseEntity.ok(projectionFactory.createProjection(AppTestProjection.class, appTestData));
}
However, when I try to use #Secured annotation over a rest repository - it does NOT, e.g.:
#RepositoryRestResource(collectionResourceRel = Shop.COLLECTION_NAME, path = RestResourceRel.SHOPS, excerptProjection = StandardShopProjection.class)
#Secured({"ROLE_USER"})
public interface RestShopRepository extends MongoRepository<Shop, String> {
#Secured({"ROLE_ADMIN"})
#Override
Shop findOne(String s);
}
ApiUserSecurityContextRepository is getting called for both of the methods, but only a custom MVC controller is get to the end of chain and I can check that it accesses vote() method in RoleVoter class for granting access.
As an example, I've checked Spring Data Rest + Spring Security sample, so #Secured or #PreAuthorize annotations should work with Spring Data Rest. Any ideas why they don't work?
Finally resolved the issue. The problem was in the following, I had another ShopRepository in different application module, which was not annotated with #RepositoryRestResource and it was the one which was used when accessing it using REST.
The following line of configuration in custom RepositoryRestConfigurerAdapter fixed the exploration of repositories which need to be exposed, so only annotated ones are exposed now:
config.setRepositoryDetectionStrategy(RepositoryDetectionStrategy.RepositoryDetectionStrategies.ANNOTATED);
After that I could not access the resource at all using REST, so I've figured out that it is not visible to Spring. I just had to enable Mongo repositories on API level with annotation #EnableMongoRepositories.