Spring专题

SpringMVC4 + Spring Data JPA + SpringSecurity源码案例

  

本案例中展示如何使用JavaConfig集成SpringMVC4, Spring Data JPA ( Hibernate )和 SpringSecurity 

1.配置pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

       <modelVersion>4.0.0</modelVersion>

       <groupId>com.jdon</groupId>

       <artifactId>spring-javaconfig</artifactId>

       <version>1.0</version>

       <packaging>war</packaging>

       <name>SpringApp JavaConfig Demo</name>

      

       <properties>

              <java.version>1.7</java.version>

              <junit.version>4.11</junit.version>

              <slf4j.version>1.7.5</slf4j.version>

              <logback.version>1.0.13</logback.version>

              <spring.version>4.0.0.RELEASE</spring.version>

              <spring-data-jpa.version>1.4.1.RELEASE</spring-data-jpa.version>

              <spring-security.version>3.2.0.RELEASE</spring-security.version>

              <hibernate.version>4.2.6.Final</hibernate.version>

              <aspectj.version>1.7.2</aspectj.version>

              <mysql.version>5.1.26</mysql.version>

              <jackson-json.version>2.3.1</jackson-json.version>

              <commons-dbcp.version>1.2.2</commons-dbcp.version>

              <commons-lang3.version>3.1</commons-lang3.version>

       </properties>

      

       <build>

              <finalName>${project.artifactId}</finalName>

              <plugins>

                     <plugin>

                            <groupId>org.apache.maven.plugins</groupId>

                            <artifactId>maven-compiler-plugin</artifactId>

                            <version>3.1</version>

                            <configuration>

                                   <source>${java.version}</source>

                                   <target>${java.version}</target>

                            </configuration>

                     </plugin>

              </plugins>

       </build>

 

       <dependencies>

       <!-- Logging dependencies -->

              <dependency>

                     <groupId>org.slf4j</groupId>

                     <artifactId>jcl-over-slf4j</artifactId>

                     <version>${slf4j.version}</version>

              </dependency>

 

              <dependency>

                     <groupId>org.slf4j</groupId>

                     <artifactId>slf4j-api</artifactId>

                     <version>${slf4j.version}</version>

              </dependency>

 

              <dependency>

                     <groupId>org.slf4j</groupId>

                     <artifactId>slf4j-log4j12</artifactId>

                     <version>${slf4j.version}</version>

              </dependency>

             

              <dependency>

                     <groupId>ch.qos.logback</groupId>

                     <artifactId>logback-classic</artifactId>

                     <version>${logback.version}</version>

              </dependency>             

 

              <!-- Spring dependencies -->

 

              <dependency>

                     <groupId>org.springframework</groupId>

                     <artifactId>spring-context-support</artifactId>

                     <exclusions>

                            <exclusion>

                                   <groupId>commons-logging</groupId>

                                   <artifactId>commons-logging</artifactId>

                            </exclusion>

                     </exclusions>

              </dependency>

              <dependency>

                     <groupId>org.springframework</groupId>

                     <artifactId>spring-webmvc</artifactId>

              </dependency>

              <dependency>

                     <groupId>org.springframework</groupId>

                     <artifactId>spring-test</artifactId>

              </dependency>

              <!-- Spring Data JPA dependencies -->

             

              <dependency>

                     <groupId>org.springframework.data</groupId>

                     <artifactId>spring-data-jpa</artifactId>

                     <version>${spring-data-jpa.version}</version>

              </dependency>

 

              <dependency>

                     <groupId>org.hibernate</groupId>

                     <artifactId>hibernate-entitymanager</artifactId>

                     <version>${hibernate.version}</version>

              </dependency>

             

              <!-- SpringSecurity dependencies -->

              <dependency>

                     <groupId>org.springframework.security</groupId>

                     <artifactId>spring-security-core</artifactId>

                     <version>${spring-security.version}</version>

              </dependency>

              <dependency>

                     <groupId>org.springframework.security</groupId>

                     <artifactId>spring-security-web</artifactId>

                     <version>${spring-security.version}</version>

              </dependency>

              <dependency>

                     <groupId>org.springframework.security</groupId>

                     <artifactId>spring-security-config</artifactId>

                     <version>${spring-security.version}</version>

              </dependency>

              <dependency>

                     <groupId>org.springframework.security</groupId>

                     <artifactId>spring-security-taglibs</artifactId>

                     <version>${spring-security.version}</version>

              </dependency>      

             

              <dependency>

                     <groupId>org.aspectj</groupId>

                     <artifactId>aspectjweaver</artifactId>

                     <version>${aspectj.version}</version>

              </dependency>

              <dependency>

                     <groupId>org.aspectj</groupId>

                     <artifactId>aspectjrt</artifactId>

                     <version>${aspectj.version}</version>

              </dependency>      

 

              <!-- Testing dependencies -->

              <dependency>

                     <groupId>junit</groupId>

                     <artifactId>junit</artifactId>

                     <version>${junit.version}</version>

                     <scope>test</scope>

              </dependency>             

 

              <!-- DB dependencies -->

              <dependency>

                     <groupId>mysql</groupId>

                     <artifactId>mysql-connector-java</artifactId>

                     <version>${mysql.version}</version>

              </dependency>

                           

              <dependency>

                     <groupId>commons-dbcp</groupId>

                     <artifactId>commons-dbcp</artifactId>

                     <version>${commons-dbcp.version}</version>

              </dependency>

                           

              <dependency>

                     <groupId>com.fasterxml.jackson.core</groupId>

                     <artifactId>jackson-databind</artifactId>

                     <version>${jackson-json.version}</version>

              </dependency>

             

              <dependency>

                  <groupId>javax.mail</groupId>

                  <artifactId>mail</artifactId>

                  <version>1.4.3</version>

           </dependency>

          

              <!-- Web dependencies -->

              <dependency>

                     <groupId>javax.servlet</groupId>

                     <artifactId>javax.servlet-api</artifactId>

                     <version>3.0.1</version>

                     <scope>provided</scope>

              </dependency>

 

              <dependency>

                     <groupId>taglibs</groupId>

                     <artifactId>standard</artifactId>

                     <version>1.1.2</version>

                     <scope>compile</scope>

              </dependency>

              <dependency>

                     <groupId>jstl</groupId>

                     <artifactId>jstl</artifactId>

                     <version>1.2</version>

                     <scope>compile</scope>

              </dependency>

       </dependencies>

 

       <dependencyManagement>

              <dependencies>

                     <dependency>

                            <groupId>org.springframework</groupId>

                            <artifactId>spring-framework-bom</artifactId>

                            <version>${spring.version}</version>

                            <type>pom</type>

                            <scope>import</scope>

                     </dependency>             

              </dependencies>

       </dependencyManagement>

      

