Hello I want to make an intercept url pattern and access dynamically by using sql query in spring security.
Generally we use this type of notation in XML and I want to take these values (/add-role and ROLE_ADMIN) from database.
<intercept-url pattern="/add-role*" access="ROLE_ADMIN" />
Is it possible to do this dynamically?
Disclaimer
As the Spring Security FAQ mentions, the first thing you should do is ask should I really do this? Security is complicated and the configuration should be tested extensively. Allowing the configuration to change dynamically only further complicates things making the application that much more vulnerable. If you really want to do this, the FAQ outlines a basic method to accomplish this. I have expanded upon the FAQ's answer below.
Implement Custom FilterInvocationSecurityMetadataSource
To obtain the security URL mappings dynamically you can implement your own FilterInvocationSecurityMetadataSource. An example implementation is given below.
NOTE: Keep in mind that getAttributes will be invoked for every request that Spring Security intercepts so you will most likely want some sort of caching.
public class JdbcFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
FilterInvocation fi = (FilterInvocation) object;
String url = fi.getRequestUrl();
HttpServletRequest request = fi.getHttpRequest();
// Instead of hard coding the roles lookup the roles from the database using the url and/or HttpServletRequest
// Do not forget to add caching of the lookup
String[] roles = new String[] { "ROLE_ADMIN", "ROLE_USER" };
return SecurityConfig.createList(roles);
}
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}
Create a BeanPostProcessor
You cannot use the namespace to wire it up, so taking another tip from the FAQ you can use a BeanPostProcessor which might look like:
public class FilterInvocationSecurityMetadataSourcePostProcessor implements BeanPostProcessor, InitializingBean {
private FilterInvocationSecurityMetadataSource securityMetadataSource;
public Object postProcessAfterInitialization(Object bean, String name) {
if (bean instanceof FilterSecurityInterceptor) {
((FilterSecurityInterceptor)bean).setSecurityMetadataSource(securityMetadataSource);
}
return bean;
}
public Object postProcessBeforeInitialization(Object bean, String name) {
return bean;
}
public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource securityMetadataSource) {
this.securityMetadataSource = securityMetadataSource;
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(securityMetadataSource,"securityMetadataSource cannot be null");
}
}
XML Configuration
Then, assuming both of the above beans are in the package sample, you would add the following configuration
<bean class="sample.FilterInvocationSecurityMetadataSourcePostProcessor">
<property name="securityMetadataSource">
<bean class="sample.JdbcFilterInvocationSecurityMetadataSource"/>
</property>
</bean>
Possible Problems
If you end up getting a ClassCastException, you are likely running into SEC-1957 which was fixed in Spring Security 3.1.1+ Try updating to the latest version to resolve this.
You cant really get those values from the databse, but you can write a custom code called DecisionManager that evaluates if the resource is allowed to execute. With that code you can even read data from the database.
<bean id="MyDecisionManagerBean" class="org.springframework.security.vote.UnanimousBased">
<property name="decisionVoters">
<list>
<!-- <bean class="org.springframework.security.vote.RoleVoter"/> -->
<bean class="org.springframework.security.vote.RoleHierarchyVoter" >
<constructor-arg>
<bean class="org.springframework.security.userdetails.hierarchicalroles.RoleHierarchyImpl" factory-bean="roleHierarchyImplFactory" factory-method="createRoleHierarchyImpl"/>
</constructor-arg>
</bean>
<bean class="com.mycompany.RoleDenyVoter"/>
<bean class="com.mycompany.RoleAllowVoter"/>
</list>
</property>
</bean>
Your class will be like this :
public class RoleDenyVoter implements AccessDecisionVoter {
public int vote(Authentication authentication, Object object, ConfigAttributeDefinition config) {
//read from the DB and decide if access is granted
the process is documented here :
http://static.springsource.org/spring-security/site/docs/3.0.x/reference/authz-arch.html#authz-voting-based
I have created this entry for update purpose
Implement Custom FilterInvocationSecurityMetadataSource
This class only obtains the URL in every request and lookup their permissions from the database or third party applications
public class CommonFilterSecurityMetaDataSource implements FilterInvocationSecurityMetadataSource {
private final Map<String, UrlRequestModel> permissions;
#Autowired
private UrlRequestDao urlRequestDao;
public CommonFilterSecurityMetaDataSource() {
permissions = new Hashtable<>();
}
public List<ConfigAttribute> getAttributes(Object object) {
final FilterInvocation fi = (FilterInvocation) object;
final String url = fi.getRequestUrl();
final String httpMethod = fi.getRequest().getMethod();
final String key = String.format("%s %s", httpMethod, url);
final UrlRequestModel urlRequestModel;
List<ConfigAttribute> attributes = null;
// Lookup your database (or other source) using this information and populate the
// list of attributes
if(permissions.containsKey(key)) {
urlRequestModel= permissions.get(key);
} else {
urlRequestModel= catRequestDao.findByUrl(url);
if(catRequestMapModel != null) {
permissions.put(key, urlRequestModel);
}
}
if (catRequestMapModel != null) {
List<RoleModel> roles = ulrRequestModel.getRoleList();
if(!roles.isEmpty()) {
attributes = new ArrayList<>(roles.size());
for (RoleModel role : roles) {
attributes.add(new SecurityConfig(role.getDescription()));
}
}
}
return attributes;
}
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}
Java configuration
For java configuration only add this to your class wich extends from WebSecurityConfigurerAdapter
#Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().frameOptions().disable();
http.authorizeRequests().
antMatchers( "/javax.faces.resource/**").permitAll().
and()
.exceptionHandling().accessDeniedPage("/accessDenied.jsf").
and().formLogin().
loginPage("/login.jsf").
loginProcessingUrl("/loginAction").
usernameParameter("app_username").
passwordParameter("app_password").
defaultSuccessUrl("/secure/index.jsf").
and().logout().
logoutUrl("/appLogout").
logoutSuccessUrl("/login.jsf").logoutRequestMatcher(new AntPathRequestMatcher("/appLogout")).
and().addFilterAfter(filterSecurityInterceptor(), FilterSecurityInterceptor.class);
http.csrf().disable();
}
#Bean
public FilterSecurityInterceptor filterSecurityInterceptor() throws Exception {
FilterSecurityInterceptor filterSecurityInterceptor = new FilterSecurityInterceptor();
filterSecurityInterceptor.setSecurityMetadataSource(securityMetadataSource());
filterSecurityInterceptor.setAuthenticationManager(authenticationManager());
filterSecurityInterceptor.setAccessDecisionManager(accessDecisionManager());
filterSecurityInterceptor.setPublishAuthorizationSuccess(true);
return filterSecurityInterceptor;
}
#Bean
public AccessDecisionManager accessDecisionManager() {
AuthenticatedVoter authenticatedVoter = new AuthenticatedVoter();
RoleVoter roleVoter = new RoleVoter();
List<AccessDecisionVoter<? extends Object>> voters = new ArrayList<>();
voters.add(authenticatedVoter);
voters.add(roleVoter);
return new AffirmativeBased(voters);
}
#Bean
public FilterInvocationSecurityMetadataSource securityMetadataSource() {
return new CommonFilterSecurityMetaDataSource();
}
I tested it using Spring security 5.0.8
Related
I am new to Spring Transaction. Something that I found really odd, probably I did understand this properly.
I wanted to have a transactional around method level and I have a caller method within the same class and it seems like it does not like that, it has to be called from the separate class. I don't understand how is that possible.
If anyone has an idea how to resolve this issue, I would greatly appreciate. I would like to use the same class to call the annotated transactional method.
Here is the code:
public class UserService {
#Transactional
public boolean addUser(String userName, String password) {
try {
// call DAO layer and adds to database.
} catch (Throwable e) {
TransactionAspectSupport.currentTransactionStatus()
.setRollbackOnly();
}
}
public boolean addUsers(List<User> users) {
for (User user : users) {
addUser(user.getUserName, user.getPassword);
}
}
}
It's a limitation of Spring AOP (dynamic objects and cglib).
If you configure Spring to use AspectJ to handle the transactions, your code will work.
The simple and probably best alternative is to refactor your code. For example one class that handles users and one that process each user. Then default transaction handling with Spring AOP will work.
Configuration tips for handling transactions with AspectJ
To enable Spring to use AspectJ for transactions, you must set the mode to AspectJ:
<tx:annotation-driven mode="aspectj"/>
If you're using Spring with an older version than 3.0, you must also add this to your Spring configuration:
<bean class="org.springframework.transaction.aspectj
.AnnotationTransactionAspect" factory-method="aspectOf">
<property name="transactionManager" ref="transactionManager" />
</bean>
In Java 8+ there's another possibility, which I prefer for the reasons given below:
#Service
public class UserService {
#Autowired
private TransactionHandler transactionHandler;
public boolean addUsers(List<User> users) {
for (User user : users) {
transactionHandler.runInTransaction(() -> addUser(user.getUsername, user.getPassword));
}
}
private boolean addUser(String username, String password) {
// TODO call userRepository
}
}
#Service
public class TransactionHandler {
#Transactional(propagation = Propagation.REQUIRED)
public <T> T runInTransaction(Supplier<T> supplier) {
return supplier.get();
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
public <T> T runInNewTransaction(Supplier<T> supplier) {
return supplier.get();
}
}
This approach has the following advantages:
It may be applied to private methods. So you don't have to break encapsulation by making a method public just to satisfy Spring limitations.
Same method may be called within different transaction propagations and it is up to the caller to choose the suitable one. Compare these 2 lines:
transactionHandler.runInTransaction(() -> userService.addUser(user.getUserName, user.getPassword));
transactionHandler.runInNewTransaction(() -> userService.addUser(user.getUserName, user.getPassword));
It is explicit, thus more readable.
The problem here is, that Spring's AOP proxies don't extend but rather wrap your service instance to intercept calls. This has the effect, that any call to "this" from within your service instance is directly invoked on that instance and cannot be intercepted by the wrapping proxy (the proxy is not even aware of any such call). One solutions is already mentioned. Another nifty one would be to simply have Spring inject an instance of the service into the service itself, and call your method on the injected instance, which will be the proxy that handles your transactions. But be aware, that this may have bad side effects too, if your service bean is not a singleton:
<bean id="userService" class="your.package.UserService">
<property name="self" ref="userService" />
...
</bean>
public class UserService {
private UserService self;
public void setSelf(UserService self) {
this.self = self;
}
#Transactional
public boolean addUser(String userName, String password) {
try {
// call DAO layer and adds to database.
} catch (Throwable e) {
TransactionAspectSupport.currentTransactionStatus()
.setRollbackOnly();
}
}
public boolean addUsers(List<User> users) {
for (User user : users) {
self.addUser(user.getUserName, user.getPassword);
}
}
}
With Spring 4 it's possible to Self autowired
#Service
#Transactional
public class UserServiceImpl implements UserService{
#Autowired
private UserRepository repository;
#Autowired
private UserService userService;
#Override
public void update(int id){
repository.findOne(id).setName("ddd");
}
#Override
public void save(Users user) {
repository.save(user);
userService.update(1);
}
}
This is my solution for self invocation:
public class SBMWSBL {
private SBMWSBL self;
#Autowired
private ApplicationContext applicationContext;
#PostConstruct
public void postContruct(){
self = applicationContext.getBean(SBMWSBL.class);
}
// ...
}
You can autowired BeanFactory inside the same class and do a
getBean(YourClazz.class)
It will automatically proxify your class and take into account your #Transactional or other aop annotation.
Here is what I do for small projects with only marginal usage of method calls within the same class. In-code documentation is strongly advised, as it may look strange to colleagues. But it works with singletons, is easy to test, simple, quick to achieve and spares me the full blown AspectJ instrumentation. However, for more heavy usage I'd advice the AspectJ solution as described in Espens answer.
#Service
#Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
class PersonDao {
private final PersonDao _personDao;
#Autowired
public PersonDao(PersonDao personDao) {
_personDao = personDao;
}
#Transactional
public void addUser(String username, String password) {
// call database layer
}
public void addUsers(List<User> users) {
for (User user : users) {
_personDao.addUser(user.getUserName, user.getPassword);
}
}
}
The issue is related to how spring load classes and proxies. It will not work , untill you write your inner method / transaction in another class or go to other class and then again come to your class and then write the inner nested transcation method.
To summarize, spring proxies does not allow the scenarios which you are facing. you have to write the 2nd transaction method in other class
There is no point to use AspectJ or Other ways. Just using AOP is sufficient. So, we can add #Transactional to addUsers(List<User> users) to solve current issue.
public class UserService {
private boolean addUser(String userName, String password) {
try {
// call DAO layer and adds to database.
} catch (Throwable e) {
TransactionAspectSupport.currentTransactionStatus()
.setRollbackOnly();
}
}
#Transactional
public boolean addUsers(List<User> users) {
for (User user : users) {
addUser(user.getUserName, user.getPassword);
}
}
}
My application uses Spring 4.3.x, EhCache 3.6 and javax Cache 1.1.0.
Here is how I've configured javax CacheManager in my application:
<bean id="jCacheManager" class="org.springframework.cache.jcache.JCacheCacheManager">
<property name="cacheManager" ref="appCacheManagerFactoryBean" />
</bean>
<bean id="appCacheManagerFactoryBean" class="com.example.AppCacheManagerFactoryBean"/>
The AppCacheManagerFactoryBean (which is just a customized version of JCacheManagerFactoryBean) helps me to configure a global persistence directory for my app. Here is how it looks:
public class AppCacheManagerFactoryBean implements FactoryBean<CacheManager>, InitializingBean,
DisposableBean {
#Value("${cache.persistenceDir}")
private String persistenceDir;
private CacheManager cacheManager;
#Override
public void afterPropertiesSet() {
this.cacheManager = buildCacheManager();
}
private CacheManager buildCacheManager()
{
EhcacheCachingProvider cachingProvider = (EhcacheCachingProvider) Caching.getCachingProvider();
DefaultConfiguration defaultConfiguration = new DefaultConfiguration(cachingProvider.getDefaultClassLoader(),
new DefaultPersistenceConfiguration(new File(persistenceDir)));
return cachingProvider.getCacheManager(cachingProvider.getDefaultURI(), defaultConfiguration);
}
#Override
public CacheManager getObject() {
return this.cacheManager;
}
#Override
public Class<?> getObjectType() {
return (this.cacheManager != null ? this.cacheManager.getClass() : CacheManager.class);
}
#Override
public boolean isSingleton() {
return true;
}
#Override
public void destroy() {
this.cacheManager.close();
}
}
Here's how I define caches. I use Ehcache API to create my caches as some of the features my caches need are not available through JCache API.
EhcacheManager ehcacheManager = jCacheCacheManager.getCacheManager().unwrap(EhcacheManager.class);
ehcacheManager.createCache("foo", CacheConfigurationBuilder.newCacheConfigurationBuilder(
String.class, Foo.class,
ResourcePoolsBuilder.newResourcePoolsBuilder()
.heap(1)
.offheap(1, MemoryUnit.GB)
.disk(5, MemoryUnit.GB)
));
When I try to retrieve a cache from the CacheManager elsewhere in my app, a null pointer exception is thrown.
Caching.getCachingProvider().getCacheManager().getCache("foo");
However, if I retrieve the cache after invoking the getCacheNames() method in CacheManager, the cache is fetched normally.
Caching.getCachingProvider().getCacheManager().getCacheNames();
Caching.getCachingProvider().getCacheManager().getCache("foo");
What have I missed? Please help me.
My first question would be: "Why not using the built-in support of Spring-cache?" It won't need to do that. The JCacheCacheManager will take care of everything.
Then, your problem is that the cache is created directly in Ehcache without passing through the JSR107 layer. Calling getCacheNames() causes a refresh of the cache list in JSR107 to make it work. However, I'm not sure this global behavior is intended. But it is the way it works.
The solution is to create a cache as expected which is through the JSR107 layer. It looks like this
CacheManager cacheManager = jcacheCacheManager.getCacheManager();
cacheManager.createCache("foo",
Eh107Configuration.fromEhcacheCacheConfiguration(
CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, Foo.class, ResourcePoolsBuilder.newResourcePoolsBuilder()
.heap(1)
.offheap(1, MemoryUnit.GB)
.disk(5, MemoryUnit.GB))
.build()));
I have problem making Spring Cache working in OSGi Environment. Maybe you can show me what i am missing.
I have configured Spring Cache successfully to work during tests like
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:spring/spring-test.xml"})
public class CacheDictTest {
#Autowired
Dictionary dictionary;
#Test
public void getDict5Times() {
for (int i = 0; i < 5; i++) {
System.out.println(dictionary.getSourceDomains());
}
Assert.assertTrue(true);
}
}
The select is executed once and then i have 5 nice prints.
However I cannot make it work in a bundle
The Cacheable annotation seems to be ignored. Queries are performed everytime i call dictionary.getSourceDomains().
I use ServiceMix 5.3.0 as a container.
My configuration:
<cache:annotation-driven cache-manager="cacheManager"/>
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="dictionary"/>
</set>
</property>
</bean>
dictionary:
public class DictionaryImpl implements Dictionary {
private DictionaryDao repository;
public DictionaryDao getRepository() {
return repository;
}
public void setRepository(DictionaryDao repository) {
this.repository = repository;
}
#Override
public List<String> getSourceDomains() {
List<DictEntry> entries = repository.getDictionary(DictTypeEnum.SOURCE_DOMAIN);
List<String> domains = new ArrayList<>();
for(DictEntry entry : entries) {
domains.add(entry.getKey());
}
return domains;
}
}
and dao
public class DictionaryDaoImpl extends BaseDaoImpl implements DictionaryDao {
private static final Logger LOG = LoggerFactory.getLogger(DictionaryDaoImpl.class);
#Override
#Cacheable(value="dictionary", key="#type")
public List<DictEntry> getDictionary(DictTypeEnum type) {
LOG.info("Loading {}", type);
Query q = getSession().createQuery("from DictEntry where type=:type");
q.setParameter("type", new DictType(type.getTypeId()));
List results = q.list();
LOG.debug("Results {}", results);
return results;
}
}
What i tried
Moving #Cacheable annotation to DictionaryDao (interface), to DictionaryImpl or Dictionary (interface) - no effect.
Use different cache implementantion (ehcache instead of JDK ConcurrentMap-based Cache) - no effect
The problem was missing import of Cacheable annotation package in osgi manifest.
org.springframework.cache.annotation
Servicemix did not show any error for missing class, just let the service work ignoring Cacheable annotation.
I am trying to set the database name as the request input parameter from the spring security login page. At present I am only getting username that's been retrieved using spring security SecurityContextHolder.getContext().getAuthentication().
How to access the additional field that's been set on the login page?
There's a number of ways to do this but the official way to do it is using a custom AuthenticationDetails and AuthenticationDetailsSource, subclassing Spring's WebAuthenticationDetails and WebAuthenticationDetailsSource, respectively. Add the extra field to the custom WebAuthenticationDetails and have the custom WebAuthenticationDetailsSource get the data from the request to populate the field.
In Spring Security 3.1 it's easy to configure by using the authentication-details-source-ref attribute of the <form-login> element.
In 3.0 you have to use a BeanPostProcessor. There is an example in the Spring Security FAQ on using a BeanPostProcessor to configure a custom WebAuthenticationDetailsSource.
Once this is done then you can call SecurityContextHolder.getContext().getAuthentication().getDetails() to get access to your extra field.
Elaborating on #Vacuum's comment
Here's a simple way (untested, but I believe this would work)
Create a new class ExUsernamePasswordAuthenticationFilter that will extend the default filter and grab the additional parameter and store it in the session. It will look something like this:
public class ExUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
final String dbValue = request.getParameter("dbParam");
request.getSession().setAttribute("dbValue", dbValue);
return super.attemptAuthentication(request, response);
}
}
In your UserDetailsService implementation, modify your implementation of:
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException;
to grab the session variable that the filter from step 1) makes available.
in your <http /> security set-up, override the default filter with your custom one
<custom-filter ref="beanForYourCustomFilterFromStep1" position="FORM_LOGIN_FILTER"/>
Refer to this part of the documentation for more info about custom filters: http://static.springsource.org/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#ns-custom-filters
sourcedelica mentioned using AuthenticationDetailsSource and a custom AuthenticationDetails.
Here is an example.
Add authentication-details-source-ref attribute with the bean id customWebAuthenticationDetailsSource to form-login:
<security:http>
<security:intercept-url pattern="/**" access="..." />
<security:form-login authentication-details-source-ref="customWebAuthenticationDetailsSource" login-page="..." />
<security:logout logout-success-url="..." />
</security:http>
Create a new class CustomWebAuthenticationDetailsSource:
package security;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import javax.servlet.http.HttpServletRequest;
public class CustomWebAuthenticationDetailsSource implements AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> {
#Override
public WebAuthenticationDetails buildDetails(HttpServletRequest context) {
return new CustomWebAuthenticationDetails(context);
}
}
and the related CustomWebAuthenticationDetails:
package security;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import javax.servlet.http.HttpServletRequest;
public class CustomWebAuthenticationDetails extends WebAuthenticationDetails {
private final String yourParameter;
public CustomWebAuthenticationDetails(HttpServletRequest request) {
super(request);
yourParameter = request.getParameter("yourParameter");
}
public String getyourParameter() {
return yourParameter;
}
//TODO override hashCode, equals and toString to include yourParameter
#Override
public int hashCode() { /* collapsed */ }
#Override
public boolean equals(Object obj) { /* collapsed */ }
#Override
public String toString() { /* collapsed */ }
}
There is an easier way if you are using custom AuthenticationProvider. You can just inject HttpServletRequest and retrieve your extra parameter:
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired(required = false)
private HttpServletRequest request;
#Autowired
private MyAccountService myAccountService;
#Override
public Authentication authenticate(Authentication authentication) {
System.out.println("request testing= " + request.getParameter("testing"));
.....
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
#user1322340 does not provide implement detail to get session Attributes in loadUserByUsername function:
Step 1: Follow all the step provided by #user1322340
Step 2:
you need add one configuration in web.xml like this:
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
Step 3:
Use such code to get attributes:
RequestContextHolder.getRequestAttributes().getAttribute("yourAttributeName", RequestAttributes.SCOPE_SESSION);
Step 4: Register your filter in spring security config.
If you get a error "authenticationManager must be specified". after you register your filter in config. You need set a authenticationManagerBean for your extended filter and config it in that way:
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public ExUsernamePasswordAuthenticationFilter exUsernamePasswordAuthenticationFilter()
throws Exception {
ExUsernamePasswordAuthenticationFilter exUsernamePasswordAuthenticationFilter = new ExUsernamePasswordAuthenticationFilter();
exUsernamePasswordAuthenticationFilter
.setAuthenticationManager(authenticationManagerBean());
return exUsernamePasswordAuthenticationFilter;
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
RequestMatcher requestMatcher = new RequestMatcher() {
#Override
public boolean matches(HttpServletRequest httpServletRequest) {
if (httpServletRequest.getRequestURI().indexOf("/api", 0) >= 0) {
return true;
}
return false;
}
};
http
.addFilterBefore(exUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
...
}
}
For spring security 3.0 or above which uses java configuration, the following simple steps works well.
Add a your of filter before the
UserNameandPasswordAuthenticationFilter in HttpSecurity object in configure.
http.addFilterBefore(new YourFilter(), UsernamePasswordAuthenticationFilter.class);
Let the filter has a line like this to get the needed fields in your
request to session.
if(requestPath != null &&requestPath.equals("/login") ) {
session.setAttribute("yourParam",req.getParameter("yourParam"));
}
Later you may get the parameter value from the session in any class as:
String yourParam =(String)request.getSession().getAttribute("yourParam");
Simple way:
1) register RequestContextListener
#Bean
public RequestContextListener requestContextListener(){
return new RequestContextListener();
}
2) And to main class:
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.
currentRequestAttributes()).
getRequest();
3) After that we can take params in custom headers:
request.getHeader("OrganizationId")
Simplest way in only 2 steps:
Step 1.
Add the following listener in web.xml:
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</context-param>
Step 2.
Add the following in your class method where you want to get additional param:
RequestAttributes attribs = RequestContextHolder.getRequestAttributes();
if (RequestContextHolder.getRequestAttributes() != null) {
HttpServletRequest request = ((ServletRequestAttributes) attribs).getRequest();
}
Now you can get your additional parameter by the following, assuming the extra parameter is named "loginType":
request.getParameter("loginType")
I'm using Spring 3.1.0.RELEASE with Spring Security 3.1. I want to inject my Spring user (i.e. the user who is currently logged in) into a controller. I want to do this as opposed to using
SecurityContextHolder.getContext().getAuthentication().getPrincipal();
because it allows me to test the controller more easily with JUnit. However, I'm having a problem with my current setup. My question is, what is the correct way to inject my user (per request) into my controller? In my application context file, I have ...
<bean id="userDetails" class="com.myco.eventmaven.security.SecurityHolder" factory-method="getUserDetails" scope="request">
<aop:scoped-proxy />
</bean>
where I define my factory class as ...
public class SecurityHolder {
#Autowired
private static UserService userService;
public static MyUserDetails getUserDetails() {
final Authentication a = SecurityContextHolder.getContext().getAuthentication();
if (a == null) {
return null;
} else {
final MyUserDetails reg = (MyUserDetails) a.getPrincipal();
final int userId = reg.getId();
final MyUserDetails foundUser = userService.findUserById(userId);
return foundUser;
} // if
} // getUserDetails
}
but the factory class repeatedly dies because "userService" fails to get autowired (the value is always null). I'm looking for a better way to do all this that can easily also integrate into my JUnit test. Any ideas?
Edit: Here's the JUnit test I'm looking to work with ...
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({ "file:src/test/resources/testApplicationContext.xml" })
public class UserEventFeedsControllerTest extends AbstractTransactionalJUnit4SpringContextTests {
private MockHttpServletRequest request;
private MockHttpServletResponse response;
...
#Autowired
private RequestMappingHandlerAdapter handlerAdapter;
#Autowired
private RequestMappingHandlerMapping handlerMapping;
#Before
public void setUp() {
...
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
}
...
#Test
public void testSubmitUserEventFeedsForm() throws Exception {
request.setRequestURI("/eventfeeds.jsp");
request.setMethod("POST");
final List<EventFeed> allEventFeeds = getAllEventFeeds();
request.setParameter("userEventFeeds", allEventFeeds.get(0).getId().toString());
final Object handler = handlerMapping.getHandler(request).getHandler();
final ModelAndView mav = handlerAdapter.handle(request, response, handler);
assertViewName(mav, "user/eventfeeds");
}
You cannot autowire static fields. There are some workarounds, but I don't want to show them to you...
There are plenty of ways to access current user in an easier and more elegant matter:
Inject Principal to your controller (see When using Spring Security, what is the proper way to obtain current username (i.e. SecurityContext) information in a bean?):
public ModelAndView showResults(final HttpServletRequest request, Principal principal) {
final String currentUser = principal.getName();
UserDetails ud = ((Authentication)principal).getPrincipal()
Develop your custom facade over SecurityContext
Replace built-in contextHolderStrategy in SecurityContextHolder for the purpose of testing
See also
How to get active user's UserDetails
Spring 3 MVC Controller integration test - inject Principal into method