Showing posts with label Spring Security. Show all posts
Showing posts with label Spring Security. Show all posts

Friday, March 4, 2016

Java-based Spring mvc configuration - with Spring security

This article need you already understand how to config Spring mvc in java-based configuration without Spring security. If not, check this article first on "Java-based Spring mvc configuration - without Spring security"

This article is a hello world level spring mvc application with following features:

  • All configuration are java-based
  • Spring security enabled
  • User/password/rolename are stored in database. (MySQL, in this demo)
  • Tables used to store username/password/rolename are not spring security's default schema. ( spring security has a default table schema for user and authories storage, but not flexiable enough).

This article can also be used as a hello world level example on how to use spring security in Spring webmvc  application.

0. What you need

  • Maven 3.2+
  • Spring 4 +
  • Spring security 4.+

1.Brief about using spring security

To use Spring security in your project you need to define your own class, MyUserDetailsService in this demo,  which implements interface UserDetailsService(only-read user info) or interface UserDetailsManager(can create new user). These 2 interfaces are defined in Spring security. Spring security also provides a in-memory implements for above interfaces, but it's only for prototype or testing, not for real usage.

The user information type used in Spring security is a interface called UserDetails.  To make your code clean, you can implements this interface on your user entity definition, so the entity loaded from database can be used in spring security framework directly.

Spring security internally use servlet filter to fulfill all the functions.  You need to create a DaoAuthenticationProvider, and set your own MyUserDetailsService to it.

2. Cheet sheet

step1. Define entity class, implements spring security's interfaces (UserDetails and GrantedAuthority for user and authority respectively)

step2. Define class implements UserDetailsService or UserDetailsManager. In this demo it's MyUserDetailsServices.java

step3. Define a java config class extends WebSecurityConfigurerAdapter. In this demo it's SecurityConfig.java. There are 3 thnings defined in this class.

  • A bean of type DaoAuthenticationProvider, will use UserDetailsService implementation defined in step 2.
  • A config method which will use the just defined DaoAuthenticationProvider bean. ( A config method is a method annotated with @Autowired, method name is not important, arguments for the method will autowired from the spring context)
  • Override  configure(HttpSecurity http) method , set all access rules based on url patterns.

step4. Define a java class extends AbstractSecurityWebApplicationInitializer. In this demo it's SecurityWebApplicationInitializer.java.

step5. Finally make sure SecurityCinfig  from step 3 is loaded. In this demo just add SecurityConfig to getRootConfigClasses() for class extends AbstractAnnotationConfigDispatcherServletInitializer.

The purple types are from Spring.

Still not clear on how to do? Don't worry, check the demo code below first, then come back to see whether you'v got it.

3. Demo

3.1. Define Entity class

There are 2 entity classes. One for username+password, the other one for user's rolename. One user can have more than one roles. 

package com.shengwang.demo.model;

import org.springframework.security.core.userdetails.UserDetails;
// omit other import


@Entity
@Table(name="t_user")
public class UserEntity implements UserDetails{

  @Id
  @GeneratedValue(strategy=GenerationType.IDENTITY)
  private Long userId;
  private String username;
  private String password;
  private Boolean enabled;

  // Make sense to Eager fetch user's authorities
  @OneToMany(mappedBy="user",fetch=FetchType.EAGER)
  private List<AuthorityEntity> authorities;

  // omit getter/setter
  
  // methods from UserDetails interface
  @Override
  public boolean isAccountNonExpired() {
    return true;  // database has no mapping fields, always true
  }

  @Override
  public boolean isAccountNonLocked() {
    return true;  // database has no mapping fields, always true
  }

  @Override
  public boolean isCredentialsNonExpired() {
    return true;  // database has no mapping fields, always true
  }

  @Override
  public boolean isEnabled() {
    return enabled;
  }
}

This UserEntity implements spring security's UserDetails interface. For simplicity, it only has 3 non-trivial fields, username, password and enabled besides the primary key. Another entity is defined in AuthorityEntity.java

package com.shengwang.demo.model;

import org.springframework.security.core.GrantedAuthority;
// omit other import

@Entity
@Table(name="t_authority")
public class AuthorityEntity implements GrantedAuthority{

