How to easily integrate Spring Security with existing Spring MVC application.
without using XML
About Spring Security
Spring security is the easiest way to enable security in your spring application. spring-security enables you to easily integrate with frameworks like Spring Web MVC (or Spring Boot), as well as with standards like OAuth2 or SAML it auto-generates login/logout pages and protects against common exploits like CSRF. spring-security is simply a bunch of servlet filters.
Spring Security Provides
- Authentication -- verifying the identity of the user with (username/password)
- Authorization -- verifies if a user has permission over a resource. (like admin pages)
Let's begin by creating a simple spring-MVC application and integrate it with spring-security
- first create a spring maven project with a dynamic web project facet.
- create a base package called demo in src/main/java. and internally config and controller packages.
Lets create a controller in src/main/java/demo/controller
Create class
BaseController
A spring controller is simply created using@Controller
annotation
package demo.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class BaseController {
@GetMapping("/")
public ModelAndView showHome() {
ModelAndView mav = new ModelAndView("dashboard");
return mav;
}
}
For adding spring security first we need SecurityWebApplicationInitializer which can be simply created by extending AbstractSecurityWebApplicationInitializer. that's it nothing else to do for this
package demo.config;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
}
now let's create a Security Config. this helps configure authorization and authentication on resources.
package demo.config;
import org.springframework.context.annotation.Configuration;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.User.UserBuilder;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
UserBuilder users = User.withDefaultPasswordEncoder();
auth.inMemoryAuthentication()
.withUser(users.username("skiran252").password("nothing").roles("USER"))
.withUser(users.username("saikiran").password("admin").roles("ADMIN", "USER"));
/* creates an in-memory database for debugging with given users */
/* creates users with role ADMIN and USER can also create users with role of anytype which you can restrict with our choice*/
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/register/**").permitAll().antMatchers("/admin/**").hasRole("ADMIN").antMatchers("/css/**").permitAll().anyRequest().authenticated().and().formLogin().loginProcessingUrl("/loginprocessing");
}
}
Description:
the configure method using HTTP allows to customize access to routes for specific authenticated users http.authorizeRequests()..anyRequest().authenticated();
restricts all the routes to be used by a user only after authentication
since we need specific routes before authentication like home and sign-in pages we allow them using .antMatchers("/register/**","/").permitAll()
and also we can restrict access to any specific /admin with access only to admin
.formLogin().loginProcessingUrl("/loginprocessing")
creates a default login page for you and for processing login loginprocessingurl is managed by spring security internally. just ensure you don't have a same route under controller
since we are not using XML let's create beans using java configuration.
package demo.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
@Configuration
@EnableWebMvc
@EnableTransactionManagement
@ComponentScan(basePackages = "demo")
@PropertySource("classpath:app.properties")
public class ApplicationConfig implements WebMvcConfigurer {
@Autowired
private Environment env;
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/view/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
} // helps resolve CSS and resource files where they need to be searched by the
// server
}
above config configures servlet dispatcher servlet which configures where to look our for view templates to be rendered by Controller
now most of the work is done. let's just add a final one to the dispatcher which dispatches servlets. we need to mention our config classes.
package demo.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class MvcDispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { ApplicationConfig.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
That's it now you have a default login page provided by spring security
Let's connect it to the database
to connect to the database we need a dataSource
. which can be created by defining a bean in Application Config. let's use Hibernate for now.
let's define some properties in src/main/resources/app.properties
#
#JDBC
#
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring
jdbc.user=root
jdbc.password=root
#
#connection pool
#
connection.pool.initialPoolSize=5
connection.pool.minPoolSize=5
connection.pool.maxPoolSize=20
connection.pool.maxIdleTime=3000
#
# Hibernate properties
#
hibernate.dialect=org.hibernate.dialect.MariaDBDialect
hibernate.show_sql=true
hibernate.packagesToScan=demo
hibernate.hbm2ddl.auto=update/create
Add these bean definitions to ApplicationConfig.java
package demo.config;
import java.sql.SQLException;
import java.util.Properties;
import java.util.logging.Logger;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSource;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.orm.hibernate5.HibernateTransactionManager;
import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
@Configuration
@EnableWebMvc
@EnableTransactionManagement
@ComponentScan(basePackages = "demo")
@PropertySource("classpath:app.properties")
public class ApplicationConfig implements WebMvcConfigurer {
@Autowired
private Environment env;
@Bean
public DataSource securityDataSource() throws SQLException {
BasicDataSource ds = new BasicDataSource();
ds.setUrl(env.getProperty("jdbc.url"));
ds.setDriverClassName(env.getProperty("jdbc.driver"));
ds.setUsername(env.getProperty("jdbc.user"));
ds.setPassword(env.getProperty("jdbc.password"));
ds.setInitialSize(this.getIntProperty("connection.pool.initialPoolSize"));
ds.setMinIdle(5);
ds.setMaxIdle(this.getIntProperty("connection.pool.maxIdleTime"));
ds.setMaxOpenPreparedStatements(100);
return ds;
}
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/view/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
private Properties getHibernateProperties() {
Properties props = new Properties();
props.setProperty("hibernate.dialect", env.getProperty("hibernate.dialect"));
props.setProperty("hibernate.show_sql", env.getProperty("hibernate.show_sql"));
props.setProperty("hibernate.hbm2ddl.auto",env.getProperty("hibernate.hbm2ddl.auto"));
return props;
}
@Bean
public LocalSessionFactoryBean sessionFactory() throws SQLException {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(securityDataSource());
//where should hibernate look for classes to create entities sessionFactory.setPackagesToScan(env.getProperty("hibernate.packagesToScan"));
sessionFactory.setHibernateProperties(getHibernateProperties());
return sessionFactory;
}
@Bean
@Autowired
public HibernateTransactionManager transactionManager(SessionFactory sessionFactory) {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(sessionFactory);
return txManager;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
//parse string values to int
@SuppressWarnings("unused")
private int getIntProperty(String propName) {
String propVal = env.getProperty(propName);
int intPropVal = Integer.parseInt(propVal);
return intPropVal;
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// TODO Auto-generated method stub
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
}
and change this in the SecurityConfig.java
@Autowired
private DataSource securityDatasource; //will automatically look for dataSource bean created and sets this object
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(securityDatasource);
}
now it's almost done. one final step we need to define tables in the database
database must of two tables users
and authorities
schema
authotities
defines roles a user has like "ROLE_USER" with prepended "ROLE__"
+-----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| username | varchar(255) | NO | PRI | NULL | |
| authority | varchar(255) | YES | | NULL | |
+-----------+--------------+------+-----+---------+-------+
users
table
+-----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| username | varchar(255) | NO | PRI | NULL | |
| enabled | int | NO | | NULL | |
| password | varchar(255) | YES | | NULL | |
+-----------+--------------+------+-----+---------+-------+
done. we can easily add a password encoder by defining a bean in Application Config and encode password when storing in database. spring security will automatically do the rest for you.