I am using Spring 4 with Spring Security 3.2. The application is running perfectly so far. Now I am writing Test Cases for all the REST services using MockMVC and RestAssured.
I have a simple Controller
#RestController
#RequestMapping("/secure")
public class MyController{
#RequestMapping(method = RequestMethod.GET)
public String getAllMedia(){
return "SECURE TEST COMPLETE";
}
}
And I have a spring-rest-servlet.xml file which has <mvc:annotation-driven />
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<mvc:annotation-driven />
<context:component-scan base-package="com.mp.web.controller.common" />
</beans>
And spring-security-context.xml looks like this
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<security:http create-session="stateless"
entry-point-ref="authenticationEntryPoint"
authentication-manager-ref="authenticationManager">
<security:custom-filter ref="customRestFilter"
position="BASIC_AUTH_FILTER" />
<security:intercept-url pattern="/api/p/**" access="ROLE_USER" />
<security:intercept-url pattern="/api/login**"
access="IS_AUTHENTICATED_ANONYMOUSLY" requires-channel="https" />
</security:http>
<bean id="authenticationEntryPoint"
class="com.mp.web.security.CustomAuthenticationEntryPoint" />
<bean id="customRestFilter"
class="com.mp.web.security.filter.AuthenticationTokenProcessingFilter">
<constructor-arg name="authenticationManager"
ref="authenticationManager" />
</bean>
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="restAuthenticationProvider" />
</security:authentication-manager>
<bean id="restAuthenticationProvider"
class="com.mp.web.security.auth.RestAuthenticationProvider" />
</beans>
The application is just working as expected but whey I run the test case the spring security filter does not execute 'customRestFilter'
Here is my Test Class
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration({"classpath:application-context.xml",
"classpath:spring-rest-servlet.xml",
"classpath:spring-security-context.xml"})
public class Test {
#Autowired
private FilterChainProxy springSecurityFilterChain;
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
MyController myController;
#Before
public void init(){
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.addFilter(springSecurityFilterChain).build();
}
#Test
public void test1() throws Exception {
given().mockMvc(mockMvc).standaloneSetup(myController)
.header("Authorization", userHeaderToken) // Token for user
.when().get("/secure")
.then().log().body();
}
}
The userHeaderToken is used to identify which from which user I got the request from and its all handled in 'customRestFilter'. But my requests from Test case never come to the filter.
Can some one guide me how to get the request pass through that filter.
Thanks.
Related
I am working on a Spring Boot project, which uses LDAP in Spring Security for authentication.
I need to automate the login once the user hits the login page based on the roles in LDAP group provided in Spring Security.
If user has any role in the group mentioned in LDAP, then it must redirect to the corresponding page after login. (i.e page1 in my example).
I have been searching 2 days in a row for this for any online documentation or an example, but in vain. All I could find is using a jdbcDataSource or hard coding the username and password in Controller and later validating it when login or through Spring using web.xml. But not via LDAP. Any help would be much helpful.
This is how my Spring Security XML looks:
<?xml version="1.0" encoding="UTF-8" ?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<http auto-config="true" use-expressions="true">
<intercept-url pattern="/login" access="permitAll" />
<intercept-url pattern="/logout" access="permitAll" />
<intercept-url pattern="/webjars/**" access="permitAll" />
<intercept-url pattern="/page1" access="hasAnyRole('GP1','GP2')" />
<intercept-url pattern="/page2" access="hasAnyRole('GP1','GP2')" />
<intercept-url pattern="/page3" access="hasAnyRole('GP1','GP2')" />
<intercept-url pattern="/**" access="permitAll" />
<form-login default-target-url="/page1" login-page="/login"
always-use-default-target="true" />
<access-denied-handler error-page="/403.html" />
<csrf disabled="true" />
<logout logout-url="/logout" />
</http>
<authentication-manager alias="authenticationManager"
erase-credentials="false">
<authentication-provider ref="ldapAuthProvider" />
</authentication-manager>
<ldap-server id="contextSource" url="ldap://url"
manager-dn="mymanagerdn" manager-password="mymanagerpswd" />
<beans:bean id="ldapAuthProvider"
class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
<beans:constructor-arg>
<beans:bean id="bindAuthenticator"
class="org.springframework.security.ldap.authentication.BindAuthenticator">
<beans:constructor-arg ref="contextSource" />
<beans:property name="userSearch" ref="userSearch" />
</beans:bean>
</beans:constructor-arg>
<beans:constructor-arg>
<beans:bean
class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator">
<beans:constructor-arg ref="contextSource" />
<beans:constructor-arg value="myDCvalues" />
<beans:property name="searchSubtree" value="true" />
<beans:property name="ignorePartialResultException"
value="true" />
<beans:property name="groupSearchFilter" value="(member={0})" />
</beans:bean>
</beans:constructor-arg>
</beans:bean>
<beans:bean id="userSearch"
class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
<beans:constructor-arg index="0"
value="myDCvalues" />
<beans:constructor-arg index="1"
value="(sAMAccountName={0})" />
<beans:constructor-arg index="2" ref="contextSource" />
<beans:property name="searchSubtree" value="true" />
</beans:bean>
</beans:beans>
My WebController:
package com.myPackage;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.springframework.context.annotation.Bean;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
#Controller
public class WebController extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/page1").setViewName("page1");
registry.addViewController("/page2").setViewName("page2");
registry.addViewController("/page3").setViewName("page3");
registry.addViewController("/login").setViewName("login");
registry.addViewController("/403").setViewName("error/403");
}
#GetMapping("/page1")
public String page1(HttpSession session) {
return "page1";
}
#GetMapping("/page2")
public String page2(HttpSession session) {
return "page2";
}
#GetMapping("/page3")
public String page3(HttpSession session) {
return "page3";
}
#GetMapping("/login")
public String login() {
return "login";
}
#GetMapping("/403")
public String error403() {
return "error/403";
}
#Bean
public ViewResolver getViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("templates/");
return resolver;
}
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
private String getCredentials() {
String credential = null;
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
credential = userDetails.getUsername().toString();
return credential;
}
}
For your convenience, this answer comes with a complete and working sample, including an LDAP server, populated with users and groups and an integration test, so that you can run these tests yourself.
Assume you have the following user in LDAP
dn: cn=marissa,ou=Users,dc=test,dc=com
changetype: add
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: marissa
userPassword: koala
uid: 20f459e0-e30b-4d1f-998c-3ded7f769db1
mail: marissa#test.com
sn: Marissa
The username is marissa and the password is koala.
Let's start with the test case:
#Test
#DisplayName("ldap login works")
void doLogin() throws Exception {
mvc.perform(
MockMvcRequestBuilders.post("/login")
.param("username", "marissa")
.param("password", "koala")
.with(csrf())
)
.andExpect(status().is3xxRedirection())
.andExpect(authenticated())
;
}
From this test, we can deduce that
LDAP uses form login, username/password
The form has CSRF protection
So let's configure your Spring Boot application using Java config
The classic sample file, SecurityConfig.java
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
The security configuration doesn't change.
We want users to be fully authenticated
We want to use form login
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest()
.fullyAuthenticated()
.and()
.formLogin()
;
}
That's it, next we configure LDAP, again in your SecurityConfig.java we do this by calling the AuthenticationManagerBuilder. This is a bean that Spring Security configures. So we can access it using #Autowired
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.contextSource()
.url("ldap://localhost:389")
.managerDn("cn=admin,dc=test,dc=com")
.managerPassword("password")
.and()
.userSearchBase("ou=Users,dc=test,dc=com")
.userSearchFilter("cn={0}")
.groupSearchBase("dc=test,dc=com")
.groupSearchFilter("member={0}")
;
}
and that's it. Now, I used some code I wrote from the Cloud Foundry UAA project to create an in memory LDAP server for my integration tests.
So when the mock MVC integration test starts up, it starts an LDAP server to run against.
It really is that simple. You can now expand this sample to map LDAP groups to Spring Security authorities.
The LDAP Sample is available in my community repo: https://github.com/fhanik/spring-security-community.git
I am configuring the security in my Spring project and unfortunately I encountered a problem. Why is the autowired userDAO in CustomUserDetailsService.java null?
Here is the code in question:
CustomUserDetailsService.java
#Service("customUserDetailsService")
#ComponentScan(basePackages="...")
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UserDAO userDAO;
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// userDAO is null
UserModel user = userDAO.getUserByUsername(username);
...
}
}
spring-security.xml
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.2.xsd">
<http pattern="/resources/**" security="none"/>
<http pattern="/login" security="none"/>
<http auto-config="true">
<intercept-url pattern="/**" access="ROLE_USER" />
<form-login
login-page="/login"
default-target-url="/home"
authentication-failure-url="/login?error"
username-parameter="username"
password-parameter="password" />
<logout logout-success-url="/login?logout" />
</http>
<authentication-manager>
<authentication-provider user-service-ref="customUserDetailsService">
<password-encoder ref="encoder" />
</authentication-provider>
</authentication-manager>
<beans:bean id="encoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder">
<beans:constructor-arg name="strength" value="10" />
</beans:bean>
<beans:bean id="customUserDetailsService" class="... .service.CustomUserDetailsService" />
Edit:
UserDAOImpl.java
public class UserDAOImpl implements UserDAO {
private JdbcTemplate jdbcTemplate;
public UserDAOImpl(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
public UserModel getUserByUsername(String username) {
String sql = "SELECT * FROM users WHERE username = '" + username + "'";
return jdbcTemplate.query(sql, new ResultSetExtractor<UserModel>() {
public UserModel extractData(ResultSet rs) throws SQLException, DataAccessException {
if (rs.next()) {
UserModel user = new UserModel();
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("passwort"));
user.setEnabled(rs.getBoolean("enabled"));
return user;
}
return null;
}
});
}
}
Here is the code snippet in the Java-based configuration file:
#Bean
public UserDAO getUserDAO() {
return new UserDAOImpl(getDataSource());
}
Interesting though, this configuration seems to be sort of working. When I autowire the UserDAO into my controller, it works perfectly fine:
MainController.java
#RequestMapping(value = {"", "/", "/home"})
public String homeHandler(Model model) {
// userDAO is not null, works perfectly fine and returns the UserModel-Object as expected
UserModel user = userDAO.getUserByUsername("dx");
...
}
I already read some Q&A, yet so far it concerned manual instatiation of the custom UserDetailsService. What is wrong with my code?
The issue seems to be because you are instantiating the customUserDetailsService in two different places. You have <bean> definition for it in your spring-security.xml file as well as you have #Service annotation on the class itself. In your XML bean definition, you are not injecting the UserDAO so that it is null.
You need to streamline your project to clean the bean definition in either java config or XML files (this is not required and you can have XML + java config but then it makes it very confusing as to what is instantiating which bean). You have few options;
Remove the XML declaration of CustomUserDetailsService. Add <context:component-scan> in your XML and add the package names and also remove the #ComponentScan from CustomUserDetailsService. This will allow spring to scan the packages and register the beans marked with #Service, #Bean, etc annotations. Make sure your java config class is marked with #Configuration annotation.
You can Decide to use XML config for all the beans in which case you need to remove the #Bean, #Service, etc annotations and declare all of them in the spring XML bean definition and make sure each of them has proper dependencies injected.
You definitely need to clean up your bean definitions so correct dependencies are injected.
Thank you for your responses. Setu's answer was the most helpful one. Obviously, the problem was indeed the double instantiation. I added <context:component-scan base-package="... .service" /> and left both annotations #Service("customUserDetailsService") and #ComponentScan(basePackages="...") in CustomUserDetailsService (without #ComponentScan it didn't work).
I've created a spring-mvc application. the configurations look like below:
dispatcher-servlet.xml
<beans ... >
<mvc:annotation-driven />
<context:annotation-config />
<aop:aspectj-autoproxy/>
<bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping"/>
<context:component-scan base-package="my.server.controller" />
<context:component-scan base-package="my.server.dao" />
<context:component-scan base-package="my.server.service" />
......
</beans>
applicationContext.xml
<beans ...>
...
<bean id="myUserDetailsService" class="my.server.service.MyUserDetailsService" autowire="byType"/>
</beans>
security.xml
<beans:beans ...>
<http pattern="/css/**" security="none"/>
<http pattern="/login.jsp*" security="none"/>
<http auto-config='true'>
<http-basic />
<logout />
<intercept-url pattern="/login.jsp*" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<intercept-url pattern="/**" access="ROLE_USER" />
</http>
<authentication-manager>
<authentication-provider user-service-ref="myUserDetailsService" >
<password-encoder hash="bcrypt" />
</authentication-provider>
</authentication-manager>
</beans:beans>
security.xml and applicationContext.xml are loaded by the following lines in the web.xml:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml, /WEB-INF/security.xml</param-value>
</context-param>
my.server.dao.UserDao.java
#Component
public interface UserDao {
JUser findByUserName(String username);
}
my.server.dao.UserDaoMemory.java
#Component("userDao")
public class UserDaoMemory implements UserDao {
private static List<JUser> myUsers = new ArrayList<>();
static {
myUsers.add(new JUser("ali", "123","ROLE_USER"));
}
#SuppressWarnings("unchecked")
#Override
public JUser findByUserName(String username) {
List<JUser> users = new ArrayList<>();
users = myUsers;
if (users.size() > 0) {
return users.get(0);
} else {
return null;
}
}
}
my.server.service.MyUserDetailsService.java
#Service
public class MyUserDetailsService implements UserDetailsService {
#Autowired
private UserDao userDao;
#Override
public UserDetails loadUserByUsername(final String throws UsernameNotFoundException {
System.out.println("MyUserDetailsService#loadUserByUsername, userDao:"+userDao);
JUser user = userDao.findByUserName(username);
List<GrantedAuthority> authorities = buildUserAuthority(user.getUserRoles());
return buildUserForAuthentication(user, authorities);
}
// Converts my.server.entity.JUser user to
// org.springframework.security.core.userdetails.User
private User buildUserForAuthentication(JUser user,
List<GrantedAuthority> authorities) {
return new User(user.getUsername(),
user.getPassword(), user.isEnabled(),
true, true, true, authorities);
}
private List<GrantedAuthority> buildUserAuthority(Set<JRole> userRoles) {
Set<GrantedAuthority> setAuths = new HashSet<GrantedAuthority>();
// Build user's authorities
for (JRole userRole : userRoles) {
setAuths.add(new SimpleGrantedAuthority(userRole.getRole()));
}
List<GrantedAuthority> Result = new ArrayList<GrantedAuthority>(setAuths);
return Result;
}
public UserDao getUserDao() {
return userDao;
}
}
My problem is in MyUserDetailsServie where the loadUserByUsername(..) be called; The userDao has not been autowired and is null:
MyUserDetailsService#loadUserByUsername, userDao:null
One probable solution is to change the applicationContext.xml such as(autowire="byName"):
<bean id="myUserDetailsService" class="my.server.service.MyUserDetailsService" autowire="byName"/>
But it is not working!!!!
You have some conflicts in how your context are set up.
The applicationContext.xml (the parent context) would generally be where you define all your shared components (e.g. repositories, security, services, handlers, ...), which you have done correctly.
Your servlet context (e.g. dispatcher-servlet.xml) is a child context, and would have visibility to everything defined in the parent context. So you should limit the scope of the components to just controllers for the servlet context, which means you should only scan for your controllers, but you're scanning everything.
<context:component-scan base-package="my.server.controller" />
<context:component-scan base-package="my.server.dao" />
<context:component-scan base-package="my.server.service" />
it should just be
<context:component-scan base-package="my.server.controller" />
you can move all of these to your applicationContext as well
<mvc:annotation-driven />
<context:annotation-config />
<aop:aspectj-autoproxy/>
unless you're using them specifically for this servlet context.
Anyways, getting back to the problem, since child context is scanning for the service/dao classes, it will find those, but they're not properly configured.
so where do I scan my.server.dao
Put them into the applicationContext.xml for now, and into something like a repositoryContext in the future as your application complexity grows.
Technically you can dump everything into a servlet context if you wanted to, but any time you define a "root" context in addition to a servlet context, you'll create scoping issue. Everything in a child context (e.g. servlet context) can see components in a parent context (e.g anything loaded by your web.xml contextloader), but not the reverse.
My component is:
package com.netpc.recruitment.models.user;
#Component
public class UserAuth {
#Autowired
private HttpSession httpSession;
#Autowired
private IUserDAO userDAO;
}
Vars httpSession and userDAO, while creating object in controllers, are null. userDAO is configured properly and it works fine in my com.netpc.recruitment.controllers.IndexController class #Controller.
My web.xml
<beans ......>
<context:component-scan base-package="com.netpc.recruitment.controllers" />
<bean id="userDAO" class="com.netpc.recruitment.models.user.JDBCUserDAO">
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
Whats wrong with it? Why it's null?
Your component scan is scanning only on "com.netpc.recruitment.controllers". Should also scan in package com.netpc.recruitment.models.user. Changes in web.xml:
<context:component-scan base-package="com.netpc.recruitment.controllers" />
to this:
<context:component-scan base-package="com.netpc.recruitment.controllers, com.netpc.recruitment.models.user" />
Hope this works for you!
My setup :
In a Spring MVC 3.1 application, I have a Spring Security protected service :
#PreAuthorize("isAnonymous()") // ("hasRole('USER')") doesn't work either
public interface UserService extends UserDetailsService, PasswordEncoder
I'm trying to use this service from the init() method of a bean declared in my context :
<bean class="com.xxx.scripts.FillDbWithInitialValues" init-method="contextInit" />
The class :
public class FillDbWithInitialValues
{
#Autowired
UserService userService;
public void contextInit()
{
User test = userService.getUser(1);
}
}
Extract of my security.xml file :
<sec:global-method-security pre-post-annotations="enabled" />
<sec:http auto-config="true" use-expressions="true">
(...)
</sec:http>
<sec:authentication-manager alias="authManager">
<sec:authentication-provider user-service-ref="userService">
<sec:password-encoder ref="userService" />
</sec:authentication-provider>
</sec:authentication-manager>
The problem :
When I start the application, I get an exception :
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
Why is this happening?
How can my bean be "authenticated" so it can use the service?
If there's no user authenticated there's no way of checking his roles. If you want that bean to be authenticated before calling the method i think you could try something in this way:
SecurityContextHolder.getContext().setAuthentication(new user here);
User test = userService.getUser(1);