  private static final long serialVersionUID = 1204090309640166925L;

  @Id
  @GeneratedValue(strategy=GenerationType.IDENTITY)
  private long id;
  private String roleName;

  //bi-directional many-to-one association to User
  @ManyToOne
  @JoinColumn(name="userId")
  private UserEntity user;
  
  // omit getter/setter
  
  // mothod from interface GrantedAuthority
  @Override
  public String getAuthority() {
    return this.roleName;
  }  
}

The AuthorityEntity implments Spring security's GrantedAuthority interface. The only non-trivial field is string for role name.  Here the points for entity definition are interfaces they implement.

3.2  Define class implements UserDetailsService

Here is our MyUserDetailsService. Choose a better name in your real project. It only has 1 method which access database, find user by username.

package com.shengwang.demo.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import com.shengwang.demo.model.UserRepository;

@Service
public class MyUserDetailsService implements UserDetailsService {

  @Autowired
  UserRepository userRepo;
  
  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    return userRepo.findByUsername(username);
  }
}

The mothod loadUserByUsername() access database to load the user entity. Since the user entity class implements the UserDetails interface, it can be returned directly.

3.3 Define config class extends from WebSecurityConfigurerAdapter

This SecurityConfig class, extends from  WebSecurityConfigurerAdapter is the key of Spring security configuration.  In fact it will finally cause Spring create "springSecuritFilterChain" eventually. This class has annotation @EnableWebSecurity.

package com.shengwang.demo;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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 com.shengwang.demo.service.MyUserDetailsService;

@EnableWebSecurity

public class SecurityConfig extends WebSecurityConfigurerAdapter {
  Logger logger = LoggerFactory.getLogger(SecurityConfig.class);

//  // In memory authentication, only for prototype or demo
//  @Autowired
//  public void configAuthenticationProvider(AuthenticationManagerBuilder auth) throws Exception {
//    logger.info("hello");
//    auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
//  }

  /**
   * 1. Define a DaoAuthenticationProvider bean. This bean use our own
   * UserDetailsService implementation for load user from database
   */
  @Bean
  DaoAuthenticationProvider daoAuthenticationProvider(MyUserDetailsService myUserDetailsService) {
    DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
    daoAuthenticationProvider.setUserDetailsService(myUserDetailsService);
    return daoAuthenticationProvider;
  }

  /**
   * 2. Use above bean for Authentication
   */
  @Autowired
  public void configAuthenticationProvider(AuthenticationManagerBuilder auth,
      DaoAuthenticationProvider daoAuthenticationProvider) throws Exception {
    auth.authenticationProvider(daoAuthenticationProvider);
  }

  /**
   * 3. Define access rules based on request url pattern. 
   * The rulese in this demo: 
   *    Anyone can access homepage / 
   *    Anyone can access static resources like /imgs/** 
   *    Only "ADMIN" role can access url under /admin/** 
   *    Only "USER" role can access url under /**
   */
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests().antMatchers("/").permitAll(); 
    http.authorizeRequests().antMatchers("/imgs/**").permitAll(); 
    http.authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN");
    http.authorizeRequests().antMatchers("/**").hasRole("USER").and().formLogin();
  }
}

Notice three things are defined here. A bean of  type DaoAuthenticationProvider, a method set to use that bean  and finally a overriden method to set up your own access rules based on http url.

3.4 Define class extends SecurityWebApplicationInitializer

This class will finally get the springSecurityFilterChain registered. Althought it's empty, it's mandatory!

package com.shengwang.demo;

import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer { 

}

3.5 Load the SecurityConfig in to root context

Load the class SecurityConfig into Spring web root context.

package com.shengwang.demo;

import javax.servlet.Filter;

import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class MyWebApplicationInitializerWithSpringSecurity extends AbstractAnnotationConfigDispatcherServletInitializer {

  @Override
  protected Class<?>[] getRootConfigClasses() {
    // compare to no spring security scenario
    // the only change is adding SecurityConfig.class 
    return new Class[] {SecurityConfig.class,RootConfig.class};
  }

  @Override
  protected Class<?>[] getServletConfigClasses() {
    return new Class[] {DispatcherConfig.class};
  }
  
  @Override
  protected String[] getServletMappings() {
    return new String[] { "/" };
  }

  // register other filters used in application
  @Override
  protected Filter[] getServletFilters() {
    return new Filter[] { new DelegatingFilterProxy("mdcInsertingServletFilter") };
  }
}

