I am building a web application using spring MVC which is connected to the database. This is part of my bean config file.
<!-- Define Database DataSource / connection pool -->
<bean id="myDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/java_task?useSSL=false" />
<property name="user" value="me" />
<property name="password" value="me" />
<!-- these are connection pool properties for C3P0 -->
<property name="minPoolSize" value="5" />
<property name="maxPoolSize" value="20" />
<property name="maxIdleTime" value="30000" />
</bean>
<!-- Define Hibernate session factory -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="myDataSource" />
<property name="packagesToScan" value="com.javatask.entity" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>
As you can see my data source is defined in a hard way. In code I am using #Autowired annotation to inject sessionFactory. But I would like to inject sessionFactory (jdbc, username, password etc.) with data which I will obtain from user during run time. For example he will write me those data to the textfeild and then I will create sessionFactory (based on his data) which will be connected to his database.
I was looking for an answer but unluckily I have not found anything that would fit to this problem.
There are multiple ways that spring bean definition can be changed in run time. One way is to refresh the Spring Application Context in run time which would have the bean definition specific to the functionality rather than all bean definition.
((ConfigurableApplicationContext)applicationContext).refresh();
Let me explain using one simple use case:
Create a PropertyBean which will load value from external properties file.
Create PropertiesApplicationContext which would have only PropertyBean definition config rather than all beans belongs the application.
Use ConfigurableApplicationContext refresh() method to reload ProperiesBean definition in run time.
More details about below example code snippet:
Web Application should display a Property Value from a bean which
will read the value from a external property file.
Property value will change anytime in external properties file.
Web Application should not restarted.
Here, bean definition should be changed in run time.
Spring Boot Application Code:
#SpringBootApplication(scanBasePackages = {"vijay.controller","vijay.configuration"})
public class SpringContextRefreshApplication extends SpringBootServletInitializer{
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(SpringContextRefreshApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(SpringContextRefreshApplication.class, args);
}
}
MVC Configuration:
package vijay.configuration;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;
/**
* MVC Configuration
* #author Vijay
*/
#Configuration
public class MvcConfiguration extends WebMvcConfigurerAdapter{
#Override
public void configureViewResolvers(ViewResolverRegistry registry) {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/view/");
resolver.setSuffix(".jsp");
resolver.setViewClass(JstlView.class);
registry.viewResolver(resolver);
}
}
Custom (Property) Application Context Aware:
package vijay.configuration;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* PropertyApplicationContext Class to load Property Configuration XML.
* #author Vijay
*/
public class PropertyApplicationContext implements ApplicationContextAware{
private ApplicationContext applicationContext;
private static PropertyApplicationContext propertyApplicationContext;
private PropertyApplicationContext(){
applicationContext = new ClassPathXmlApplicationContext("property-config.xml");
}
#Override
public void setApplicationContext(ApplicationContext ac) throws BeansException {
if(applicationContext == null){
this.applicationContext = ac;
}
}
public ApplicationContext getApplicationContext(){
return applicationContext;
}
public static PropertyApplicationContext getInstance(){
if(propertyApplicationContext == null){
propertyApplicationContext = new PropertyApplicationContext();
}
return propertyApplicationContext;
}
}
MVC Controller:
package vijay.controller;
import vijay.beans.PropertyDto;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import vijay.configuration.PropertyApplicationContext;
/**
* Property List Controller.
* #author Vijay
*/
#Controller
public class PropertyController {
#RequestMapping("/")
public String testController(){
System.out.println("vijay.controller.PropertyController.testController()");
return "sample";
}
#RequestMapping(path = "/getProperties")
public String testPropertiesController(ModelMap modelMap){
ApplicationContext applicationContext = PropertyApplicationContext.getInstance().getApplicationContext();
PropertyDto propertyDto = (PropertyDto) applicationContext.getBean("propertyBean");
System.out.println("vijay.controller.PropertyController.testPropertiesController() " + propertyDto);
modelMap.addAttribute("message", propertyDto.getKey());
return "sample";
}
/**
* Method will refresh ApplicationContext
* #param modelMap as ModelMap
* #return viewName as String
*/
#RequestMapping(path = "/refreshProperties")
public String refreshBean(ModelMap modelMap){
ApplicationContext applicationContext = PropertyApplicationContext.getInstance().getApplicationContext();
// Refresh Application Context
((ConfigurableApplicationContext)applicationContext).refresh();
PropertyDto propertyDto = (PropertyDto) applicationContext.getBean("propertyBean");
System.out.println("vijay.controller.PropertyController.refreshBean()" + propertyDto);
modelMap.addAttribute("message", propertyDto.getKey());
return "sample";
}
}
Spring Bean (Property-config.xml) definition File:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd" >
<bean id="placeholderConfig" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="file:C://sample.properties" />
</bean>
<context:annotation-config/>
<context:component-scan base-package="vijay.beans"/>
</beans>
View JSP (sample.jsp):
<!DOCTYPE html>
<%# taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<html lang="en">
<body>
<div>
<div>
<h1>Spring Boot JSP Example</h1>
<h2>Property Value :: ${message} !</h2>
</div>
</div>
</body>
</html>
Here, MVC Controller has /refreshProperties which will refresh the PropertiesBean defined in PropertiesApplicationContext. As properties-config.xml the only bean definition defined in PropertiesApplicationContext, propertiesBean alone will be refreshed and no other bean definition will be refreshed.
Similar way, you can create Application Context by extending ApplicationContextAware which will have bean definition of sessionFactory. You can get SessionFactory wherever required from this context and refresh the context wherever required.
This is one way to achieve the bean definition change in run time.
Did you try this [Change SessionFactory datasource jdbcurl late in runtime] (Change SessionFactory datasource jdbcurl late in runtime)?
Problem description: I have trouble setting up a mock for a particular spring bean to return the correct mock test resource location on my development box, rather than the runtime web application root. I am sure I am doing something silly with my refactoring. I hope someone sees it.
I am using Quartz to execute a job over Spring. The Quartz job is working fine and picking up the Spring applicationcontext ok. The only thing left is to wire up a configuration property for the test or runtime location where the web resources are located. :
JUnit 4, Spring 3.1, Quartz, Java 8, Mockito
The interface:
public interface ITitle {
/**
* Gets the root of the application.
* #return location String
*/
public String getRuntimeLocation();
}
The implementation:
#Component("Title")
public class Title implements ITitle, ApplicationContextAware {
private String location;
/**
* Gets the root of the application.
* #return location String
*/
public String getRuntimeLocation() {
String location = "";
config = getConfig();
log.debug("Class Title --- Method getRuntimeLocation -- config is " + config );
location = config.getRuntimeLocation();
log.debug("Class Title --- Method getRuntimeMethod -- runtime location is " + location );
return location;
}
}
The Unit Test
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.generators.analytics.serialised.ITitle;
import java.io.File;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.when;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {
"classpath*:/WEB-INF/conf/analytics-context.xml"
})
public class GeneratorTest {
private AnalyticsGenerator generator;
#Mock
private IConfig config;
#Mock
private ApplicationContext applicationContext;
#Mock
private ITitle title;
// Set a logger
private static Logger log = LoggerFactory.getLogger(GeneratorTest.class);
private JobExecutionContext job;
/**
* Initialises the test parameters.
*/
#Before
public void setUp() throws Exception {
//create a generator object
generator = new AnalyticsGenerator();
MockitoAnnotations.initMocks(this);
when(applicationContext.getBean("Query")).thenReturn(query);
when(applicationContext.getBean("Config")).thenReturn(config);
when(config.getRuntimeLocation()).thenReturn(“/Users/me/dev/workarea/");
generator.executeInternal(ctx);
}
/**
* Expected: Json exists
*/
#Test
public void testThatExecuteInternalCreatesAJsonFile() throws JobExecutionException {
generator.executeInternal(job);
File returnedJson = new File("classpath:/google-analytics.json");
Assert.assertNotNull("The JSON file does not exist", returnedJson );
}
/**
* Remove objects from memory.
*/
#After
public void tearDown() throws Exception {
generator = null;
}
}
The spring xml file
<?xml version="1.0" encoding="UTF-8"?>
<!--
* analytics-context.xml
*
* resource configuration file for Google Analytics recommendations integration framework.
*
* All custom beans and services must be defined in this file.
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:ehcache="http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-3.2.xsd
http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring
http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring/ehcache-spring-1.1.xsd">
<context:annotation-config/>
<!--
START Globally used Google Analytics Query parameters
-->
<bean id="Config" class=“com.generators.analytics.Config">
<property name="reportLocation" value="/data/runtime/web/assets/generated-list/google-analytics.json"/>
<property name="runtimeLocation" value="/data/runtime/web/"/>
</bean>
<bean id="configFactory" class="org.springframework.beans.factory.config.ServiceLocatorFactoryBean">
<property name="serviceLocatorInterface" value=“com.generators.analytics.ConfigFactory" />
</bean>
<alias name="Config" alias="C" />
<bean id="Title" class=“com.generators.analytics.serialised.Title">
<property name="config" ref="Config"/>
</bean>
<context:component-scan base-package=“com.generators.analytics" />
<!--
END Globally used Google Analytics Query parameters
-->
After running the test I get this in the log:
11:51:43.020 [main] DEBUG com.generators.analytics.serialised.Title - Class Title --- Method getConfig -- applicationContext is org.springframework.context.support.GenericApplicationContext#57f23557: startup date [Tue Jan 24 11:51:31 GMT 2017]; root of context hierarchy
11:51:43.020 [main] DEBUG com.generators.analytics.serialised.Title - Class Title --- Method getConfig -- config is com.generators.analytics.Config#13d9cbf5
11:51:43.020 [main] DEBUG com.generators.analytics.serialised.Title - Class Title --- Method getRuntimeLocation -- config is com.generators.analytics.Config#13d9cbf5
11:51:43.020 [main] DEBUG com.generators.analytics.serialised.Title - Class Title --- Method getRuntimeMethod -- runtime location is /data/runtime/web/
The question is, is there anything obvious that I do wrong to get the intended path /Users/me/dev/workarea/ ?
I guess I need to do refactoring to extract the location from the method ? I am not sure how to refactor this particular step.
when(config.getRuntimeLocation()).thenReturn(“/Users/me/dev/workarea/");
I resolved this problem by creating a copy of the spring configuration file and changing
<bean id="Config" class="com.generators.analytics.Config">
<property name="reportLocation" value="/Users/arnout/dev/soton-test/workarea/assets/generated-list/google-analytics.json"/>
<property name="runtimeLocation" value="/Users/arnout/dev/soton-test/workarea/"/>
</bean>
and then changing
#ContextConfiguration(locations = {
"classpath*:/WEB-INF/conf/livesite_customer/resources/test-analytics-resource-config.xml"
})
public class GeneratorTest {
I did some lateral thinking after I read up on Profiles in Spring configurations. In fact I did not need a profile, it was just as easy as taking another spring configuration in test mode.
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 7 years ago.
Improve this question
In a Spring MVC app using Spring Security, I want to use a custom AuthenticationProvider to check n-number of additional fields beyond the default username and password. For example, if a user wants to authenticate, I want her to have to supplement her username and password with a pin code she receives via email, a pincode she receives via text, and n number of other credentials. However, to keep this question narrow, let's just focus on adding one additional pin to the login, but let's set it up in a way that enables us to add n-other credentials easily afterwards.
I want to use Java configuration.
I have created a custom AuthenticationProvider, a custom AuthenticationFilter, custom UserDetailsService, and a few other changes.
But the app is granting access when a user tries to log in whether or not the user has valid credentials, as shown in a screen shot in the instructions for reproducing the problem below. What specific changes need to be made to the code that I am sharing so that the custom n-factor authentication can function properly?
The structure of my test project is shown in the following screen shots:
Here is the Java code structure in eclipse project explorer:
{ Image host not available }
The XML config files can be located by scrolling down in project explorer to show the following:
{ Image host not available }
The view code can be found by scrolling a little further down in project explorer as follows:
{ Image host not available }
You can download and explore all this code in a working Eclipse project:
{ File now deleted }
CustomAuthenticationProvider.java is:
package my.app.config;
import java.util.ArrayList;
import java.util.List;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
public class CustomAuthenticationProvider implements AuthenticationProvider{
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String name = authentication.getName();
String password = authentication.getCredentials().toString();
List<GrantedAuthority> grantedAuths = new ArrayList<>();
if (name.equals("admin") && password.equals("system")) {
grantedAuths.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
}
if(pincodeEntered(name)){
grantedAuths.add(new SimpleGrantedAuthority("registered"));
}
Authentication auth = new UsernamePasswordAuthenticationToken(name, password, grantedAuths);
return auth;
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
private boolean pincodeEntered(String userName){
// do your check here
return true;
}
}
MessageSecurityWebApplicationInitializer.java is:
package my.app.config;
import org.springframework.core.annotation.Order;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
#Order(2)
public class MessageSecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
}
TwoFactorAuthenticationFilter.java is:
package my.app.config;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
public class TwoFactorAuthenticationFilter extends UsernamePasswordAuthenticationFilter
{
private String extraParameter = "extra";
private String delimiter = ":";
/**
* Given an {#link HttpServletRequest}, this method extracts the username and the extra input
* values and returns a combined username string of those values separated by the delimiter
* string.
*
* #param request The {#link HttpServletRequest} containing the HTTP request variables from
* which the username client domain values can be extracted
*/
#Override
protected String obtainUsername(HttpServletRequest request){
String username = request.getParameter(getUsernameParameter());
String extraInput = request.getParameter(getExtraParameter());
String combinedUsername = username + getDelimiter() + extraInput;
System.out.println("Combined username = " + combinedUsername);
return combinedUsername;
}
/**
* #return The parameter name which will be used to obtain the extra input from the login request
*/
public String getExtraParameter(){
return this.extraParameter;
}
/**
* #param extraParameter The parameter name which will be used to obtain the extra input from the login request
*/
public void setExtraParameter(String extraParameter){
this.extraParameter = extraParameter;
}
/**
* #return The delimiter string used to separate the username and extra input values in the
* string returned by <code>obtainUsername()</code>
*/
public String getDelimiter(){
return this.delimiter;
}
/**
* #param delimiter The delimiter string used to separate the username and extra input values in the
* string returned by <code>obtainUsername()</code>
*/
public void setDelimiter(String delimiter){
this.delimiter = delimiter;
}
}
SecurityConfig.java is:
package my.app.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
#Configuration
#EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void registerGlobalAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider());
}
#Bean
AuthenticationProvider customAuthenticationProvider() {
CustomAuthenticationProvider impl = new CustomAuthenticationProvider();
return impl ;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/secure-home")
.usernameParameter("j_username")
.passwordParameter("j_password")
.loginProcessingUrl("/j_spring_security_check")
.failureUrl("/login")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.and()
.authorizeRequests()
.antMatchers("/secure-home").hasAuthority("registered")
.antMatchers("/j_spring_security_check").permitAll()
.and()
.userDetailsService(userDetailsService());
}
}
User.java is:
package my.app.model;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
#Entity
#Table(name="users")
public class User implements UserDetails{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
private Integer id;
#Column(name= "email", unique=true, nullable=false)
private String login;//must be a valid email address
#Column(name = "password")
private String password;
#Column(name = "phone")
private String phone;
#Column(name = "pin")
private String pin;
#Column(name = "sessionid")
private String sessionId;
#ManyToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinTable(name="user_roles",
joinColumns = {#JoinColumn(name="user_id", referencedColumnName="id")},
inverseJoinColumns = {#JoinColumn(name="role_id", referencedColumnName="id")}
)
private Set<Role> roles;
public Integer getId() {return id;}
public void setId(Integer id) { this.id = id;}
public String getPhone(){return phone;}
public void setPhone(String pn){phone = pn;}
public String getPin(){return pin;}
public void setPin(String pi){pin = pi;}
public String getSessionId(){return sessionId;}
public void setSessionId(String sd){sessionId = sd;}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
//roles methods
public void addRole(Role alg) {roles.add(alg);}
public Set<Role> getRoles(){
if(this.roles==null){this.roles = new HashSet<Role>();}
return this.roles;
}
public void setRoles(Set<Role> alg){this.roles = alg;}
public boolean isInRoles(int aid){
ArrayList<Role> mylgs = new ArrayList<Role>();
mylgs.addAll(this.roles);
for(int a=0;a<mylgs.size();a++){if(mylgs.get(a).getId()==aid){return true;}}
return false;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// TODO Auto-generated method stub
return null;
}
#Override
public String getUsername() {
// TODO Auto-generated method stub
return null;
}
#Override
public boolean isAccountNonExpired() {
// TODO Auto-generated method stub
return false;
}
#Override
public boolean isAccountNonLocked() {
// TODO Auto-generated method stub
return false;
}
#Override
public boolean isCredentialsNonExpired() {
// TODO Auto-generated method stub
return false;
}
#Override
public boolean isEnabled() {
// TODO Auto-generated method stub
return false;
}
}
The xml config is in business-config.xml and is:
<beans profile="default,spring-data-jpa">
<!-- lots of other stuff -->
<bean class="my.app.config.SecurityConfig"></bean>
</beans>
<!-- lots of unrelated stuff -->
In addition, mvc-core-config.xml contains the following:
<!-- lots of other stuff -->
<mvc:view-controller path="/" view-name="welcome" />
<mvc:view-controller path="/login" view-name="login" />
And login.jsp looks like this:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<%#taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%# page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
<title>Custom Login page</title>
<style>.error {color: red;}</style>
</head>
<body>
<div class="container">
<h1>Custom Login page</h1>
<p>
<c:if test="${error == true}">
<b class="error">Invalid login or password or pin.</b>
</c:if>
</p>
<form method="post" action="<c:url value='j_spring_security_check'/>" >
<table>
<tbody>
<tr>
<td>Login:</td>
<td><input type="text" name="j_username" id="j_username"size="30" maxlength="40" /></td>
</tr>
<tr>
<td>Password:</td>
<td><input type="password" name="j_password" id="j_password" size="30" maxlength="32" /></td>
</tr>
<tr>
<td>Pin:</td>
<td><input type="text" name="pin" id="pin"size="30" maxlength="40" /></td>
</tr>
<tr>
<td colspan=2>
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
</td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="Login" /></td>
</tr>
</tbody>
</table>
</form>
</div>
</body>
</html>
The spring security dependencies in 'pom.xml' are:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>3.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>3.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>3.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>3.2.2.RELEASE</version>
</dependency>
Download and reproduce on your machine
I have also uploaded a working Eclipse project which contains the bare minimum code required to reproduce the problem on your local devbox. You can download the Eclipse project here:
{ File now deleted }
Once you have downloaded the zipped project, you can reproduce the problem on your machine by following these steps:
1.) Unzip the zip file to a new folder
2.) In Eclipse, do File > Import > Existing Maven Projects
3.) Click Next. Browse to folder of unzipped project. Complete wizard to import project.
4.) Right click on project name in eclipse and do Maven > Download sources
5.) Right click on project name again in eclipse and do Maven > Update project
6.) Open MySQL and create an empty new database called somedb
7.) In the Eclipse project, open data-access.properties as shown in the following picture, and change someusername and somepassword to your real username and password for your MySQL.
{ Image host not available }
8.) In Eclipse, right click the project and chose Run As .. Run on server.. . This should launch the app so that you see the following in your browser at the http://localhost:8080/n_factor_auth/ url:
{ Image host not available }
9.) Change the URL to http://localhost:8080/n_factor_auth/secure-home to see that you were redirected to http://localhost:8080/n_factor_auth/login which serves the sample custom login page, which requires a pin in addition to the username and password. Note that the result needs to accommodate n-factors and not simply adding a single pin code:
{ Image host not available }
10.) Insert test credentials into the MySQL database by running the following SQL commands, which you could put in a .sql file and run from the MySQL command line using the source command. Note that the database objects will be deleted and recreated empty every time the app starts because hbm2ddl is enabled to simplify this example. Thus, the following SQL commands will need to be re-run every time you reload the app in Eclipse.
SET FOREIGN_KEY_CHECKS=0;
INSERT INTO `roles` VALUES (100,'registered');
INSERT INTO `user_roles` VALUES (100,100);
INSERT INTO `users` (id, email,password, phone, pin) VALUES (100,'me#mydomain.com','somepassword','xxxxxxxxxx', 'yyyy');
SET FOREIGN_KEY_CHECKS=1;
11.) Try to login using any credentials (valid or invalid), and get the following successful login screen (Note that the user gets logged in whether or not they give valid credentials):
{ Image host not available }
That's it. You now have the problem recreated on your machine, including all the code shown above, but in a working minimalist eclipse project. So now how do you answer the OP above? What changes do you make to the code above, and what else do you do in order to get the custom authenticator to engage upon login?
I am interested to learn what specific changes need to be made to the minimalist download app in order to enable n-factor authentication. I will validate by checking your suggestions in the sample app on my machine.
Thanks to various people (including M.Deinum) who have suggested deleting redundant XML config to create the current version shown in this posting.
First, some explanation about the interfaces you are working with and the role they play in the authentication process:
Authentication - represents the result of authenticating a user. Holds the authorities granted to that user and any additional details that may be needed about the user. As there is no way for the framework to know, what details are going to be needed, the authentication object has a getDetails method that can return any object
AuthenticationProvider - object that can create an Authentication object in some way. To make them more reusable, some (or most) of AuthenticationProviders refrain from setting the user details on the Authentication object, as each application may need specific user details. Instead they delegate the process of resolving the user details to a settable UserDetailsService
UserDetailsService - a strategy for retrieving the user details required in your application.
So, if you are creating a custom AuthenticationProvider you may not even need to implement it in a way that requires a UserDetailsService. The decission is up to you and depends, on whether you plan on reusing your implementation in other projects.
As for the compilation problems in your code, you are mixing two ways of providing the UserDetailsService. In the CustomAuthenticationProvider you have annotated the userService field with the #Inject annotation .This means, that the container (Spring application context in your case) is to find a suitable implementation and inject it into that field at runtime using reflection. The process of setting this field by the context is called dependency injection. In the SecurityConfig class you are trying to provide the implementation yourself by setting the field through the setUserDetailsService method that does not exist in your class.
To resolve this problem you need to decide to use one of the ways to provide the UserDetails service and either:
remove the #Inject annotation and create the setUserDetailsService method, or
remove the line when you are calling the non-existant method and declare your implementation of the UserDetailsService as a bean
As for which of the ways should you choose, the dependecy injection way may by better if you can find a way of making your SecurityConfig class reusable in other projects. In that case you could just import it (by using the #Import annotaion) and declare a different UserDetailsSerice implementation as a bean in your next application and have it working.
Usually, classes like the SecurityConfig are not really reusable, so creating the setter and removing the dependency injection would probably be my first choice.
EDIT
A working, albeit a simplistic implementation (based heavily on this blog entry) would be:
public class CustomAuthenticationProvider implements AuthenticationProvider{
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String name = authentication.getName();
String password = authentication.getCredentials().toString();
List<GrantedAuthority> grantedAuths = new ArrayList<>();
if (name.equals("admin") && password.equals("system")) {
grantedAuths.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
}
if(pincodeEntered(name)){
grantedAuths.add(new SimpleGrantedAuthority("ROLE_PINCODE_USER"));
}
Authentication auth = new UsernamePasswordAuthenticationToken(name, password, grantedAuths);
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
private boolean pincodeEntered(String userName){
// do your check here
return true;
}
}
Then in your config class change the following method:
#Bean
AuthenticationProvider customAuthenticationProvider() {
return new CustomAuthenticationProvider();
}
The first thing we need to do is extend the UsernamePasswordAuthenticationFilter class so that it can handle a second input field.
public class TwoFactorAuthenticationFilter extends UsernamePasswordAuthenticationFilter
{
private String extraParameter = "extra";
private String delimiter = ":";
//getters and setters
#Override
protected String obtainUsername(HttpServletRequest request)
{
String username = request.getParameter(getUsernameParameter());
String extraInput = request.getParameter(getExtraParameter());
String combinedUsername = username + getDelimiter() + extraInput;
return combinedUsername;
}
}
obtainUsername() This method is to retrieve the username and “extra” input field from the HttpServletRequest object that’s passed in.
It then concatenates these two values into one string, separating them by the delimiter string (a colon, by default).
It then returns this combined string. The parameter from which the “extra” input field is read is extra by default.
UserDetailsService should look like this:
#Override
public UserDetails loadUserByUsername(String input) throws UsernameNotFoundException, DataAccessException
{
String[] split = input.split(":");
if(split.length < 2)
{
throw new UsernameNotFoundException("Must specify both username and corporate domain");
}
String username = split[0];
String domain = split[1];
User user = userDao.findByUsernameAndDomain(username, domain);
if(user == null)
{
throw new UsernameNotFoundException("Invalid username or corporate domain");
}
return user;
}
Split the given username into its two components: the username and the extra field. In this example, the extra field is the user’s corporate domain.
Once we have the username and the domain, we can use our DAO to find the matching user.
Last Puzzle:
TwoFactorAuthenticationFilter:
<http use-expressions="true" auto-config="false" entry-point-ref="loginUrlAuthenticationEntryPoint">
<intercept-url pattern="/secured" access="isAuthenticated()" />
<intercept-url pattern="/**" access="permitAll" />
<custom-filter position="FORM_LOGIN_FILTER" ref="twoFactorAuthenticationFilter" />
<logout logout-url="/logout" />
</http>
<authentication-manager alias="authenticationManager">
<authentication-provider ref="authenticationProvider" />
</authentication-manager>
<beans:bean id="authenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<beans:property name="passwordEncoder">
<beans:bean class="org.springframework.security.authentication.encoding.ShaPasswordEncoder" />
</beans:property>
<beans:property name="userDetailsService" ref="userService" />
</beans:bean>
<beans:bean id="userService" class="com.awnry.springexample.UserDetailsServiceImpl" />
<beans:bean id="loginUrlAuthenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<beans:property name="loginFormUrl" value="/login" />
</beans:bean>
<beans:bean id="twoFactorAuthenticationFilter" class="com.awnry.springexample.TwoFactorAuthenticationFilter">
<beans:property name="authenticationManager" ref="authenticationManager" />
<beans:property name="authenticationFailureHandler" ref="failureHandler" />
<beans:property name="authenticationSuccessHandler" ref="successHandler" />
<beans:property name="filterProcessesUrl" value="/processLogin" />
<beans:property name="postOnly" value="true" />
<beans:property name="extraParameter" value="domain" />
</beans:bean>
<beans:bean id="successHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
<beans:property name="defaultTargetUrl" value="/login" />
</beans:bean>
<beans:bean id="failureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<beans:property name="defaultFailureUrl" value="/login?login_error=true" />
</beans:bean>
In twoFactorAuthenticationFilter bean definition, we set the extraParameter property to “domain” which is the name of the input field to use in our login form.
EDIT:
Have a look into constructors of User class.
If you don't know what a granted authority get into a look over this below link:
http://docs.spring.io/autorepo/docs/spring-security/3.2.1.RELEASE/apidocs/org/springframework/security/core/GrantedAuthority.html
Your coding gives a different mode applicable only for normal username and password. My code works for n factor authentication. Try switch over to my code if any problem keep on persists.
I'm very concious that this post has undergone 28 edits, so I may have missed some context. I'm also concious that you've amalgamated some code from the other answers into your question and that the problem has been somewhat "turned on its head" from "why won't a valid user authenticate?" to "why does every user authenticate?".
Current problem.
However, as written, your CustomAuthenticationProvider.authenticate() method will always return an Authentication object that returns auth.isAuthenticated() == true because you instantiate using this method which warns you about that very thing. Even if the collection you passed in as the third argument were empty, this would be the case. In fact, the collection always contains a GrantedAuthority for "registered", because pincodeEntered(name) always returns true. So, you need to correct your logic in those methods. authenticate() should return null if authentication is not successful.
Next steps
You've indicated in comments that what you want is a reference implementation of multi factor authentication. This is problematic - there is not necessarily agreement on what would constitute such a thing. For instance some would argue that multi factor should include a possession factor, rather than n knowledge factors on a single login page. It's also not really suited to an SO answer as it would need a blog post (or a series) - however generous the bounty.
There are working examples of multi factor authentication in spring on the web, here and here, for example. The latter I think you must have discovered as you appear to be using some of the code from there.
Making your CustomAuthenticationProvider work might take hours. Debugging might take even longer, as you have a mixture of methods in your example - it is not minimal. In particular, the TwoFactorAuthenticationFilter class is supposed to be used to intercept input on a request from the login page and concatenate the username and pin. In the example from the blog, this is set up in XML - you could add the security namespace to your business-config.xml and add those beans there for instance.
However, the SecurityConfig class and CustomAuthenticationProvider is a different method again.
Next, your project code references a j_security_check url, but that URL is not handled by anything. I am not sure of the intent behind that, or where it comes from. Finally, the MVC config for URL routing adds another element to the mix - one which I'm not familiar with.
I've played with your example for a while. There are too many mixed methods and too much complexity for me to fix quickly - maybe others can.
I strongly suggest that you start from the example in the blog exactly, then add the mvc config you want to over the top of that.
N.B. Setup for others trying to get the example to work
There were a couple of wrinkles in setting the project - it had an un-needed and unsatisfied dependency on javax.mail, you need to publish the maven dependencies to the server (in project->properties->deployment assembly) and you need to download and install adapters for the tomcat server if you don't already have it.
You also need to create the tables and columns in your database.
The easiest way to use java config for n-factor authentication is to start with a working example of single-factor authentication (username and password) that uses java config. Then you only have to make a few very minor changes: Assuming that you have a working single factor authentication app using java configuration, the steps are simply:
First, define layered roles, with one role for each factor. If you only have two factor authentication, keep your existing one role in the database, but then create a second role with full-access that you only assign at runtime. Thus, when the user logs in, they are logged in to the minimal role stored in the database, and that minimal role is only given access to one view, which is a form allowing them to enter a pin code that your controller just sent them via text or email or some other method. These layered roles get defined in SecurityConfig.java, as follows:
#Configuration
#EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/getpin")
.usernameParameter("j_username")
.passwordParameter("j_password")
.loginProcessingUrl("/j_spring_security_check")
.failureUrl("/login")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.and()
.authorizeRequests()
.antMatchers("/getpin").hasAuthority("get_pin")
.antMatchers("/securemain/**").hasAuthority("full_access")
.antMatchers("/j_spring_security_check").permitAll()
.and()
.userDetailsService(userDetailsService);
}
}
Second, add code that upgrades the user's role to full-access upon successful entry of the correct pin code to the controller code that handles the pin code entry form POST. The code to manually assign full access in the controller is:
Role rl2 = new Role();rl2.setRole("full-access");//Don't save this one because we will manually assign it on login.
Set<Role> rls = new HashSet<Role>();
rls.add(rl2);
CustomUserDetailsService user = new CustomUserDetailsService(appService);
Authentication authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities(rls));
SecurityContextHolder.getContext().setAuthentication(authentication);
return "redirect:/securemain";
You can add as many layers as you want to after /getpin. You can also support multiple authorization roles and make it as complicated as you want to. But this answer gives the simplest way to get it running with java config.
We secure our REST services (for server to server communication, no user involved) with Spring Security OAuth2. However when one tries to access a protected resource in a browser, it will show:
<oauth>
<error_description>
An Authentication object was not found in the SecurityContext
</error_description>
<error>unauthorized</error>
</oauth>
We want this to be a custom page of our own choosing. Is there a way?
Setting the access-denied-page won't do. For one it requires the definition of a login page which we don't have as this is a pure server to server communication. For another this attribute is supposedly deprecated since Spring 3.0.. or something.
Anyway.. Debugged my way into the OAuth Error Handling. And found that the response seems to somehow get enriched with the information I see on the error page. Apparently no page rendering at all is done so it looks like there is no error page to replace..?!
At least we want to hide the fact that we use OAuth and just display a basic "Denied" text if we can't have a "real" page.. So maybe I'll have to extend the spring security handler or add a custom filter to modify the response?!
Maybe a redirect to our error page?
Thanks!
Edit
For our current setup check my other SO question here
I had to remove the oauth detail too and my solution was to implement my own OAuth2ExceptionRenderer
package org.some.nice.code;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.ResponseEntity;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.security.oauth2.provider.error.OAuth2ExceptionRenderer;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
public class HeaderOnlyOAuth2ExceptionRenderer implements OAuth2ExceptionRenderer
{
private final Log logger = LogFactory
.getLog(MyOAuth2ExceptionRenderer.class);
public void handleHttpEntityResponse(HttpEntity<?> responseEntity,
ServletWebRequest webRequest) throws Exception
{
if (responseEntity == null)
{
return;
}
HttpInputMessage inputMessage = createHttpInputMessage(webRequest);
HttpOutputMessage outputMessage = createHttpOutputMessage(webRequest);
logger.info("filtering headers only...");
if (responseEntity instanceof ResponseEntity
&& outputMessage instanceof ServerHttpResponse)
{
((ServerHttpResponse) outputMessage)
.setStatusCode(((ResponseEntity<?>) responseEntity)
.getStatusCode());
}
HttpHeaders entityHeaders = responseEntity.getHeaders();
if (!entityHeaders.isEmpty())
{
outputMessage.getHeaders().putAll(entityHeaders);
}
}
private HttpInputMessage createHttpInputMessage(NativeWebRequest webRequest)
throws Exception
{
HttpServletRequest servletRequest = webRequest
.getNativeRequest(HttpServletRequest.class);
return new ServletServerHttpRequest(servletRequest);
}
private HttpOutputMessage createHttpOutputMessage(
NativeWebRequest webRequest) throws Exception
{
HttpServletResponse servletResponse = (HttpServletResponse) webRequest
.getNativeResponse();
return new ServletServerHttpResponse(servletResponse);
}
}
Then you will have to reference it within your spring context
<bean id="oauthAuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
<property name="realmName" value="theRealm" />
<property name="exceptionRenderer" ref="headerOnlyExceptionRender" />
</bean>
<bean id="clientAuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
<property name="realmName" value="theRealm/client" />
<property name="typeName" value="Basic" />
<property name="exceptionRenderer" ref="headerOnlyExceptionRender" />
</bean>
<bean id="oauthAccessDeniedHandler" class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler">
<property name="exceptionRenderer" ref="headerOnlyExceptionRender" />
</bean>
<bean id="headerOnlyExceptionRender" class="org.some.nice.code.HeaderOnlyOAuth2ExceptionRenderer"/>
Hope it helps.
I am trying to hook up a messageSource in spring to use for my application. Its not working, gives this error:
org.springframework.context.NoSuchMessageException: No message found under code 'validation_required' for locale 'en'.
my applicationContext.xml contains this def for messageSource:
<bean id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basenames">
<list>
<value>classpath:messages</value>
</list>
</property>
</bean>
my messages properties file lives in:
/WEB-INF/classes/messages/messages_en_US.properties
Finally, the call i made that generates the error is:
String message = messageSource.getMessage("validation_required", null, Locale.ENGLISH);
Can anyone help me at this hour?
It seems like your path is not correct.
since you have your bundle under /WEB-INF/classes/messages/messages_en_US.properties, your basename setting should look like: classpath:messages/messages (basename in this case means path and properties file prefix).
The problem is with the way you have defined the resource bundle and the locale you are specifying (They doesn't match with the resource bundle's search order. Either rename your bundle to "messages_en.properties" or invoke the "getMessage(...)" with new Locale("en","US"). I prefer the first option.
I use following bean and it is working fine without specifying the path to the file:
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource" abstract="false"
scope="singleton" lazy-init="default">
<property name="basename" value="messages"/>
</bean>
Although the files I use are simply called "messages_en.properties" and "messages_es.properties" well you get the idea.
When you call
messageSource.getMessage("validation_required", null, null);
do you get an exception? Try to use this file name messages_en.properties or messages_us_en.properties
Try this
look at the comment for getting the string
package yours;
import java.util.Locale;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
/**
*
* Permet la recuperation des String du LocaleContextHolder hors des jsp
* Les langues sont placées dans main/ressources/messagesSources.properties
*
* Example : new MSG("recuperation_name_invalid").value()
*
*/
#Configurable
public class MSG
{
private String key;
#Resource(name = "messageSource")
private MessageSource messageSource;
public MSG(String key)
{
super();
this.key = key;
}
public String value()
{
Locale locale = LocaleContextHolder.getLocale();
return messageSource.getMessage(key, new Object[0], locale);
}
#Override
public String toString()
{
return value();
}
}