</project>

2.配置application.properties数据库连接和Email设置


################### DataSource Configuration ##########################

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=root
jdbc.password=admin

init-db=false

################### Hibernate Configuration ##########################

hibernate.dialect=org.hibernate.dialect.MySQLDialect
hibernate.show_sql=true
hibernate.hbm2ddl.auto=update

################### JavaMail Configuration ##########################
smtp.host=smtp.gmail.com
smtp.port=465
smtp.protocol=smtps
smtp.username=sivaprasadreddy.k@gmail.com
smtp.password=
support.email=sivaprasadreddy.k@gmail.com

3.在AppConfig.java中配置服务层Bean如PropertySourcesPlaceholderConfigurer 和 JavaMailSender 

@Configuration

@ComponentScan(basePackages={"com.jdon.springapp"},

              excludeFilters=@ComponentScan.Filter(type=FilterType.REGEX, pattern={"com.jdon.springapp.web.*"}))

@PropertySource(value = { "classpath:application.properties" })

@EnableScheduling

@EnableAspectJAutoProxy

@EnableCaching

public class AppConfig

{

       @Autowired

       private Environment env;

 

       @Bean

       public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer()

       {

              return new PropertySourcesPlaceholderConfigurer();

       }

      

       @Bean

       public JavaMailSenderImpl javaMailSenderImpl() {

              JavaMailSenderImpl mailSenderImpl = new JavaMailSenderImpl();

              mailSenderImpl.setHost(env.getProperty("smtp.host"));

              mailSenderImpl.setPort(env.getProperty("smtp.port", Integer.class));

              mailSenderImpl.setProtocol(env.getProperty("smtp.protocol"));

              mailSenderImpl.setUsername(env.getProperty("smtp.username"));

              mailSenderImpl.setPassword(env.getProperty("smtp.password"));

 

              Properties javaMailProps = new Properties();

              javaMailProps.put("mail.smtp.auth", true);

              javaMailProps.put("mail.smtp.starttls.enable", true);

 

              mailSenderImpl.setJavaMailProperties(javaMailProps);

 

              return mailSenderImpl;

       }

             