All done. Now the project hierrarchy may look like below.

image

4. Run it

Suppose we have our entity tables ready. User 'admin' has both ADMIN role and USER role. User 'tom' has only USER role.

imageimage

We have simple index page as below with 3 links , first to /admin, second to /user, last one to a statice resource /imgs/sample.jpg. Just to test the access rules we set in SecurityConfig.java.

image

Click the last link will directly show you the image without asking you to login in.

image

 

If you click first link to /admin, a default (and ugly) login page appears ask you to login.

image

If we only loing as a user WITHOUT the ADMIN role, like above, the access to /admin will result  403- Access Denied.

image

Wednesday, March 2, 2016

Java-based Spring mvc configuration - without Spring security

Spring 3+ supports using java-based configuration in replacement of the tradictional web.xml. You can replace web.xml with any class which implments interface org.springframework.web.WebApplicationInitializer ( which only has one onStartup() method).

Usually all you need to provide to a web.xml for a spring mvc application are:

  1. Spring Config for root context
  2. Spring config for servlet context   (for spring mvc, it's config for org.springframework.web.servlet.DispatcherServlet)
  3. Where to map spring's DispatcherServlet (ususally "/")
  4. Filters, if any

To facilitate usage of this WebApplicationInitializer  interface, Spring also provide serveral Abstract classes, which has methods directly ask for informations list above. The most used is class org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer

In this article, there are 2 demos, first uses WebApplicationInitializer, second uses AbstractAnnotationConfigDispatcherServletInitializer. Spring 4.2 is used in the demos.

This article only show basics when Spring security is not used. For more practical usage with Spring security, please check another article on "Java-based Spring mvc configuration  - with Spring security". 

0. Java config classes for root and servlet context

Both demos use same Java config classes to config root and servlet context.

The java config for root context is named RootConfig.java.

package com.shengwang.demo;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

import ch.qos.logback.classic.helpers.MDCInsertingServletFilter;

@Configuration
@EnableWebMvc
@ComponentScan({"com.shengwang.demo.service","com.shengwang.demo.model"})
public class RootConfig {
  
  // logback's filter for easy logging info. e.g client's IP
  @Bean
  public MDCInsertingServletFilter mdcInsertingServletFilter() {
    return new MDCInsertingServletFilter();
  }
}

The root context here has service and model beans registerd, but in practical it's really not that strict. Also a filter bean proviced by logback is configured in the root context. This "mdcInsertingServletFilter" bean is actually a filter and will be used later.

The java config for dispatcher servlet is called DispatcherConfig.java.

package com.shengwang.demo;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
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.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;


@Configuration
@EnableWebMvc
@ComponentScan("com.shengwang.demo.controller")

public class DispatcherConfig extends WebMvcConfigurerAdapter{
  
  // To use jsp
  @Bean
  ViewResolver internalViewResolver() {
    // the view resolver bean ...
    InternalResourceViewResolver resolver = new InternalResourceViewResolver();
    resolver.setPrefix("/WEB-INF/");
    resolver.setSuffix(".jsp");
    return resolver;
  }
  
  // config static resources
  @Override
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
    // Don't forget the ending "/" for location or you will hit 404.
    registry.addResourceHandler("/img/**").addResourceLocations("/static/images/");
    registry.addResourceHandler("/js/**").addResourceLocations("/static/js/");
    registry.addResourceHandler("/css/**").addResourceLocations("/static/css/");
  }
}

The servlet context contain controller beans and a view resolver bean to render JSP page, just for demo. Usually it extends WebMvcConfigurerAdapter, so you can configure web mvc specific behaviours, such as static resource mapping or async support by overriding corresponding methods. (WebMvcConfigurerAdapter workds as default web mvc config with a bunch of empty methods)

The content of the RootConfig.java and DispatcherConfig.java are list here for completeness of the demos. Just remember RootConfig.java for root context, DispatcherConfig.java for servlet context. They are used in following demos. For both demos, the hierrarchy looks like below.

