Well, I'm using spring web MVC for my protect and have two dispatchers configured in my WebAppInitializer:
#Override
public void onStartup(ServletContext container) {
// Create the 'root' Spring application context
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(MvcConfig.class);
rootContext.register(ApplicationContextProvider.class);
// Manage the lifecycle of the root application context
container.addListener(new ContextLoaderListener(rootContext));
// Register and map the dispatcher servlet
ServletRegistration.Dynamic dispatcher
= container.addServlet("dispatcher", new DispatcherServlet(rootContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
// Create servlet for WS
MessageDispatcherServlet messageDispatcherServlet = new MessageDispatcherServlet();
messageDispatcherServlet.setApplicationContext(rootContext);
messageDispatcherServlet.setTransformWsdlLocations(true);
ServletRegistration.Dynamic messageDispatcher
= container.addServlet("messageDispatcher", messageDispatcherServlet);
messageDispatcher.setLoadOnStartup(2);
messageDispatcher.addMapping("/ws/*");
One for regular web application, another for soap web service.
#EnableTransactionManagement(proxyTargetClass = true) annotation is set,
I have HibernateTransactionManager bean and SessionFactory configured;
Session factory is used to access db - through DAO, annotated with #Transactional and #Repository;
And well in most cases everything works like a charm, even in the new threads produced by Spring's ThreadPoolTaskExecutor;
However when I try to use my DAO Repository inside of the WS Interceptor - SessionFactory.getCurrentSession fails with:
org.hibernate.HibernateException: Could not obtain transaction-synchronized Session for current thread;
Currently the problem might be solved with following:
Session session;
try {
session = sessionFactory.getCurrentSession();
} catch (HibernateException e) {
session = sessionFactory.openSession();
}
But telling the truth I do not like this solution.
Here is my hibernate properties:
hibernate.format_sql = false
hibernate.show_sql = false
hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
hibernate.hbm2ddl.auto = update
javax.persistence.validation.mode = none
WS interceptor config:
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
PayloadRootSmartSoapEndpointInterceptor interceptor = new PayloadRootSmartSoapEndpointInterceptor(
authEndpointInterceptor, LeadServiceEndpoint.NAMESPACE_URI, "");
interceptors.add(interceptor);
super.addInterceptors(interceptors);
}
So the question is - why there is no session in my session factory bean, when it is accessed from Spring WS interceptor?
Any help is greatly appreciated;
Adding more details:
Interceptor:
#Component
public class AuthEndpointInterceptor implements EndpointInterceptor {
#Autowired
private AuthProvider defaultAuthProvider;
#Override
public boolean handleRequest(MessageContext messageContext, Object endpoint) throws Exception {
// TODO Make marshaller as a bean
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setPackagesToScan("com.itsupportme.gpipeline.component.soap.ws.types");
SaajSoapMessage saajSoapMessage = (SaajSoapMessage) messageContext.getRequest();
SoapBody requestBody = saajSoapMessage.getSoapBody();
Object obj = marshaller.unmarshal(requestBody.getPayloadSource());
if (!(obj instanceof KeySignatureAware)) {
// TODO Implement bad response functionality
return false;
}
AuthResultInterface authResult = defaultAuthProvider.doAuth((KeySignatureAware) obj);
if (!authResult.getStatus()) {
// TODO Implement bad response functionality
return false;
}
return true;
}
Provider:
#Service
#Transactional
public class DefaultAuthProvider implements AuthProvider {
#Autowired
private CredentialsDao credentialsDao;
#Override
public AuthResultInterface doAuth(KeySignatureAware keySignatureAware) {
// Check key and signature
if (keySignatureAware.getKey() == null) {
return new AuthResult(false, "Key is not provided.");
}
if (keySignatureAware.getSignature() == null) {
return new AuthResult(false, "Signature is not provided.");
}
// Find if such credentials are there
Credentials credentials = credentialsDao.findByKey(keySignatureAware.getKey());
if (credentials == null) {
return new AuthResult(false, "Invalid key provided");
}
...
}
DAO:
#Repository
#Transactional
public class CredentialsDaoImpl implements CredentialsDao{
#Autowired
SessionFactory sessionFactory;
#Override
#SuppressWarnings("unchecked")
public Credentials findByKey(String key) {
List<Credentials> credentialsList;
Session session = sessionFactory.getCurrentSession();;
credentialsList = session
.createQuery("from Credentials where apiKey = :apiKey")
.setParameter("apiKey", key)
.list();
if (credentialsList.size() > 0){
return credentialsList.get(0);
} else {
return null;
}
}
}
Problem was solved by marking #Repository bean - CredentialsDaoImpl as PROTOTYPE_SCOPE bean. And retrieving repo. instance from application context;
As result spring creates new repository bean for thread in which repo. is called and (from my understanding) creates an appropriate session associated with this thread;
Related
I'm working with RedisHttpSession and my basic goal is to save the staff object in the session object on successful login, retrieve it wherever I need and destroy the session on logout.
On successful login, this is what I'm doing:
Staff staff = staffService.getEmailInstance(body.getEmailId());
request.getSession(true).setAttribute("staff", staff);
And Logout is simply this:
request.getSession().invalidate();
In a different controller, I am calling this utility method that checks if the staff is logged in: util.isStaffLoggedIn(request, response, StaffRole.EDITOR); If the staff is logged in, the API proceeds, else the user is redirected to the login page.
#Service
public class Util {
public boolean isStaffLoggedIn(HttpServletRequest request, HttpServletResponse response, StaffRole staffRole)
throws PaperTrueInvalidCredentialsException, PaperTrueJavaException {
Staff staff = (Staff) request.getSession().getAttribute("staff");
if (!isObjectNull(staff) && staff.getStaffRole().equals(staffRole)) {
return true;
}
invalidateSessionAndRedirect(request, response);
return false;
}
public void invalidateSessionAndRedirect(HttpServletRequest request, HttpServletResponse response)
throws PaperTrueJavaException, PaperTrueInvalidCredentialsException {
request.getSession().invalidate();
try {
response.sendRedirect(ProjectConfigurations.configMap.get("staff_logout_path"));
} catch (IOException e) {
throw new PaperTrueJavaException(e.getMessage());
}
throw new PaperTrueInvalidCredentialsException("Staff not loggedIn");
}
}
Now while the app is running, the get-jobs API is called immidiately after successful login. Most of the times the request.getSession().getAttribute("staff") method works fine and returns the 'staff' object but, once in a while, it returns null. This doesn't happen often, but it does. I printed the session Id to see if they are different after logout, and they were. After each logout I had a new session Id. I even checked if the staff object I retrieved from the database was null, but it wasn't.
The staff object was successfully saved in the sessions but I wasn't able to retrieve it in othe APIs. This is how my session config looks:
#EnableRedisHttpSession(maxInactiveIntervalInSeconds = 10800)
public class SessionConfig {
HashMap<String, String> configMap = ProjectConfigurations.configMap;
#Bean
public LettuceConnectionFactory connectionFactory() {
int redisPort = Integer.parseInt(configMap.get("redis_port"));
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(
configMap.get("redis_host"), redisPort);
redisStandaloneConfiguration.setPassword(configMap.get("redis_password"));
return new LettuceConnectionFactory(redisStandaloneConfiguration);
}
#Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName("PTSESSIONID");
serializer.setSameSite("none");
serializer.setUseSecureCookie(!configMap.get("staff_logout_path").contains("localhost"));
return serializer;
}
}
Please let me know if I missed out anything. Thanks in advance.
Update 1
I'm not invalidating the session anymore and I've replaced request.getSession(true).setAttribute("staff", staff); to request.getSession().setAttribute("staff", staff);
I'm setting the 'staff' in StaffController and getting it in EditorController. Here's how I'm setting it:
#RestController
#RequestMapping(path = { "/staff" }, produces = "application/json")
public class StaffApiController {
private final HttpServletRequest request;
private final HttpSession httpSession;
#Autowired
private StaffService staffService;
#Autowired
StaffApiController(HttpServletRequest request, HttpServletResponse response, HttpSession session) {
this.request = request;
this.httpSession = session;
}
#PostMapping("/login")
public ResponseEntity<StaffLoginResponse> login(#Valid #RequestBody StaffLoginBody body) {
StaffLoginResponse staffLoginResponse = new StaffLoginResponse();
try {
if (!staffService.isValidLogin(body.getEmailId(), body.getPassword())) {
throw new PaperTrueInvalidCredentialsException("Invalid Credentials");
}
Staff staff = staffService.getEmailInstance(body.getEmailId());
httpSession.setAttribute("staff", staff);
staffLoginResponse.setEmail(staff.getEmail()).setRole(staff.getStaffRole().getValue())
.setStaffID(staff.getId()).setStatus(new Status("Staff Login Successful"));
} catch (PaperTrueException e) {
httpSession.removeAttribute("staff");
staffLoginResponse.setStatus(new Status(e.getCode(), e.getMessage()));
}
return ResponseEntity.ok(staffLoginResponse);
}
#PostMapping("/logout")
public ResponseEntity<Status> logout() {
httpSession.removeAttribute("staff");
return ResponseEntity.ok(new Status("Staff Logged Out Successfully"));
}
}
If you are using Spring Security, you can create a custom "/login" endpoint that authenticates the user by setting the SecurityContext.
You can use the default logout behaviour provided by Spring Security.
If you do not need to supply the credentials in the body, you can use the default login behaviour provided by Spring Security and omit this Controller altogether.
This is intended as a starting point.
It does not offer comprehensive security, for example it may be vulnerable session fixation attacks.
#RestController
public class LoginController {
private AuthenticationManager authenticationManager;
public LoginController(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#PostMapping("/login")
public void login(#RequestBody StaffLoginBody body, HttpServletRequest request) {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(body.getUsername(), body.getPassword());
Authentication auth = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(auth);
HttpSession session = request.getSession();
session.setAttribute("staff", "staff_value");
}
#GetMapping("/jobs")
public String getStaffJobs(HttpServletRequest request) {
return request.getSession().getAttribute("staff").toString();
}
}
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// expose AuthenticationManager bean to be used in Controller
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorize -> authorize
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
)
// use built in logout
.logout(logout -> logout
.deleteCookies("PTSESSIONID")
);
}
}
You will need to add the Spring Security dependency to use this code org.springframework.boot:spring-boot-starter-security.
I'm new in the Spring's world I'm using Spring Boot 1.2.5 with Spring Security 3.1.2. Due to my project's requirements I need to configure an ACL security model. I have the following java class configuration:
#Configuration
public class ACLConfig {
#Autowired
DataSource dataSource;
#Bean
JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource);
}
#Bean
DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource);
}
#Bean
EhCacheBasedAclCache aclCache() {
EhCacheFactoryBean factoryBean = new EhCacheFactoryBean();
EhCacheManagerFactoryBean cacheManager = new EhCacheManagerFactoryBean();
cacheManager.setAcceptExisting(true);
cacheManager.setCacheManagerName(CacheManager.getInstance().getName());
cacheManager.afterPropertiesSet();
factoryBean.setName("aclCache");
factoryBean.setCacheManager(cacheManager.getObject());
factoryBean.setMaxBytesLocalHeap("16M");
factoryBean.setMaxEntriesLocalHeap(0L);
factoryBean.afterPropertiesSet();
return new EhCacheBasedAclCache(factoryBean.getObject());
}
#Bean
LookupStrategy lookupStrategy() {
return new BasicLookupStrategy(dataSource, aclCache(), aclAuthorizationStrategy(), new ConsoleAuditLogger());
}
#Bean
AclAuthorizationStrategy aclAuthorizationStrategy() {
return new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_SUPER_ADMIN"),
new SimpleGrantedAuthority("ROLE_SUPER_ADMIN"),
new SimpleGrantedAuthority("ROLE_SUPER_ADMIN"));
}
#Bean
JdbcMutableAclService aclService() {
JdbcMutableAclService service = new JdbcMutableAclService(dataSource, lookupStrategy(), aclCache());
service.setClassIdentityQuery("select currval(pg_get_serial_sequence('acl_class', 'id'))");
service.setSidIdentityQuery("select currval(pg_get_serial_sequence('acl_sid', 'id'))");
return service;
}
#Bean
AclEntryVoter aclDeleteVoter()
{
AclEntryVoter voter = new AclEntryVoter(aclService(),"ACL_NOMCITY_DELETE", new Permission[] {BasePermission.DELETE});
voter.setProcessDomainObjectClass(NomCity.class);
return voter;
}
#Bean
AclEntryVoter aclUpdateVoter()
{
return new AclEntryVoter(aclService(),"ACL_NOMCITY_UPDATE", new Permission[]{BasePermission.ADMINISTRATION});
}
#Bean
AclEntryVoter aclReadVoter()
{
return new AclEntryVoter(aclService(),"ACL_NOMCITY_READ", new Permission[]{BasePermission.READ});
}
#Bean
AccessDecisionManager accessDecisionManager (){
List<AccessDecisionVoter<? extends Object>> list = new ArrayList<>();
list.add(aclDeleteVoter());
list.add(aclReadVoter());
list.add(aclUpdateVoter());
return new AffirmativeBased(list);
}
}
I have the following RestController's methods, it use the ACLs defined earlier:
#RequestMapping(value = "/nomCitys",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE)
#Timed
#Transactional
#Secured({"ROLE_ADMIN","ROLE_USER"})
public ResponseEntity<NomCity> create(#Valid #RequestBody NomCity nomCity) throws URISyntaxException {
NomCity result = nomCityRepository.save(nomCity);
User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
ObjectIdentity oi = new ObjectIdentityImpl(NomCity.class,result.hashCode());
MutableAcl acl = mutableAclService.createAcl(oi);
acl.insertAce(0, BasePermission.ADMINISTRATION, new GrantedAuthoritySid("ROLE_ADMIN"), true);
acl.insertAce(1, BasePermission.DELETE, new PrincipalSid(user.getUsername()), true);
acl.insertAce(2, BasePermission.READ, new GrantedAuthoritySid("ROLE_USER"), true);
mutableAclService.updateAcl(acl);
return ResponseEntity.created(new URI("/api/nomCitys/" + result.getId()))
.headers(HeaderUtil.createEntityCreationAlert("nomCity", result.getId().toString()))
.body(result);
}
When I create a new city the following ACL entries are created too:
The user with ROLE_ADMIN role have Admin permission.
The user how create the city have Delete permission.
The user with ROLE_USER role can read the city.
The following method is the delete method:
#RequestMapping(value = "/nomCitys/{id}",
method = RequestMethod.DELETE,
produces = MediaType.APPLICATION_JSON_VALUE)
#Timed
#Transactional
#Secured("ACL_NOMCITY_DELETE")
public ResponseEntity<Void> delete(#PathVariable Long id) {
nomCityRepository.delete(id);
ObjectIdentity oid = new ObjectIdentityImpl(NomCity.class,id);
mutableAclService.deleteAcl(oid, true);
return ResponseEntity.ok().headers(HeaderUtil.createEntityDeletionAlert("nomCity", id.toString())).build();
}
When I create a new city all work fine, the ACL entries are created and stored in the data base, but when I go to remove a city I get a 403, although I'm logging with the user who created the city, reviewing some pages I saw the following xml entry:
<security:global-method-security
secured-annotations="enabled" access-decision-manager ref="customAccessDecisionManager" />
I suppose that it register the AccessDecisionManager but I don't know how do the same using Java Config and I don't if it's the reason of all my problems.
This question is for #secure anotation, but I finally solve the problem make a class configuration for using #Pre and #Post anotation, I post a config java class in my answer for this question.
at the moment I am trying to integrate Spring-Security-Oauth2, Zuul, OpenAM as OAuth2 authorization Server and a WCF REST API as resource Server. The final Setup should look something like the following:
I read the tutorial, which explains how to setup a SSO Environment with spring and AngularJS (sso with spring and angularJS), however in my case I would like to use OpenAM and password grant flow to authenticate useres.
So in the Spring Boot application my current config Looks as follows:
#SpringBootApplication
#EnableZuulProxy
#EnableOAuth2Client
public class ApolloUIProxyApplication {
public static void main(String[] args) {
SpringApplication.run(ApolloUIProxyApplication.class, args);
}
#Configuration
protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.logout().and().antMatcher("/**").authorizeRequests()
.antMatchers("/index.html", "/home.html", "/", "/login").permitAll()
.anyRequest().authenticated().and().csrf()
.csrfTokenRepository(csrfTokenRepository()).and()
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class)
.addFilterAfter(authenticationProcessingFilter(), CsrfFilter.class);
}
#Bean
public ZuulFilter tokenRelayFilter(){
JwtTokenRelayFilter filter = new JwtTokenRelayFilter();
filter.setRestTemplate(restTemplate());
return new JwtTokenRelayFilter();
}
#Bean
public ZuulFilter customTokenFilter(){
return new CustomZuulFilter();
}
#Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
return new JwtAccessTokenConverter();
}
private Filter csrfHeaderFilter() {
return new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class
.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null || token != null
&& !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
};
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
private OAuth2ClientAuthenticationProcessingFilter authenticationProcessingFilter(){
OAuth2ClientAuthenticationProcessingFilter processingFilter = new OAuth2ClientAuthenticationProcessingFilter("/login");
processingFilter.setRestTemplate(restTemplate());
processingFilter.setTokenServices(resourceServerTokenServices());
return processingFilter;
}
#Bean
public ResourceServerTokenServices resourceServerTokenServices(){
OpenAMRemoteTokenService remoteTokenServices = new OpenAMRemoteTokenService();
remoteTokenServices.setRestTemplate(restTemplate());
remoteTokenServices.setAccessTokenConverter(accessTokenConverter());
remoteTokenServices.setClientId("...");
remoteTokenServices.setClientSecret("...");
remoteTokenServices.setCheckTokenEndpointUrl("http://...");
return remoteTokenServices;
}
#Bean
public OAuth2RestTemplate restTemplate(){
OAuth2RestTemplate template = new OAuth2RestTemplate(resourceDetails(), clientContext());
return template;
}
#Bean
public AccessTokenProvider accessTokenProvider(){
ResourceOwnerPasswordAccessTokenProvider provider = new ResourceOwnerPasswordAccessTokenProvider();
return provider;
}
#Bean
public OAuth2ClientContext clientContext(){
return new OpenAMClientContext();
}
#Bean
public OAuth2ProtectedResourceDetails resourceDetails(){
ResourceOwnerPasswordResourceDetails details = new ResourceOwnerPasswordResourceDetails();
details.setGrantType("password");
details.setAccessTokenUri("http://...");
details.setScope(Arrays.asList("openid");
details.setClientId("...");
details.setClientSecret("...");
return details;
}
#Bean
public AccessTokenConverter accessTokenConverter(){
DefaultAccessTokenConverter tokenConverter = new DefaultAccessTokenConverter();
tokenConverter.setUserTokenConverter(userAuthenticationConverter());
return tokenConverter;
}
#Bean
public UserAuthenticationConverter userAuthenticationConverter(){
return new OpenAMUserAuthenticationConverter();
}
}
}
I wrote a custom RemoteTokenService, because otherwise Spring could not access the tokeninfo endpoint of OpenAM, which requires a GET-request and not a Post. This Connection works fine now, so that i get a valid access-token from OpenAM and can also query the tokeninfo.endpoint for token/user-Infos. The Authentication object gets created and is stored in the security-context of Spring. I can also access the Authentication Object in the ZuulFilter.
My Problem now is, that i had to tweek the "OAuth2ClientContext" to grab the users credentials from the servletRequest and put it on the "AccessTokenRequest". Otherwise I would have to hard-code them in the ResourceDetails, which is not appropriate in my case.
The result is, that the ClientContext (and also AccessTokenRequest I guess) is shared between all users of the System. What i want is a session scoped Client Context, so that I can have multiple useres logged in and can access the right SecurityContext for each user on every request.
So my question are,
1) how can I make the ClientContext and AccessTokenRequest session scoped?
2) Do I need to use Spring Session module?
3) Do I need to set the sessionStrategy
Thank you!
I have a web application on Tomcat 7.0.34, Spring 3.2.3, Spring Security 3.2.0.RC1 and Spring Social 1.1.
For some reason the Spring context is being loaded twice. The second load is happening immediately after the first load has finished. The log below shows the Context Loader loading the Root WebApplicationContext. Everything progresses normally and all the RequstMappingHandlers are registering correctly. Then immediately the context is refreshed again.
I've read several solutions on SO about ensuring you don't load the configuration as part of the Context Loader and the DispatcherServlet at the same time and have tested various combinations of this but that doesn't seem to have fixed it and I'm becoming code blind as well.
All my testing on this has pushed me to an annotation only configuration both for the container and Spring components but the config classes are pretty much copy and paste from the Spring Social examples on github. Although I've included the SocialConfig.java details below, this problem has been happening before I implemented Spring Social but I can't move on without fixing it.
Also, the problem was present with a hybrid xml (web.xml, security-app-context.xml) and annotation configuration.
I'm implementing WebApplicationInitializer rather than having a web.xml
public class WebClientInitialiser implements WebApplicationInitializer {
public void onStartup(ServletContext container) throws ServletException {
// Create the 'root' Spring application context
AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
// Manage the lifecycle of the root application context
appContext.setConfigLocation("com.mycompany.webclient.config");
appContext.setServletContext(container);
container.addListener(new ContextLoaderListener(appContext));
container.addListener(new MyCompanyContextListener());
container.addFilter("springSecurityFilterChain", new DelegatingFilterProxy("springSecurityFilterChain"))
.addMappingForUrlPatterns(null, false, "/*");
// Register and map the dispatcher servlet
Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(appContext));
dispatcher.addMapping("/");
dispatcher.setLoadOnStartup(1);
}
}
My MainConfig.java
/**
* Main configuration class for the application.
* Turns on #Component scanning, loads externalized application properties
* and imports legacy security configuration
*/
#Configuration
#ComponentScan(basePackages = "com.mycompany.webclient", excludeFilters = { #Filter(Configuration.class) })
#PropertySource("classpath:wc.properties")
#ImportResource("/WEB-INF/spring/appServlet/security-app-context.xml")
public class MainConfig {
#Bean
public PropertySourcesPlaceholderConfigurer propertyPlaceHolderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
My WebMvcConfig.java
#Configuration
#EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
#Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("/WEB-INF/messages/messages");
return messageSource;
}
#Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
My SocialConfig.java
#Configuration
#EnableSocial
public class SocialConfig implements SocialConfigurer {
private SocialUserDAO socialUserDao;
//
// SocialConfigurer implementation methods
//
#Override
public void addConnectionFactories(ConnectionFactoryConfigurer cfConfig, Environment env) {
String clientId="XXXXXX";
String clientSecret="XXXXX";
cfConfig.addConnectionFactory(new FacebookConnectionFactory(clientId, clientSecret));
}
#Override
public UserIdSource getUserIdSource() {
return new UserIdSource() {
#Override
public String getUserId() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
throw new IllegalStateException("Unable to get a ConnectionRepository: no user signed in");
}
return authentication.getName();
}
};
}
#Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
return new HibernateUsersConnectionRepository(socialUserDao, connectionFactoryLocator, Encryptors.noOpText());
}
//
// API Binding Beans
//
#Bean
#Scope(value="request", proxyMode=ScopedProxyMode.INTERFACES)
public Facebook facebook(ConnectionRepository repository) {
Connection<Facebook> connection = repository.findPrimaryConnection(Facebook.class);
return connection != null ? connection.getApi() : null;
}
#Bean
#Scope(value="request", proxyMode=ScopedProxyMode.INTERFACES)
public Twitter twitter(ConnectionRepository repository) {
Connection<Twitter> connection = repository.findPrimaryConnection(Twitter.class);
return connection != null ? connection.getApi() : null;
}
#Bean
#Scope(value="request", proxyMode=ScopedProxyMode.INTERFACES)
public LinkedIn linkedin(ConnectionRepository repository) {
Connection<LinkedIn> connection = repository.findPrimaryConnection(LinkedIn.class);
return connection != null ? connection.getApi() : null;
}
//
// Web Controller and Filter Beans
//
#Bean
public ConnectController connectController(ConnectionFactoryLocator connectionFactoryLocator, ConnectionRepository connectionRepository) {
ConnectController connectController = new ConnectController(connectionFactoryLocator, connectionRepository);
connectController.addInterceptor(new PostToWallAfterConnectInterceptor());
connectController.addInterceptor(new TweetAfterConnectInterceptor());
return connectController;
}
#Bean
public ProviderSignInController providerSignInController(ConnectionFactoryLocator connectionFactoryLocator, UsersConnectionRepository usersConnectionRepository) {
return new ProviderSignInController(connectionFactoryLocator, usersConnectionRepository, new SimpleSignInAdapter(new HttpSessionRequestCache()));
}
#Bean
public DisconnectController disconnectController(UsersConnectionRepository usersConnectionRepository, Environment env) {
return new DisconnectController(usersConnectionRepository, env.getProperty("facebook.clientSecret"));
}
#Bean
public ReconnectFilter apiExceptionHandler(UsersConnectionRepository usersConnectionRepository, UserIdSource userIdSource) {
return new ReconnectFilter(usersConnectionRepository, userIdSource);
}
}
Any help, comments, pointers is greatly appreciated.
This log output is repeated twice at the start of each context refresh:
org.springframework.web.context.ContextLoader- Root WebApplicationContext: initialization started
org.springframework.web.context.support.AnnotationConfigWebApplicationContext- Refreshing Root WebApplicationContext: startup date [Mon Nov 25 22:43:39 GMT 2013]; root of context hierarchy
org.springframework.context.annotation.ClassPathBeanDefinitionScanner- JSR-330 'javax.inject.Named' annotation found and supported for component scanning
org.springframework.web.context.support.AnnotationConfigWebApplicationContext- Registering annotated classes: [class com.mycompany.webclient.config.WebMvcConfig,class com.mycompany.webclient.config.SocialConfig]
You are passing the same context to both the ContextLoaderListener and DispatcherServlet and hence this will trigger loading the configuration twice.
You should have 2 seperate AnnotationConfigWebApplicationContext instances one for the ContextLoaderListener loading all your generic beans (services etc.) and one for the DispatcherServlet loading the web related things.
public class WebClientInitialiser implements WebApplicationInitializer {
public void onStartup(ServletContext container) throws ServletException {
// Create the 'root' Spring application context
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(MainConfig.class);
// Manage the lifecycle of the root application context
container.addListener(new ContextLoaderListener(rootContext));
container.addListener(new MyCompanyContextListener());
container.addFilter("springSecurityFilterChain", new DelegatingFilterProxy("springSecurityFilterChain"))
.addMappingForUrlPatterns(null, false, "/*");
AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
dispatcherContext.register(WebMvcConfig.class, SocialConfig.class);
// Register and map the dispatcher servlet
Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
dispatcher.addMapping("/");
dispatcher.setLoadOnStartup(1);
}
}
Something like this. Also you don't need to set the contextConfigLocation simply register the #Configuration annotated classes. Also setting the ServletContext is already done by Spring so no need for that to.
A note on your configuration, the PropertySourcesPlaceHolderConfigurer is enabled by default so no need to register that again in in MainConfig class.
Another thing to take into account is that now probably your application fails (i.e. your #Controllers don't work anymore). This is due to the fact that everything is inside the root application context whereas #Controllers should be loaded by the DispatcherServlet. To fix this you need to exclude #Controller scanning in your MainConfig and enable #Controller scanning on the WebMvcConfig class.
I have a java that calls a Servlet:
public class UserServlet extends HttpServlet {
#Autowired
private UserService userService;
#Override
protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
userService.checkUser();
userService.doSomethingRestricted();
}
#Override
public void init(final ServletConfig config) throws ServletException {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this, config.getServletContext());
super.init(config);
}
}
And my autowired service :
#Component(value = "userService")
public class UserService {
public boolean checkUser() {
if (SecurityContextHolder.getContext().getAuthentication() != null) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.getPrincipal() != null && auth.getPrincipal() instanceof User) {
User springUser = (User) auth.getPrincipal();
if (springUser != null) {
LOG.debug("USER CONNECTED :: {}", springUser.getUsername());
}
}
} else {
LOG.debug("NO CONNECTED USER, CREATING ONE");
Collection<GrantedAuthority> authorities = getGrantedAuthorities();
org.springframework.security.core.userdetails.User springUser = new org.springframework.security.core.userdetails.User("user","password", true, true, true, true, authorities);
Authentication auth = new UsernamePasswordAuthenticationToken(springUser, "", authorities);
SecurityContext sc = new SecurityContextImpl();
sc.setAuthentication(auth);
SecurityContextHolder.setContext(sc);
}
return true;
}
#Secured({ "CONNECTED" })
public void doSomethingRestricted() {
LOG.debug("SOMETHING RESTRICTED HAS BEEN DONE!!");
}
}
When I test my application the first time, the Java client sends a POST to the server, the server would check the user and would not find a context: a new context would be created.
When I run the java client the subsequent times, I find an existing Context (the one created in the first call).
Obviously there's something missing because If the first user logs in successfully it does not mean any user can connect.
What am I missing ? At first I thought about using sessions for each Java client's instance (I dont have web browser clients so I need to set the session ids manually), but when is Spring supposed to get or set the session id in the http request ?
TL;DR :
What does SecurityContextHolder.getContext().getAuthentication() do in my example ?
It gets you the authentication details of the current login user , have you added
<bean id="httpSessionFilter" class="org.springframework.security.web.context.SecurityContextPersistenceFilter"/>
to introduce login for web application , spring security is designed to work with POJO as well , you would need to add this filter in your mapping if you are doing it old way. If you are using http tags in applicationContext then it should work as it is.
</security:filter-chain-map>
Its been quite long since I have used spring security without the new http tags in applicatin Context . The spring security context comes with different filters , SecurityContextPersistenceFilter determines how the context is persisted.
"org.springframework.security.web.context.SecurityContextPersistenceFilter" is for persisting security context per session .
Spring security derived from its integration with acegi security which used to have "net.sf.acegisecurity.
ui.webapp.HttpSessionIntegrationFilter" filter for the same task
It is a filter , so spring can identify session based on sessionid.