       @Bean

       public CacheManager cacheManager()

       {

              return new ConcurrentMapCacheManager();

       }

}

 

注意到我们已经使用REGEX excludeFilter 排除扫描com.jdon.springapp.web.*,如果不这样做,单元测试会遇到下下面错误:

java.lang.IllegalArgumentException: A ServletContext is required to configure default servlet handling

同时也必须注意到我们使用@EnableCaching激活了缓存,那就要定义CacheManager  bean

4.在com.jdon.springapp.config.PersistenceConfig.java中定义持久层Bean

 

@Configuration

@EnableTransactionManagement

@EnableJpaRepositories(basePackages="com.jdon.springapp.repositories")

public class PersistenceConfig

{

       @Autowired

       private Environment env;

 

       @Value("${init-db:false}")

       private String initDatabase;

      

       @Bean

       public PlatformTransactionManager transactionManager()

       {

              EntityManagerFactory factory = entityManagerFactory().getObject();

              return new JpaTransactionManager(factory);

       }

 

       @Bean

       public LocalContainerEntityManagerFactoryBean entityManagerFactory()

       {

              LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();

 

              HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();

              vendorAdapter.setGenerateDdl(Boolean.TRUE);

              vendorAdapter.setShowSql(Boolean.TRUE);

 

              factory.setDataSource(dataSource());

              factory.setJpaVendorAdapter(vendorAdapter);

              factory.setPackagesToScan("com.jdon.springapp.entities");

 

              Properties jpaProperties = new Properties();

              jpaProperties.put("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));

              factory.setJpaProperties(jpaProperties);

 

              factory.afterPropertiesSet();

              factory.setLoadTimeWeaver(new InstrumentationLoadTimeWeaver());

              return factory;

       }

 

       @Bean

       public HibernateExceptionTranslator hibernateExceptionTranslator()

       {

              return new HibernateExceptionTranslator();

       }

      

       @Bean

       public DataSource dataSource()

       {

              BasicDataSource dataSource = new BasicDataSource();

              dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));

              dataSource.setUrl(env.getProperty("jdbc.url"));

              dataSource.setUsername(env.getProperty("jdbc.username"));

              dataSource.setPassword(env.getProperty("jdbc.password"));

              return dataSource;

       }

      

       @Bean

       public DataSourceInitializer dataSourceInitializer(DataSource dataSource)

       {

              DataSourceInitializer dataSourceInitializer = new DataSourceInitializer();

              dataSourceInitializer.setDataSource(dataSource);

              ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();

              databasePopulator.addScript(new ClassPathResource("db.sql"));

              dataSourceInitializer.setDatabasePopulator(databasePopulator);

              dataSourceInitializer.setEnabled(Boolean.parseBoolean(initDatabase));

              return dataSourceInitializer;

       }    

}

在这里我们已经配置了数据库源DataSource 和 JPA EntityManagerFactory ,使用的是Hibernate实现,我们还配置了DataSourceInitializer bean 用来初始化和导入数据表,我们能够通过改变application.properties中的 init-db属性值来执行 db.sql 脚本,最后我们激活了Spring Data JPA 仓库扫描,使用的是@EnableJpaRepositories t实现扫描,"com.jdon.springapp.repositories"包,这是PA repository 接口.

5.现在我们在WebMvcConfig配置Web相关的Bean

 

 

@Configuration

@ComponentScan(basePackages = { "com.sivalabs.springapp.web"})

@EnableWebMvc

public class WebMvcConfig extends WebMvcConfigurerAdapter

{

       @Override

       public void addViewControllers(ViewControllerRegistry registry)

       {

              super.addViewControllers(registry);

              registry.addViewController("login/form").setViewName("login");           

              registry.addViewController("welcome").setViewName("welcome");

              registry.addViewController("admin").setViewName("admin");

       }

 

       @Bean

       public ViewResolver resolver()

       {

              InternalResourceViewResolver url = new InternalResourceViewResolver();

              url.setPrefix("/WEB-INF/jsp/");

              url.setSuffix(".jsp");

              return url;

       }

 