image

 

1. Demo One - Use WebApplicationInitializer directly

The MyWebApplicatonInitializerWithoutSpringSecurity.java implements interface WebApplicationInitializer. Spring mvc doesn't need this class to be a bean, no @Bean  or @Component annotation for this class.

package com.shengwang.demo;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.servlet.DispatcherServlet;


public class MyWebApplicatonInitializerWithoutSpringSecurity implements WebApplicationInitializer {
  
  @Override
  public void onStartup(ServletContext container) throws ServletException {
    useRootContext(container);
    useDispatcherContext(container);
    addFilter(container);
  }

  private void useRootContext(ServletContext container) {
    // Create the 'root' Spring application context
    AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
    rootContext.register(RootConfig.class); // <-- Use RootConfig.java

    // Manage the lifecycle of the root application context
    container.addListener(new ContextLoaderListener(rootContext));
  }
  
  private void useDispatcherContext(ServletContext container) {
    // Create the dispatcher servlet's Spring application context
    AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
    dispatcherContext.register(DispatcherConfig.class); // <-- Use DispatcherConfig.java

    // Define mapping between <servlet> and <servlet-mapping>
    ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(
        dispatcherContext));
    dispatcher.addMapping("/");
    dispatcher.setLoadOnStartup(1);
  }

  private void addFilter(ServletContext container) {
    String filterName = "WhatEverYouWantToNameYourFilter";
    String filterBeanName = "mdcInsertingServletFilter";
    container.addFilter(filterName, new DelegatingFilterProxy(filterBeanName)).addMappingForUrlPatterns(null, false, "/");
  }
}

2. Demo Two - Use AbstractAnnotationConfigDispatcherServletInitializer for simplicity

Define own class extends from this abstract class.

package com.shengwang.demo;

import javax.servlet.Filter;

import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;


public class MyWebApplicationInitializerWithoutSpringSecurity extends AbstractAnnotationConfigDispatcherServletInitializer {

  // return null, if you don't use both root and servlet context
  @Override
  protected Class<?>[] getRootConfigClasses() {
    return new Class[] {RootConfig.class};
  }
  
  // return null, if you don't use both root and servlet context
  @Override
  protected Class<?>[] getServletConfigClasses() {
    return new Class[] {DispatcherConfig.class};
  }
  
  // map dispatcherServlet to "/"
  @Override
  protected String[] getServletMappings() {
    return new String[] { "/" };
  }

  // returned filters will be register to dispatcherServlet
  @Override
  protected Filter[] getServletFilters() {
    // bean defined in RootConfig.java
    String beanNameOfFilter = "mdcInsertingServletFilter";
    return new Filter[] { new DelegatingFilterProxy(beanNameOfFilter) };
  }
}

There are 4 override  mothods, each has a every clear purpose.

3.  Misc

To use java-based spring mvc to replace web.xml, don't forget to config maven war plugin to ignore the "web.xml missging error" in the pom.xml.

  <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-war-plugin</artifactId>
    <version>2.2</version>

    <!-- ignore missing web.xml error -->
    <configuration>
      <failOnMissingWebXml>false</failOnMissingWebXml>
    </configuration>
  </plugin>

See Also

Simplified Web Configuration with Spring  by Josh Long

Tuesday, February 16, 2016

How to use H2 embeded database in spring application

H2, as a embeded memory database, is mainly used for development and test phase. H2 also has a web console which is very convenient.

image

1. Use H2 in Spring boot application

Spring boot has almost everything done for you. You need to do 2 steps, first inluce H2 in you pom.xml.

  <!-- H2 -->
  <dependency>
   <groupId>com.h2database</groupId>
   <artifactId>h2</artifactId>
   <scope>runtime</scope>
  </dependency>

The version of H2 is managed by spring boot and  scope can be set to runtime because you code should not have any APIs come from H2 itself. Then specify datasource url to use H2 in the application.properties file or in lieu of yaml styled configuration

# define DataSrouce properties 
# use h2 can have a buid in web console http://localhost:8080/h2-console
spring.datasource.url=jdbc:h2:mem:mydb

In spring boot application, there is no need to set username and password for using H2 memory database, because H2's default username is 'sa' (or SA, case insensitive), default password is empty. These default username/password will be auto configured by spring boot.  Also Spring boot will config the H2 web console for you automatcally at http://localhost:8080/h2-console

Use H2 in spring-mvc application (non-spring boot)

In non-spring boot application, which is normall just powered by spring-webmvc and other spring artifacts, using H2 need 3 steps. First is also to include H2 in pom.xml

  <!-- H2 -->
  <dependency>
   <groupId>com.h2database</groupId>
   <artifactId>h2</artifactId>
   <version>1.4.190</version>
  </dependency>

Secondly,  use H2 in any ORM configuration. For example if your project uses JPA as persistence and uses Hibernate as JPA service provider. The JPA's configuration file /META-INF/persistence.xml may looks like below.

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1"
 xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
 <persistence-unit name="spring-persistence-jpa-tx" transaction-type="RESOURCE_LOCAL">
  <provider>org.hibernate.ejb.HibernatePersistence</provider>
  <exclude-unlisted-classes>false</exclude-unlisted-classes>
  <properties>
   <property name="javax.persistence.jdbc.driver" value="org.h2.Driver" />
   <property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:mydb" />
   <property name="javax.persistence.jdbc.user" value="sa" />
   <property name="javax.persistence.jdbc.password" value="" />
   
   <!-- Automatically drop then create table -->
   <property name="hibernate.hbm2ddl.auto" value="create" />
   <!-- print out sql  -->
   <property name="hibernate.show_sql" value="true"/>
  </properties>
 </persistence-unit>
</persistence>

Specify the jdbc driver, jdbc url and etc , just like other database.

Finally, and optionally, If you want the H2 web console, you need to add a bean into spring context. In xml-styled spring configuration file, it looks like below.

 <bean id="h2WebServer" class="org.h2.tools.Server" factory-method="createWebServer"
  init-method="start" destroy-method="stop">
  <constructor-arg value="-web,-webAllowOthers,-webDaemon,-webPort,8082" />
 </bean>
In java-styled spring configuration, it looks like below.
@Configuration
@ComponentScan
public class JavaConfiguration {
 //... other beans
 
 @Bean(initMethod="start",destroyMethod="stop")
 public org.h2.tools.Server h2WebConsonleServer () throws SQLException {
   return org.h2.tools.Server.createWebServer("-web","-webAllowOthers","-webDaemon","-webPort", "8082");
 }
}

The -webDaemon means run H2 web console in a daemon thread which will not block the application process termination. -webPort can set the web console service port, default is 8082. The web console url is http://localhost:8082 (Different from the web console url in spring boot application)

Use H2 in spring boot application

In a spring boot application, first add H2 to the dependency.

  <dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>1.4.182</version>
    <scope>runtime</scope>
  </dependency>

Choose a proper H2 version to use and also set scope to runtime become you should not expliity use any class from H2 in you application.

Then configure application.properties or application.yml file for spring boot datasource. Following is example in application.yml file.

spring:
  datasource:
    url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
    username: sa
    password:
    driver-class-name: org.h2.Driver
    platform: h2

  # enable H2 web console and set url for web console
  # http://localhost:8080/console
  h2:
    console:
      enabled: true
      path: /console
The default username for H2 is 'sa', default password is empty.

If you also use spring security in your application, which means spring security is in your pom.xml like below.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>

Then you need to add a line in your spring security configuration to make H2 web console working.

@EnableWebSecurity
 
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
 
 //......
 
 
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests().antMatchers("/").permitAll(); 
    http.authorizeRequests().antMatchers("/imgs/**").permitAll(); 
    http.authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN");
    http.authorizeRequests().antMatchers("/**").hasRole("USER").and().formLogin();
  
    // add this line to use H2 web console
    http.headers().frameOptions().disable();
  }
}

Without this line to setup frame in spring security, you will see a empty page after you login the H2  web console.

Powered by Blogger.

About The Author

My Photo
Has been a senior software developer, project manager for 10+ years. Dedicate himself to Alcatel-Lucent and China Telecom for delivering software solutions.

Pages

Unordered List