       @Override

       public void addResourceHandlers(ResourceHandlerRegistry registry)

       {

              registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");

       }

 

       @Override

       public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer)

       {

              configurer.enable();

       }

 

       @Bean(name = "messageSource")

       public MessageSource configureMessageSource()

       {

              ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();

              messageSource.setBasename("classpath:messages");

              messageSource.setCacheSeconds(5);

              messageSource.setDefaultEncoding("UTF-8");

              return messageSource;

       }

 

       @Bean

       public SimpleMappingExceptionResolver simpleMappingExceptionResolver()

       {

              SimpleMappingExceptionResolver b = new SimpleMappingExceptionResolver();

              Properties mappings = new Properties();

              mappings.put("org.springframework.dao.DataAccessException", "error");

              b.setExceptionMappings(mappings);

              return b;

       }

}

6.使用AbstractAnnotationConfigDispatcherServletInitializer配置DispatcherService

import javax.servlet.Filter;

import org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter;

import org.springframework.web.filter.DelegatingFilterProxy;

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

import com.sivalabs.springapp.config.AppConfig;

 

public class SpringWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer

{

 

       @Override

       protected Class<?>[] getRootConfigClasses()

       {

              return new Class<?>[] { AppConfig.class};

       }

 

       @Override

       protected Class<?>[] getServletConfigClasses()

       {

              return new Class<?>[] { WebMvcConfig.class };

       }

 

       @Override

       protected String[] getServletMappings()

       {

              return new String[] { "/" };

       }

 

       @Override

    protected Filter[] getServletFilters() {

       return new Filter[]{

                                                 new OpenEntityManagerInViewFilter()

                                            };

    }

 

}

在这里我们配置了AppConfig.class 作为RootConfig类,WebMvcConfig.class 作为ServletConfigClasses,类似我们在web.xml 配置ContextLoaderListener 和DispatcherServlet's contextConfigLocation .
另外我们也注册了 OpenEntityManagerInViewFilter用来激JPA实体的活懒加载。

7.开始配置SpringSecurity

首先创建SecurityUser 实现接口org.springframework.security.core.userdetails.UserDetails

 

public class SecurityUser extends User implements UserDetails

{

 

       private static final long serialVersionUID = 1L;

       public SecurityUser(User user) {

              if(user != null)

              {

                     this.setId(user.getId());

                     this.setName(user.getName());

                     this.setEmail(user.getEmail());

                     this.setPassword(user.getPassword());

                     this.setDob(user.getDob());

                     this.setRoles(user.getRoles());

              }           

       }

      

       @Override

       public Collection<? extends GrantedAuthority> getAuthorities() {

             

              Collection<GrantedAuthority> authorities = new ArrayList<>();

              Set<Role> userRoles = this.getRoles();

             

              if(userRoles != null)

              {

                     for (Role role : userRoles) {

                            SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role.getRoleName());

                            authorities.add(authority);

                     }

              }

              return authorities;

       }

 

       @Override

       public String getPassword() {

              return super.getPassword();

       }

 

       @Override

       public String getUsername() {

              return super.getEmail();

       }

 

       @Override

       public boolean isAccountNonExpired() {

              return true;

       }

 

       @Override

       public boolean isAccountNonLocked() {

              return true;

       }

 

       @Override

       public boolean isCredentialsNonExpired() {

              return true;

       }

 

       @Override

       public boolean isEnabled() {

              return true;

       }    

}

其次,实现UserDetailsService 然后使用Spring Data JPA 仓储加载用户细节资料。

@Component

public class CustomUserDetailsService implements UserDetailsService

{

       @Autowired

       private UserService userService;

      

       @Override

       public UserDetails loadUserByUsername(String userName)

                     throws UsernameNotFoundException {

              User user = userService.findUserByEmail(userName);

              if(user == null){

                     throw new UsernameNotFoundException("UserName "+userName+" not found");

              }

              return new SecurityUser(user);

       }

}

创建SecurityConfig包含Spring安全配置:

 

@Configuration

@EnableWebSecurity

public class SecurityConfig extends WebSecurityConfigurerAdapter

{

       @Autowired

       private DataSource dataSource;

 

       @Autowired

       private CustomUserDetailsService customUserDetailsService;

 

       @Override

    protected void configure(AuthenticationManagerBuilder registry) throws Exception {

              /*

        registry

        .inMemoryAuthentication()

        .withUser("siva")

          .password("siva")

          .roles("USER")

          .and()

        .withUser("admin")

          .password("admin")

          .roles("ADMIN","USER");

        */

       

        //registry.jdbcAuthentication().dataSource(dataSource);

              registry.userDetailsService(customUserDetailsService);

    }

 

 

         @Override

         public void configure(WebSecurity web) throws Exception {

           web

             .ignoring()

                .antMatchers("/resources/**");

         }

 

         @Override

         protected void configure(HttpSecurity http) throws Exception {

           http

           .csrf().disable()

           .authorizeRequests()

               .antMatchers("/login","/login/form**","/register","/logout").permitAll()

               .antMatchers("/admin","/admin/**").hasRole("ADMIN")

               .anyRequest().authenticated()

               .and()

           .formLogin()

               .loginPage("/login/form")

               .loginProcessingUrl("/login")

               .failureUrl("/login/form?error")

               .permitAll();

         }

}

在我们之前创建的SpringWebAppInitializer 中加入SecurityConfig 配置类

public class SpringWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer

{

       @Override

       protected Class<?>[] getRootConfigClasses()

       {

              return new Class<?>[] { AppConfig.class};

              //As we have SecurityConfig.java in same package as AppConfig.java and enabled ComponentScan to scan "com.sivalabs.springapp.config" we don't need to explicitely configure it.

              //otherwise we should add SecurityConfig.class to getRootConfigClasses()

              //return new Class<?>[] { AppConfig.class, SecurityConfig.class};

       }

       ...

       ...

       @Override

    protected Filter[] getServletFilters() {

       return new Filter[]{

                     new DelegatingFilterProxy("springSecurityFilterChain"),

                     new OpenEntityManagerInViewFilter()};

    }

 

}    

登录视图页面login.jsp

<!DOCTYPE html>

<%@taglib uri="http://www.springframework.org/tags"  prefix="spring"%>

<%@taglib uri="http://www.springframework.org/tags/form" prefix="form" %>

<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>

<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c" %>

<c:url var="rootURL" value="/"/>

<html>

<head>

<title>Login</title>

<link href="${rootURL}resources/bootstrap/css/bootstrap.css" media="screen" rel="stylesheet" type="text/css" />

<script type="text/javascript" src="${rootURL}resources/jquery/jquery-1.10.2.js"></script>

<script type="text/javascript" src="${rootURL}resources/bootstrap/js/bootstrap.js"></script>

<script type="text/javascript" src="${rootURL}resources/js/app.js"></script>

</head>

<body>

              <div class="col-md-6 col-md-offset-2">     

              <c:if test="${param.error != null}">

             <div class="alert alert-danger">

                 Invalid UserName and Password.

             </div>

         </c:if>

         <c:if test="${param.logout != null}">

             <div class="alert alert-success">

                 You have been logged out.

             </div>

         </c:if>  

         </div> 

           

     <div class="row">

              <div class="col-md-6 col-md-offset-2">     

                     <h2>User Login Form</h2>

                     <form:form id="loginForm" method="post" action="${rootURL}login" modelAttribute="user"

              class="form-horizontal" role="form" cssStyle="width: 800px; margin: 0 auto;">

                <div class="form-group">

                  <label for="username" class="col-sm-2 control-label">UserName*</label>

                  <div class="col-sm-4">

                    <input type="text" id="username" name="username" class="form-control" placeholder="UserName" />

                  </div>

                </div>

                <div class="form-group">

                  <label for="password" class="col-sm-2 control-label">Password*</label>

                  <div class="col-sm-4">

                    <input type="password" id="password" name="password" class="form-control" placeholder="Password" />

                  </div>

                </div>

                <div class="form-group">

                  <div class="col-sm-offset-2 col-sm-4">

                    <input type="submit" class="btn btn-primary" value="Login">

                  </div>

                </div>

               

              </form:form>

       </div>

</div>    

</body>

</html>

登录成功后的welcome.jsp

<h3>Email: <sec:authentication property="name"/></h3>

<h3>

       <sec:authorize access="hasRole('ROLE_ADMIN')">

              <a href="admin">Administration</a>

       </sec:authorize>

</h3>

<p> <a href="logout">Logout</a></p>

</body>

本项目源码下载github

 

Spring各种源码项目下载