• Spring MVC4 + Spring Security4 + Hibernate实例


    http://www.yiibai.com/spring-security/spring-mvc-4-and-spring-security-4-integration-example.html

    在这篇教程文章中,我们将使用Spring Security,Hibernate+MySQL数据库来集成构建一个成熟的Spring MVC应用程序。处理多对多映射关系,同时利用BCrypt格式加密密码存储,和使用自定义PersistentTokenRepository实现Hibernate HibernateTokenRepositoryImpl并提供了 "记住我" 功能,从数据库检索和更新记录或在事务中删除它们;在这个实例中全部采用注释配置。
    这个项目可以作为您自己的Spring MVC项目集成 Spring Security 模板,免受搭建开发环境皮肉之苦。

    注意:

    这篇文章展示了一个完整的应用的完整代码。为了缩小本教程文章的篇幅,这里需要省略一些基本的知识点文字描述。如果您有兴趣了解这些细节,这个这个这个文章可能会帮助你。

    先来做个概括:

    这个项目显示了一个简单的用户管理应用程序。您可以创建一个新用户,编辑或删除现有用户,并列出所有用户信息列表。一个用户可以与一个或多个用户配置(UserProfile)相关联,这表现出了多对多的关系。应用程序的URL是使用 Spring Security 作访问保护的。这意味着,基于对登录用户的角色来获得判定URL是否被授予或禁止访问。在视图层,用户将根据分配给他/她的角色只能看到被允许页面内容,这些是在视图层中使用Spring Security标签来实现的。

     
    以下是一些需要使用到的技术:
    • Spring 4.2.5.RELEASE
    • Spring Security 4.0.4.RELEASE
    • Hibernate Core 4.3.11.Final
    • validation-api 1.1.0.Final
    • hibernate-validator 5.1.3.Final
    • MySQL Server 5.6
    • Maven 3
    • JDK 1.7
    • Tomcat 8.0.21
    • Eclipse MARS.1 Release 4.5.1
    • logback 1.1.7

    现在就让我们开始一步步地学习和实现吧!

    第1步:创建目录结构
    以下将是最终的项目结构:

    现在,让我们解释在上面提到的结构内容每个细节。

    第2步: 更新 pom.xml 以包括必需的依懒

    <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
    	xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    
    	<modelVersion>4.0.0</modelVersion>
    	<groupId>com.yiibai.springmvc</groupId>
    	<artifactId>SpringMVCHibernateManyToManyCRUDExample</artifactId>
    	<packaging>war</packaging>
    	<version>1.0.0</version>
    	<name>SpringMVCHibernateWithSpringSecurityExample</name>
    
      	<properties>
    		<springframework.version>4.2.5.RELEASE</springframework.version>
    		<springsecurity.version>4.0.4.RELEASE</springsecurity.version>
    		<hibernate.version>4.3.11.Final</hibernate.version>
    		<mysql.connector.version>5.1.31</mysql.connector.version>
    	</properties>
    
    	<dependencies>
    		<!-- Spring -->
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-core</artifactId>
    			<version>${springframework.version}</version>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-web</artifactId>
    			<version>${springframework.version}</version>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-webmvc</artifactId>
    			<version>${springframework.version}</version>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-tx</artifactId>
    			<version>${springframework.version}</version>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-orm</artifactId>
    			<version>${springframework.version}</version>
    		</dependency>
    
    		<!-- Spring Security -->
    		<dependency>
    			<groupId>org.springframework.security</groupId>
    			<artifactId>spring-security-web</artifactId>
    			<version>${springsecurity.version}</version>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework.security</groupId>
    			<artifactId>spring-security-config</artifactId>
    			<version>${springsecurity.version}</version>
    		</dependency>
    		<dependency>
        		<groupId>org.springframework.security</groupId>
        		<artifactId>spring-security-taglibs</artifactId>
        		<version>${springsecurity.version}</version>
    		</dependency>
    
    
    		<!-- Hibernate -->
    		<dependency>
    			<groupId>org.hibernate</groupId>
    			<artifactId>hibernate-core</artifactId>
    			<version>${hibernate.version}</version>
    		</dependency>
    
    		<!-- jsr303 validation -->
    		<dependency>
    			<groupId>javax.validation</groupId>
    			<artifactId>validation-api</artifactId>
    			<version>1.1.0.Final</version>
    		</dependency>
    		<dependency>
    			<groupId>org.hibernate</groupId>
    			<artifactId>hibernate-validator</artifactId>
    			<version>5.1.3.Final</version>
    		</dependency>
    		
    		<!-- MySQL -->
    		<dependency>
    			<groupId>mysql</groupId>
    			<artifactId>mysql-connector-java</artifactId>
    			<version>${mysql.connector.version}</version>
    		</dependency>
    		
    		<!-- SLF4J/Logback -->
    		<dependency>
    			<groupId>ch.qos.logback</groupId>
    			<artifactId>logback-classic</artifactId>
    			<version>1.1.7</version>
    		</dependency>
    
    		<!-- Servlet+JSP+JSTL -->
    		<dependency>
    			<groupId>javax.servlet</groupId>
    			<artifactId>javax.servlet-api</artifactId>
    			<version>3.1.0</version>
    		</dependency>
    		<dependency>
    			<groupId>javax.servlet.jsp</groupId>
    			<artifactId>javax.servlet.jsp-api</artifactId>
    			<version>2.3.1</version>
    		</dependency>
    		<dependency>
    		    <groupId>javax.servlet</groupId>
    		    <artifactId>jstl</artifactId>
    		    <version>1.2</version>
    		</dependency>
    		
    	</dependencies>
    
    	<build>
    		<pluginManagement>
    			<plugins>
    				<plugin>
    			        <groupId>org.apache.maven.plugins</groupId>
    			        <artifactId>maven-compiler-plugin</artifactId>
    			        <version>3.2</version>
    			        <configuration>
    			            <source>1.7</source>
    			            <target>1.7</target>
    			        </configuration>
    			    </plugin>
    				<plugin>
    					<groupId>org.apache.maven.plugins</groupId>
    					<artifactId>maven-war-plugin</artifactId>
    					<version>2.4</version>
    					<configuration>
    						<warSourceDirectory>src/main/webapp</warSourceDirectory>
    						<warName>SpringMVCHibernateWithSpringSecurityExample</warName>
    						<failOnMissingWebXml>false</failOnMissingWebXml>
    					</configuration>
    				</plugin>
    			</plugins>
    		</pluginManagement>
    		<finalName>SpringMVCHibernateWithSpringSecurityExample</finalName>
    	</build>
    </project>
    

    第3步: 配置安全

    这是最重要的步骤,在我们的应用程序中添加的Spring Security是创建Spring Security的Java配置。

    该配置将创建被称为负责所有安全的springSecurityFilterChain Servlet过滤程序(保护应用程序的URL,验证提交用户名和密码,重定向到日志等等)提供在应用程序内。

    package com.yiibai.springmvc.security;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.authentication.AuthenticationTrustResolver;
    import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
    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 org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;
    import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
    
    @Configuration
    @EnableWebSecurity
    public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    
    	@Autowired
    	@Qualifier("customUserDetailsService")
    	UserDetailsService userDetailsService;
    
    	@Autowired
    	PersistentTokenRepository tokenRepository;
    
    	@Autowired
    	public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
    		auth.userDetailsService(userDetailsService);
    		auth.authenticationProvider(authenticationProvider());
    	}
    
    	@Override
    	protected void configure(HttpSecurity http) throws Exception {
    		http.authorizeRequests().antMatchers("/", "/list")
    				.access("hasRole('USER') or hasRole('ADMIN') or hasRole('DBA')")
    				.antMatchers("/newuser/**", "/delete-user-*").access("hasRole('ADMIN')").antMatchers("/edit-user-*")
    				.access("hasRole('ADMIN') or hasRole('DBA')").and().formLogin().loginPage("/login")
    				.loginProcessingUrl("/login").usernameParameter("ssoId").passwordParameter("password").and()
    				.rememberMe().rememberMeParameter("remember-me").tokenRepository(tokenRepository)
    				.tokenValiditySeconds(86400).and().csrf().and().exceptionHandling().accessDeniedPage("/Access_Denied");
    	}
    
    	@Bean
    	public PasswordEncoder passwordEncoder() {
    		return new BCryptPasswordEncoder();
    	}
    
    	@Bean
    	public DaoAuthenticationProvider authenticationProvider() {
    		DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
    		authenticationProvider.setUserDetailsService(userDetailsService);
    		authenticationProvider.setPasswordEncoder(passwordEncoder());
    		return authenticationProvider;
    	}
    
    	@Bean
    	public PersistentTokenBasedRememberMeServices getPersistentTokenBasedRememberMeServices() {
    		PersistentTokenBasedRememberMeServices tokenBasedservice = new PersistentTokenBasedRememberMeServices(
    				"remember-me", userDetailsService, tokenRepository);
    		return tokenBasedservice;
    	}
    
    	@Bean
    	public AuthenticationTrustResolver getAuthenticationTrustResolver() {
    		return new AuthenticationTrustResolverImpl();
    	}
    
    }
    
    如上图所示,接入到URL被控制,如下所示:
    • ‘/’ & ‘/list’ : 供所有用户访问;
    • ‘/newuser’ & ‘/delete-user-*’ : 只供管理员(Admin)用户访问;
    • ‘/edit-user-*’ : 供 Admin & DBA 用户访问;

    由于我们存储凭据在数据库中,所以要在UserDetailsService中配置DaoAuthenticationProvider来处理。此外,为了在数据库加密密码,我们选择BCryptPasswordEncoder。 此外,由于我们也将提供记住我的功能,跟踪令牌数据在数据库中,我们配置PersistentTokenRepository 实现。

    Spring Security带有两个PersistentTokenRepository的实现: JdbcTokenRepositoryImpl 和 InMemoryTokenRepositoryImpl. 我们可以选择JdbcTokenRepositoryImpl[此文章演示了rememberMe和JdbcTokenRepositoryImpl]。但在我们的应用程序使用Hibernate,为什么不使用Hibernate来代替JDBC创建一个自定义的实现?下面是相同功能的一个尝试。

    package com.yiibai.springmvc.dao;
    
    import java.util.Date;
    
    import org.hibernate.Criteria;
    import org.hibernate.criterion.Restrictions;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken;
    import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
    import org.springframework.stereotype.Repository;
    import org.springframework.transaction.annotation.Transactional;
    
    import com.yiibai.springmvc.dao.AbstractDao;
    import com.yiibai.springmvc.model.PersistentLogin;
    
    @Repository("tokenRepositoryDao")
    @Transactional
    public class HibernateTokenRepositoryImpl extends AbstractDao<String, PersistentLogin>
    		implements PersistentTokenRepository {
    
    	static final Logger logger = LoggerFactory.getLogger(HibernateTokenRepositoryImpl.class);
    
    	@Override
    	public void createNewToken(PersistentRememberMeToken token) {
    		logger.info("Creating Token for user : {}", token.getUsername());
    		PersistentLogin persistentLogin = new PersistentLogin();
    		persistentLogin.setUsername(token.getUsername());
    		persistentLogin.setSeries(token.getSeries());
    		persistentLogin.setToken(token.getTokenValue());
    		persistentLogin.setLast_used(token.getDate());
    		persist(persistentLogin);
    
    	}
    
    	@Override
    	public PersistentRememberMeToken getTokenForSeries(String seriesId) {
    		logger.info("Fetch Token if any for seriesId : {}", seriesId);
    		try {
    			Criteria crit = createEntityCriteria();
    			crit.add(Restrictions.eq("series", seriesId));
    			PersistentLogin persistentLogin = (PersistentLogin) crit.uniqueResult();
    
    			return new PersistentRememberMeToken(persistentLogin.getUsername(), persistentLogin.getSeries(),
    					persistentLogin.getToken(), persistentLogin.getLast_used());
    		} catch (Exception e) {
    			logger.info("Token not found...");
    			return null;
    		}
    	}
    
    	@Override
    	public void removeUserTokens(String username) {
    		logger.info("Removing Token if any for user : {}", username);
    		Criteria crit = createEntityCriteria();
    		crit.add(Restrictions.eq("username", username));
    		PersistentLogin persistentLogin = (PersistentLogin) crit.uniqueResult();
    		if (persistentLogin != null) {
    			logger.info("rememberMe was selected");
    			delete(persistentLogin);
    		}
    
    	}
    
    	@Override
    	public void updateToken(String seriesId, String tokenValue, Date lastUsed) {
    		logger.info("Updating Token for seriesId : {}", seriesId);
    		PersistentLogin persistentLogin = getByKey(seriesId);
    		persistentLogin.setToken(tokenValue);
    		persistentLogin.setLast_used(lastUsed);
    		update(persistentLogin);
    	}
    
    }
    
    上述实现使用实体[PersistentLogin]映射到persistent_logins表,如下图所示是实体本身。
    package com.yiibai.springmvc.model;
    
    import java.io.Serializable;
    import java.util.Date;
    
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.Id;
    import javax.persistence.Table;
    import javax.persistence.Temporal;
    import javax.persistence.TemporalType;
    
    @Entity
    @Table(name="PERSISTENT_LOGINS")
    public class PersistentLogin implements Serializable{
    
    	@Id
    	private String series;
    
    	@Column(name="USERNAME", unique=true, nullable=false)
    	private String username;
    	
    	@Column(name="TOKEN", unique=true, nullable=false)
    	private String token;
    	
    	@Temporal(TemporalType.TIMESTAMP)
    	private Date last_used;
    
    	public String getSeries() {
    		return series;
    	}
    
    	public void setSeries(String series) {
    		this.series = series;
    	}
    
    	public String getUsername() {
    		return username;
    	}
    
    	public void setUsername(String username) {
    		this.username = username;
    	}
    
    	public String getToken() {
    		return token;
    	}
    
    	public void setToken(String token) {
    		this.token = token;
    	}
    
    	public Date getLast_used() {
    		return last_used;
    	}
    
    	public void setLast_used(Date last_used) {
    		this.last_used = last_used;
    	}
    	
    	
    }
    
    这个 UserDetailsService 实现,在安全性配置中使用如下图所示:
    package com.yiibai.springmvc.security;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    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 org.springframework.transaction.annotation.Transactional;
    
    import com.yiibai.springmvc.model.User;
    import com.yiibai.springmvc.model.UserProfile;
    import com.yiibai.springmvc.service.UserService;
    
    
    @Service("customUserDetailsService")
    public class CustomUserDetailsService implements UserDetailsService{
    
    	static final Logger logger = LoggerFactory.getLogger(CustomUserDetailsService.class);
    	
    	@Autowired
    	private UserService userService;
    	
    	@Transactional(readOnly=true)
    	public UserDetails loadUserByUsername(String ssoId)
    			throws UsernameNotFoundException {
    		User user = userService.findBySSO(ssoId);
    		logger.info("User : {}", user);
    		if(user==null){
    			logger.info("User not found");
    			throw new UsernameNotFoundException("Username not found");
    		}
    			return new org.springframework.security.core.userdetails.User(user.getSsoId(), user.getPassword(), 
    				 true, true, true, true, getGrantedAuthorities(user));
    	}
    
    	
    	private List<GrantedAuthority> getGrantedAuthorities(User user){
    		List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
    		
    		for(UserProfile userProfile : user.getUserProfiles()){
    			logger.info("UserProfile : {}", userProfile);
    			authorities.add(new SimpleGrantedAuthority("ROLE_"+userProfile.getType()));
    		}
    		logger.info("authorities : {}", authorities);
    		return authorities;
    	}
    	
    }
    
    最后,使用下述初始化类注册 springSecurityFilter 应用程序 war。
    package com.yiibai.springmvc.security;
    
    import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
    
    public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
    
    }
    
    这就是 Spring Security 配置。现在,让我们从Spring MVC部分开始,讨论Hibernate配置,必要的DAO,模型和服务。

    第4步: 配置Hibernate

    package com.yiibai.springmvc.configuration;
    
    import java.util.Properties;
    
    import javax.sql.DataSource;
    
    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.jdbc.datasource.DriverManagerDataSource;
    import org.springframework.orm.hibernate4.HibernateTransactionManager;
    import org.springframework.orm.hibernate4.LocalSessionFactoryBean;
    import org.springframework.transaction.annotation.EnableTransactionManagement;
    
    @Configuration
    @EnableTransactionManagement
    @ComponentScan({ "com.yiibai.springmvc.configuration" })
    @PropertySource(value = { "classpath:application.properties" })
    public class HibernateConfiguration {
    
        @Autowired
        private Environment environment;
    
        @Bean
        public LocalSessionFactoryBean sessionFactory() {
            LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
            sessionFactory.setDataSource(dataSource());
            sessionFactory.setPackagesToScan(new String[] { "com.yiibai.springmvc.model" });
            sessionFactory.setHibernateProperties(hibernateProperties());
            return sessionFactory;
         }
    	
        @Bean
        public DataSource dataSource() {
            DriverManagerDataSource dataSource = new DriverManagerDataSource();
            dataSource.setDriverClassName(environment.getRequiredProperty("jdbc.driverClassName"));
            dataSource.setUrl(environment.getRequiredProperty("jdbc.url"));
            dataSource.setUsername(environment.getRequiredProperty("jdbc.username"));
            dataSource.setPassword(environment.getRequiredProperty("jdbc.password"));
            return dataSource;
        }
        
        private Properties hibernateProperties() {
            Properties properties = new Properties();
            properties.put("hibernate.dialect", environment.getRequiredProperty("hibernate.dialect"));
            properties.put("hibernate.show_sql", environment.getRequiredProperty("hibernate.show_sql"));
            properties.put("hibernate.format_sql", environment.getRequiredProperty("hibernate.format_sql"));
            return properties;        
        }
        
    	@Bean
        @Autowired
        public HibernateTransactionManager transactionManager(SessionFactory s) {
           HibernateTransactionManager txManager = new HibernateTransactionManager();
           txManager.setSessionFactory(s);
           return txManager;
        }
    }
    
    下面是这篇教程文章中使用的属性文件。

    /src/main/resources/application.properties

    jdbc.driverClassName = com.mysql.jdbc.Driver
    jdbc.url = jdbc:mysql://localhost:3306/yiibai
    jdbc.username = root
    jdbc.password = 
    hibernate.dialect = org.hibernate.dialect.MySQLDialect
    hibernate.show_sql = true
    hibernate.format_sql = true
    

    第5步:配置Spring MVC

    package com.yiibai.springmvc.configuration;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.MessageSource;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.support.ResourceBundleMessageSource;
    import org.springframework.format.FormatterRegistry;
    import org.springframework.web.servlet.config.annotation.EnableWebMvc;
    import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
    import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
    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;
    
    import com.yiibai.springmvc.converter.RoleToUserProfileConverter;
    
    
    @Configuration
    @EnableWebMvc
    @ComponentScan(basePackages = "com.yiibai.springmvc")
    public class AppConfig extends WebMvcConfigurerAdapter{
    	
    	
    	@Autowired
    	RoleToUserProfileConverter roleToUserProfileConverter;
    	
    
    	/**
         * Configure ViewResolvers to deliver preferred views.
         */
    	@Override
    	public void configureViewResolvers(ViewResolverRegistry registry) {
    
    		InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
    		viewResolver.setViewClass(JstlView.class);
    		viewResolver.setPrefix("/WEB-INF/views/");
    		viewResolver.setSuffix(".jsp");
    		registry.viewResolver(viewResolver);
    	}
    	
    	/**
         * Configure ResourceHandlers to serve static resources like CSS/ Javascript etc...
         */
        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
            registry.addResourceHandler("/static/**").addResourceLocations("/static/");
        }
        
        /**
         * Configure Converter to be used.
         * In our example, we need a converter to convert string values[Roles] to UserProfiles in newUser.jsp
         */
        @Override
        public void addFormatters(FormatterRegistry registry) {
            registry.addConverter(roleToUserProfileConverter);
        }
    	
    
        /**
         * Configure MessageSource to lookup any validation/error message in internationalized property files
         */
        @Bean
    	public MessageSource messageSource() {
    	    ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
    	    messageSource.setBasename("messages");
    	    return messageSource;
    	}
        
        /**Optional. It's only required when handling '.' in @PathVariables which otherwise ignore everything after last '.' in @PathVaidables argument.
         * It's a known bug in Spring [https://jira.spring.io/browse/SPR-6164], still present in Spring 4.1.7.
         * This is a workaround for this issue.
         */
        @Override
        public void configurePathMatch(PathMatchConfigurer matcher) {
            matcher.setUseRegisteredSuffixPatternMatch(true);
        }
    }
    
    这种配置的主要亮点是RoleToUserProfileConverter。这将需要在数据库视图中的单个USERPROFILE ID映射到实际的 UserProfile实体。
    package com.yiibai.springmvc.converter;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.core.convert.converter.Converter;
    import org.springframework.stereotype.Component;
    
    import com.yiibai.springmvc.model.UserProfile;
    import com.yiibai.springmvc.service.UserProfileService;
    
    /**
     * A converter class used in views to map id's to actual userProfile objects.
     */
    @Component
    public class RoleToUserProfileConverter implements Converter<Object, UserProfile>{
    
    	static final Logger logger = LoggerFactory.getLogger(RoleToUserProfileConverter.class);
    	
    	@Autowired
    	UserProfileService userProfileService;
    
    	/**
    	 * Gets UserProfile by Id
    	 * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
    	 */
    	public UserProfile convert(Object element) {
    		Integer id = Integer.parseInt((String)element);
    		UserProfile profile= userProfileService.findById(id);
    		logger.info("Profile : {}",profile);
    		return profile;
    	}
    	
    }
    
    由于我们使用JSR验证应用程序验证用户输入,我们已经配置在用户在验证失败情况下显示的消息。下图所示为 message.properties 文件内容:
    NotEmpty.user.firstName=First name can not be blank.
    NotEmpty.user.lastName=Last name can not be blank.
    NotEmpty.user.email=Email can not be blank.
    NotEmpty.user.password=Password can not be blank.
    NotEmpty.user.ssoId=SSO ID can not be blank.
    NotEmpty.user.userProfiles=At least one profile must be selected.
    non.unique.ssoId=SSO ID {0} already exist. Please fill in different value.
    
    最后,Spring初始化器类如下所示:
    package com.yiibai.springmvc.configuration;
    
    import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
    
    public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    
    	@Override
    	protected Class<?>[] getRootConfigClasses() {
    		return new Class[] { AppConfig.class };
    	}
     
    	@Override
    	protected Class<?>[] getServletConfigClasses() {
    		return null;
    	}
     
    	@Override
    	protected String[] getServletMappings() {
    		return new String[] { "/" };
    	}
    
    }
    

    第6步: 创建Spring控制器

    package com.yiibai.springmvc.controller;
    
    import java.util.List;
    import java.util.Locale;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.validation.Valid;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.MessageSource;
    import org.springframework.security.authentication.AuthenticationTrustResolver;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.ModelMap;
    import org.springframework.validation.BindingResult;
    import org.springframework.validation.FieldError;
    import org.springframework.web.bind.annotation.ModelAttribute;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.SessionAttributes;
    
    import com.yiibai.springmvc.model.User;
    import com.yiibai.springmvc.model.UserProfile;
    import com.yiibai.springmvc.service.UserProfileService;
    import com.yiibai.springmvc.service.UserService;
    
    
    
    @Controller
    @RequestMapping("/")
    @SessionAttributes("roles")
    public class AppController {
    
    	@Autowired
    	UserService userService;
    	
    	@Autowired
    	UserProfileService userProfileService;
    	
    	@Autowired
    	MessageSource messageSource;
    
    	@Autowired
    	PersistentTokenBasedRememberMeServices persistentTokenBasedRememberMeServices;
    	
    	@Autowired
    	AuthenticationTrustResolver authenticationTrustResolver;
    	
    	
    	/**
    	 * This method will list all existing users.
    	 */
    	@RequestMapping(value = { "/", "/list" }, method = RequestMethod.GET)
    	public String listUsers(ModelMap model) {
    
    		List<User> users = userService.findAllUsers();
    		model.addAttribute("users", users);
    		model.addAttribute("loggedinuser", getPrincipal());
    		return "userslist";
    	}
    
    	/**
    	 * This method will provide the medium to add a new user.
    	 */
    	@RequestMapping(value = { "/newuser" }, method = RequestMethod.GET)
    	public String newUser(ModelMap model) {
    		User user = new User();
    		model.addAttribute("user", user);
    		model.addAttribute("edit", false);
    		model.addAttribute("loggedinuser", getPrincipal());
    		return "registration";
    	}
    
    	/**
    	 * This method will be called on form submission, handling POST request for
    	 * saving user in database. It also validates the user input
    	 */
    	@RequestMapping(value = { "/newuser" }, method = RequestMethod.POST)
    	public String saveUser(@Valid User user, BindingResult result,
    			ModelMap model) {
    
    		if (result.hasErrors()) {
    			return "registration";
    		}
    
    		/*
    		 * Preferred way to achieve uniqueness of field [sso] should be implementing custom @Unique annotation 
    		 * and applying it on field [sso] of Model class [User].
    		 * 
    		 * Below mentioned peace of code [if block] is to demonstrate that you can fill custom errors outside the validation
    		 * framework as well while still using internationalized messages.
    		 * 
    		 */
    		if(!userService.isUserSSOUnique(user.getId(), user.getSsoId())){
    			FieldError ssoError =new FieldError("user","ssoId",messageSource.getMessage("non.unique.ssoId", new String[]{user.getSsoId()}, Locale.getDefault()));
    		    result.addError(ssoError);
    			return "registration";
    		}
    		
    		userService.saveUser(user);
    
    		model.addAttribute("success", "User " + user.getFirstName() + " "+ user.getLastName() + " registered successfully");
    		model.addAttribute("loggedinuser", getPrincipal());
    		//return "success";
    		return "registrationsuccess";
    	}
    
    
    	/**
    	 * This method will provide the medium to update an existing user.
    	 */
    	@RequestMapping(value = { "/edit-user-{ssoId}" }, method = RequestMethod.GET)
    	public String editUser(@PathVariable String ssoId, ModelMap model) {
    		User user = userService.findBySSO(ssoId);
    		model.addAttribute("user", user);
    		model.addAttribute("edit", true);
    		model.addAttribute("loggedinuser", getPrincipal());
    		return "registration";
    	}
    	
    	/**
    	 * This method will be called on form submission, handling POST request for
    	 * updating user in database. It also validates the user input
    	 */
    	@RequestMapping(value = { "/edit-user-{ssoId}" }, method = RequestMethod.POST)
    	public String updateUser(@Valid User user, BindingResult result,
    			ModelMap model, @PathVariable String ssoId) {
    
    		if (result.hasErrors()) {
    			return "registration";
    		}
    
    		/*//Uncomment below 'if block' if you WANT TO ALLOW UPDATING SSO_ID in UI which is a unique key to a User.
    		if(!userService.isUserSSOUnique(user.getId(), user.getSsoId())){
    			FieldError ssoError =new FieldError("user","ssoId",messageSource.getMessage("non.unique.ssoId", new String[]{user.getSsoId()}, Locale.getDefault()));
    		    result.addError(ssoError);
    			return "registration";
    		}*/
    
    
    		userService.updateUser(user);
    
    		model.addAttribute("success", "User " + user.getFirstName() + " "+ user.getLastName() + " updated successfully");
    		model.addAttribute("loggedinuser", getPrincipal());
    		return "registrationsuccess";
    	}
    
    	
    	/**
    	 * This method will delete an user by it's SSOID value.
    	 */
    	@RequestMapping(value = { "/delete-user-{ssoId}" }, method = RequestMethod.GET)
    	public String deleteUser(@PathVariable String ssoId) {
    		userService.deleteUserBySSO(ssoId);
    		return "redirect:/list";
    	}
    	
    
    	/**
    	 * This method will provide UserProfile list to views
    	 */
    	@ModelAttribute("roles")
    	public List<UserProfile> initializeProfiles() {
    		return userProfileService.findAll();
    	}
    	
    	/**
    	 * This method handles Access-Denied redirect.
    	 */
    	@RequestMapping(value = "/Access_Denied", method = RequestMethod.GET)
    	public String accessDeniedPage(ModelMap model) {
    		model.addAttribute("loggedinuser", getPrincipal());
    		return "accessDenied";
    	}
    
    	/**
    	 * This method handles login GET requests.
    	 * If users is already logged-in and tries to goto login page again, will be redirected to list page.
    	 */
    	@RequestMapping(value = "/login", method = RequestMethod.GET)
    	public String loginPage() {
    		if (isCurrentAuthenticationAnonymous()) {
    			return "login";
    	    } else {
    	    	return "redirect:/list";  
    	    }
    	}
    
    	/**
    	 * This method handles logout requests.
    	 * Toggle the handlers if you are RememberMe functionality is useless in your app.
    	 */
    	@RequestMapping(value="/logout", method = RequestMethod.GET)
    	public String logoutPage (HttpServletRequest request, HttpServletResponse response){
    		Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    		if (auth != null){    
    			//new SecurityContextLogoutHandler().logout(request, response, auth);
    			persistentTokenBasedRememberMeServices.logout(request, response, auth);
    			SecurityContextHolder.getContext().setAuthentication(null);
    		}
    		return "redirect:/login?logout";
    	}
    
    	/**
    	 * This method returns the principal[user-name] of logged-in user.
    	 */
    	private String getPrincipal(){
    		String userName = null;
    		Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    
    		if (principal instanceof UserDetails) {
    			userName = ((UserDetails)principal).getUsername();
    		} else {
    			userName = principal.toString();
    		}
    		return userName;
    	}
    	
    	/**
    	 * This method returns true if users is already authenticated [logged-in], else false.
    	 */
    	private boolean isCurrentAuthenticationAnonymous() {
    	    final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    	    return authenticationTrustResolver.isAnonymous(authentication);
    	}
    
    
    }
    
    这是一个很小的Spring MVC控制器。对每种方法的视图我们也提供了一些解释。

    第7步: 创建模型

    package com.yiibai.springmvc.model;
    
    import java.io.Serializable;
    import java.util.HashSet;
    import java.util.Set;
    
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.FetchType;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.JoinColumn;
    import javax.persistence.JoinTable;
    import javax.persistence.ManyToMany;
    import javax.persistence.Table;
    
    import org.hibernate.validator.constraints.NotEmpty;
    
    @Entity
    @Table(name="APP_USER")
    public class User implements Serializable{
    
    	@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
    	private Integer id;
    
    	@NotEmpty
    	@Column(name="SSO_ID", unique=true, nullable=false)
    	private String ssoId;
    	
    	@NotEmpty
    	@Column(name="PASSWORD", nullable=false)
    	private String password;
    		
    	@NotEmpty
    	@Column(name="FIRST_NAME", nullable=false)
    	private String firstName;
    
    	@NotEmpty
    	@Column(name="LAST_NAME", nullable=false)
    	private String lastName;
    
    	@NotEmpty
    	@Column(name="EMAIL", nullable=false)
    	private String email;
    
    	@NotEmpty
    	@ManyToMany(fetch = FetchType.LAZY)
    	@JoinTable(name = "APP_USER_USER_PROFILE", 
                 joinColumns = { @JoinColumn(name = "USER_ID") }, 
                 inverseJoinColumns = { @JoinColumn(name = "USER_PROFILE_ID") })
    	private Set<UserProfile> userProfiles = new HashSet<UserProfile>();
    
    	public Integer getId() {
    		return id;
    	}
    
    	public void setId(Integer id) {
    		this.id = id;
    	}
    
    	public String getSsoId() {
    		return ssoId;
    	}
    
    	public void setSsoId(String ssoId) {
    		this.ssoId = ssoId;
    	}
    
    	public String getPassword() {
    		return password;
    	}
    
    	public void setPassword(String password) {
    		this.password = password;
    	}
    
    	public String getFirstName() {
    		return firstName;
    	}
    
    	public void setFirstName(String firstName) {
    		this.firstName = firstName;
    	}
    
    	public String getLastName() {
    		return lastName;
    	}
    
    	public void setLastName(String lastName) {
    		this.lastName = lastName;
    	}
    
    	public String getEmail() {
    		return email;
    	}
    
    	public void setEmail(String email) {
    		this.email = email;
    	}
    
    	public Set<UserProfile> getUserProfiles() {
    		return userProfiles;
    	}
    
    	public void setUserProfiles(Set<UserProfile> userProfiles) {
    		this.userProfiles = userProfiles;
    	}
    
    	@Override
    	public int hashCode() {
    		final int prime = 31;
    		int result = 1;
    		result = prime * result + ((id == null) ? 0 : id.hashCode());
    		result = prime * result + ((ssoId == null) ? 0 : ssoId.hashCode());
    		return result;
    	}
    
    	@Override
    	public boolean equals(Object obj) {
    		if (this == obj)
    			return true;
    		if (obj == null)
    			return false;
    		if (!(obj instanceof User))
    			return false;
    		User other = (User) obj;
    		if (id == null) {
    			if (other.id != null)
    				return false;
    		} else if (!id.equals(other.id))
    			return false;
    		if (ssoId == null) {
    			if (other.ssoId != null)
    				return false;
    		} else if (!ssoId.equals(other.ssoId))
    			return false;
    		return true;
    	}
    
    	/*
    	 * DO-NOT-INCLUDE passwords in toString function.
    	 * It is done here just for convenience purpose.
    	 */
    	@Override
    	public String toString() {
    		return "User [id=" + id + ", ssoId=" + ssoId + ", password=" + password
    				+ ", firstName=" + firstName + ", lastName=" + lastName
    				+ ", email=" + email + "]";
    	}
    
    
    	
    }
    
    package com.yiibai.springmvc.model;
    
    import java.io.Serializable;
    
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.Table;
    
    @Entity
    @Table(name="USER_PROFILE")
    public class UserProfile implements Serializable{
    
    	@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
    	private Integer id;	
    
    	@Column(name="TYPE", length=15, unique=true, nullable=false)
    	private String type = UserProfileType.USER.getUserProfileType();
    	
    	public Integer getId() {
    		return id;
    	}
    
    	public void setId(Integer id) {
    		this.id = id;
    	}
    
    	public String getType() {
    		return type;
    	}
    
    	public void setType(String type) {
    		this.type = type;
    	}
    
    	@Override
    	public int hashCode() {
    		final int prime = 31;
    		int result = 1;
    		result = prime * result + ((id == null) ? 0 : id.hashCode());
    		result = prime * result + ((type == null) ? 0 : type.hashCode());
    		return result;
    	}
    
    	@Override
    	public boolean equals(Object obj) {
    		if (this == obj)
    			return true;
    		if (obj == null)
    			return false;
    		if (!(obj instanceof UserProfile))
    			return false;
    		UserProfile other = (UserProfile) obj;
    		if (id == null) {
    			if (other.id != null)
    				return false;
    		} else if (!id.equals(other.id))
    			return false;
    		if (type == null) {
    			if (other.type != null)
    				return false;
    		} else if (!type.equals(other.type))
    			return false;
    		return true;
    	}
    
    	@Override
    	public String toString() {
    		return "UserProfile [id=" + id + ", type=" + type + "]";
    	}
    
    
    
    
    }
    
    package com.yiibai.springmvc.model;
    
    import java.io.Serializable;
    
    public enum UserProfileType implements Serializable{
    	USER("USER"),
    	DBA("DBA"),
    	ADMIN("ADMIN");
    	
    	String userProfileType;
    	
    	private UserProfileType(String userProfileType){
    		this.userProfileType = userProfileType;
    	}
    	
    	public String getUserProfileType(){
    		return userProfileType;
    	}
    	
    }
    

    第7步: 创建DAO

    package com.yiibai.springmvc.dao;
    
    import java.io.Serializable;
    
    import java.lang.reflect.ParameterizedType;
    
    import org.hibernate.Criteria;
    import org.hibernate.Session;
    import org.hibernate.SessionFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    
    public abstract class AbstractDao<PK extends Serializable, T> {
    	
    	private final Class<T> persistentClass;
    	
    	@SuppressWarnings("unchecked")
    	public AbstractDao(){
    		this.persistentClass =(Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1];
    	}
    	
    	@Autowired
    	private SessionFactory sessionFactory;
    
    	protected Session getSession(){
    		return sessionFactory.getCurrentSession();
    	}
    
    	@SuppressWarnings("unchecked")
    	public T getByKey(PK key) {
    		return (T) getSession().get(persistentClass, key);
    	}
    
    	public void persist(T entity) {
    		getSession().persist(entity);
    	}
    
    	public void update(T entity) {
    		getSession().update(entity);
    	}
    
    	public void delete(T entity) {
    		getSession().delete(entity);
    	}
    	
    	protected Criteria createEntityCriteria(){
    		return getSession().createCriteria(persistentClass);
    	}
    
    }
    
    package com.yiibai.springmvc.dao;
    
    import java.util.List;
    
    import com.yiibai.springmvc.model.User;
    
    
    public interface UserDao {
    
    	User findById(int id);
    	
    	User findBySSO(String sso);
    	
    	void save(User user);
    	
    	void deleteBySSO(String sso);
    	
    	List<User> findAllUsers();
    
    }
    
    package com.yiibai.springmvc.dao;
    
    import java.util.List;
    
    import org.hibernate.Criteria;
    import org.hibernate.Hibernate;
    import org.hibernate.criterion.Order;
    import org.hibernate.criterion.Restrictions;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Repository;
    
    import com.yiibai.springmvc.model.User;
    
    
    
    @Repository("userDao")
    public class UserDaoImpl extends AbstractDao<Integer, User> implements UserDao {
    
    	static final Logger logger = LoggerFactory.getLogger(UserDaoImpl.class);
    	
    	public User findById(int id) {
    		User user = getByKey(id);
    		if(user!=null){
    			Hibernate.initialize(user.getUserProfiles());
    		}
    		return user;
    	}
    
    	public User findBySSO(String sso) {
    		logger.info("SSO : {}", sso);
    		Criteria crit = createEntityCriteria();
    		crit.add(Restrictions.eq("ssoId", sso));
    		User user = (User)crit.uniqueResult();
    		if(user!=null){
    			Hibernate.initialize(user.getUserProfiles());
    		}
    		return user;
    	}
    
    	@SuppressWarnings("unchecked")
    	public List<User> findAllUsers() {
    		Criteria criteria = createEntityCriteria().addOrder(Order.asc("firstName"));
    		criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);//To avoid duplicates.
    		List<User> users = (List<User>) criteria.list();
    		
    		// No need to fetch userProfiles since we are not showing them on list page. Let them lazy load. 
    		// Uncomment below lines for eagerly fetching of userProfiles if you want.
    		/*
    		for(User user : users){
    			Hibernate.initialize(user.getUserProfiles());
    		}*/
    		return users;
    	}
    
    	public void save(User user) {
    		persist(user);
    	}
    
    	public void deleteBySSO(String sso) {
    		Criteria crit = createEntityCriteria();
    		crit.add(Restrictions.eq("ssoId", sso));
    		User user = (User)crit.uniqueResult();
    		delete(user);
    	}
    
    }
    
    package com.yiibai.springmvc.dao;
    
    import java.util.List;
    
    import com.yiibai.springmvc.model.UserProfile;
    
    
    public interface UserProfileDao {
    
    	List<UserProfile> findAll();
    	
    	UserProfile findByType(String type);
    	
    	UserProfile findById(int id);
    }
    
    package com.yiibai.springmvc.dao;
    
    import java.util.List;
    
    import org.hibernate.Criteria;
    import org.hibernate.criterion.Order;
    import org.hibernate.criterion.Restrictions;
    import org.springframework.stereotype.Repository;
    
    import com.yiibai.springmvc.model.UserProfile;
    
    
    
    @Repository("userProfileDao")
    public class UserProfileDaoImpl extends AbstractDao<Integer, UserProfile>implements UserProfileDao{
    
    	public UserProfile findById(int id) {
    		return getByKey(id);
    	}
    
    	public UserProfile findByType(String type) {
    		Criteria crit = createEntityCriteria();
    		crit.add(Restrictions.eq("type", type));
    		return (UserProfile) crit.uniqueResult();
    	}
    	
    	@SuppressWarnings("unchecked")
    	public List<UserProfile> findAll(){
    		Criteria crit = createEntityCriteria();
    		crit.addOrder(Order.asc("type"));
    		return (List<UserProfile>)crit.list();
    	}
    	
    }
    

    第8步: 创建Services

    package com.yiibai.springmvc.service;
    
    import java.util.List;
    
    import com.yiibai.springmvc.model.User;
    
    
    public interface UserService {
    	
    	User findById(int id);
    	
    	User findBySSO(String sso);
    	
    	void saveUser(User user);
    	
    	void updateUser(User user);
    	
    	void deleteUserBySSO(String sso);
    
    	List<User> findAllUsers(); 
    	
    	boolean isUserSSOUnique(Integer id, String sso);
    
    }
    
    package com.yiibai.springmvc.service;
    
    import java.util.List;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import com.yiibai.springmvc.dao.UserDao;
    import com.yiibai.springmvc.model.User;
    
    
    @Service("userService")
    @Transactional
    public class UserServiceImpl implements UserService{
    
    	@Autowired
    	private UserDao dao;
    
    	@Autowired
        private PasswordEncoder passwordEncoder;
    	
    	public User findById(int id) {
    		return dao.findById(id);
    	}
    
    	public User findBySSO(String sso) {
    		User user = dao.findBySSO(sso);
    		return user;
    	}
    
    	public void saveUser(User user) {
    		user.setPassword(passwordEncoder.encode(user.getPassword()));
    		dao.save(user);
    	}
    
    	/*
    	 * Since the method is running with Transaction, No need to call hibernate update explicitly.
    	 * Just fetch the entity from db and update it with proper values within transaction.
    	 * It will be updated in db once transaction ends. 
    	 */
    	public void updateUser(User user) {
    		User entity = dao.findById(user.getId());
    		if(entity!=null){
    			entity.setSsoId(user.getSsoId());
    			if(!user.getPassword().equals(entity.getPassword())){
    				entity.setPassword(passwordEncoder.encode(user.getPassword()));
    			}
    			entity.setFirstName(user.getFirstName());
    			entity.setLastName(user.getLastName());
    			entity.setEmail(user.getEmail());
    			entity.setUserProfiles(user.getUserProfiles());
    		}
    	}
    
    	
    	public void deleteUserBySSO(String sso) {
    		dao.deleteBySSO(sso);
    	}
    
    	public List<User> findAllUsers() {
    		return dao.findAllUsers();
    	}
    
    	public boolean isUserSSOUnique(Integer id, String sso) {
    		User user = findBySSO(sso);
    		return ( user == null || ((id != null) && (user.getId() == id)));
    	}
    	
    }
    
    package com.yiibai.springmvc.service;
    
    import java.util.List;
    
    import com.yiibai.springmvc.model.UserProfile;
    
    
    public interface UserProfileService {
    
    	UserProfile findById(int id);
    
    	UserProfile findByType(String type);
    	
    	List<UserProfile> findAll();
    	
    }
    
    package com.yiibai.springmvc.service;
    
    import java.util.List;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import com.yiibai.springmvc.dao.UserProfileDao;
    import com.yiibai.springmvc.model.UserProfile;
    
    
    @Service("userProfileService")
    @Transactional
    public class UserProfileServiceImpl implements UserProfileService{
    	
    	@Autowired
    	UserProfileDao dao;
    	
    	public UserProfile findById(int id) {
    		return dao.findById(id);
    	}
    
    	public UserProfile findByType(String type){
    		return dao.findByType(type);
    	}
    
    	public List<UserProfile> findAll() {
    		return dao.findAll();
    	}
    }
    

    第9步: 创建视图

    从登录页面开始,要求输入用户名和密码,以及可选“记住我”的标志。

    WEB-INF/views/login.jsp

    <%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
    <%@ page isELIgnored="false" %>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
    <html>
    	<head>
    		<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
    		<title>Login page</title>
    		<link href="<c:url value='/static/css/bootstrap.css' />"  rel="stylesheet"></link>
    		<link href="<c:url value='/static/css/app.css' />" rel="stylesheet"></link>
    		<link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.2.0/css/font-awesome.css" />
    	</head>
    
    	<body>
    		<div id="mainWrapper">
    			<div class="login-container">
    				<div class="login-card">
    					<div class="login-form">
    						<c:url var="loginUrl" value="/login" />
    						<form action="${loginUrl}" method="post" class="form-horizontal">
    							<c:if test="${param.error != null}">
    								<div class="alert alert-danger">
    									<p>Invalid username and password.</p>
    								</div>
    							</c:if>
    							<c:if test="${param.logout != null}">
    								<div class="alert alert-success">
    									<p>You have been logged out successfully.</p>
    								</div>
    							</c:if>
    							<div class="input-group input-sm">
    								<label class="input-group-addon" for="username"><i class="fa fa-user"></i></label>
    								<input type="text" class="form-control" id="username" name="ssoId" placeholder="Enter Username" required>
    							</div>
    							<div class="input-group input-sm">
    								<label class="input-group-addon" for="password"><i class="fa fa-lock"></i></label> 
    								<input type="password" class="form-control" id="password" name="password" placeholder="Enter Password" required>
    							</div>
    							<div class="input-group input-sm">
                                  <div class="checkbox">
                                    <label><input type="checkbox" id="rememberme" name="remember-me"> Remember Me</label>  
                                  </div>
                                </div>
    							<input type="hidden" name="${_csrf.parameterName}"  value="${_csrf.token}" />
    								
    							<div class="form-actions">
    								<input type="submit"
    									class="btn btn-block btn-primary btn-default" value="Log in">
    							</div>
    						</form>
    					</div>
    				</div>
    			</div>
    		</div>
    
    	</body>
    </html> 

    当用户登录成功后,将呈现列表页面并显示现有的所有用户。要特别注意下面 Spring Security 标签的使用。添加,编辑和删除的链接/按钮的显示是基于角色,所以‘User’角色的用户不能看到它们。你可能会问:如果直接在浏览器栏输入网址呢?我们已经在 Spring Security 中配置了URL,因此无后顾之忧。

    WEB-INF/views/userslist.jsp

    <%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
    <%@ page isELIgnored="false" %>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>
    
    <html>
    
    <head>
    	<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
    	<title>Users List</title>
    	<link href="<c:url value='/static/css/bootstrap.css' />" rel="stylesheet"></link>
    	<link href="<c:url value='/static/css/app.css' />" rel="stylesheet"></link>
    </head>
    
    <body>
    	<div class="generic-container">
    		<%@include file="authheader.jsp" %>	
    		<div class="panel panel-default">
    			  <!-- Default panel contents -->
    		  	<div class="panel-heading"><span class="lead">List of Users </span></div>
    			<table class="table table-hover">
    	    		<thead>
    		      		<tr>
    				        <th>Firstname</th>
    				        <th>Lastname</th>
    				        <th>Email</th>
    				        <th>SSO ID</th>
    				        <sec:authorize access="hasRole('ADMIN') or hasRole('DBA')">
    				        	<th width="100"></th>
    				        </sec:authorize>
    				        <sec:authorize access="hasRole('ADMIN')">
    				        	<th width="100"></th>
    				        </sec:authorize>
    				        
    					</tr>
    		    	</thead>
    	    		<tbody>
    				<c:forEach items="${users}" var="user">
    					<tr>
    						<td>${user.firstName}</td>
    						<td>${user.lastName}</td>
    						<td>${user.email}</td>
    						<td>${user.ssoId}</td>
    					    <sec:authorize access="hasRole('ADMIN') or hasRole('DBA')">
    							<td><a href="<c:url value='/edit-user-${user.ssoId}' />" class="btn btn-success custom-width">edit</a></td>
    				        </sec:authorize>
    				        <sec:authorize access="hasRole('ADMIN')">
    							<td><a href="<c:url value='/delete-user-${user.ssoId}' />" class="btn btn-danger custom-width">delete</a></td>
            				</sec:authorize>
    					</tr>
    				</c:forEach>
    	    		</tbody>
    	    	</table>
    		</div>
    		<sec:authorize access="hasRole('ADMIN')">
    		 	<div class="well">
    		 		<a href="<c:url value='/newuser' />">Add New User</a>
    		 	</div>
    	 	</sec:authorize>
       	</div>
    </body>
    </html>
    
    上述页面还包含一个函有欢迎辞和注销链接的JSP页面,如下图所示:

    WEB-INF/views/authheader.jsp

    	<div class="authbar">
    		<span>Dear <strong>${loggedinuser}</strong>, Welcome to CrazyUsers.</span> <span class="floatRight"><a href="<c:url value="/logout" />">Logout</a></span>
    	</div>
    

    以“Admin”角色的用户可以添加一个新用户。下面显示出的页面它相同于注册页面。

    WEB-INF/views/registration.jsp

    <%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
    <%@ page isELIgnored="false" %>
    <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    
    <html>
    
    <head>
    	<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
    	<title>User Registration Form</title>
    	<link href="<c:url value='/static/css/bootstrap.css' />" rel="stylesheet"></link>
    	<link href="<c:url value='/static/css/app.css' />" rel="stylesheet"></link>
    </head>
    
    <body>
     	<div class="generic-container">
    		<%@include file="authheader.jsp" %>
    
    		<div class="well lead">User Registration Form</div>
    	 	<form:form method="POST" modelAttribute="user" class="form-horizontal">
    			<form:input type="hidden" path="id" id="id"/>
    			
    			<div class="row">
    				<div class="form-group col-md-12">
    					<label class="col-md-3 control-lable" for="firstName">First Name</label>
    					<div class="col-md-7">
    						<form:input type="text" path="firstName" id="firstName" class="form-control input-sm"/>
    						<div class="has-error">
    							<form:errors path="firstName" class="help-inline"/>
    						</div>
    					</div>
    				</div>
    			</div>
    	
    			<div class="row">
    				<div class="form-group col-md-12">
    					<label class="col-md-3 control-lable" for="lastName">Last Name</label>
    					<div class="col-md-7">
    						<form:input type="text" path="lastName" id="lastName" class="form-control input-sm" />
    						<div class="has-error">
    							<form:errors path="lastName" class="help-inline"/>
    						</div>
    					</div>
    				</div>
    			</div>
    	
    			<div class="row">
    				<div class="form-group col-md-12">
    					<label class="col-md-3 control-lable" for="ssoId">SSO ID</label>
    					<div class="col-md-7">
    						<c:choose>
    							<c:when test="${edit}">
    								<form:input type="text" path="ssoId" id="ssoId" class="form-control input-sm" disabled="true"/>
    							</c:when>
    							<c:otherwise>
    								<form:input type="text" path="ssoId" id="ssoId" class="form-control input-sm" />
    								<div class="has-error">
    									<form:errors path="ssoId" class="help-inline"/>
    								</div>
    							</c:otherwise>
    						</c:choose>
    					</div>
    				</div>
    			</div>
    	
    			<div class="row">
    				<div class="form-group col-md-12">
    					<label class="col-md-3 control-lable" for="password">Password</label>
    					<div class="col-md-7">
    						<form:input type="password" path="password" id="password" class="form-control input-sm" />
    						<div class="has-error">
    							<form:errors path="password" class="help-inline"/>
    						</div>
    					</div>
    				</div>
    			</div>
    	
    			<div class="row">
    				<div class="form-group col-md-12">
    					<label class="col-md-3 control-lable" for="email">Email</label>
    					<div class="col-md-7">
    						<form:input type="text" path="email" id="email" class="form-control input-sm" />
    						<div class="has-error">
    							<form:errors path="email" class="help-inline"/>
    						</div>
    					</div>
    				</div>
    			</div>
    	
    			<div class="row">
    				<div class="form-group col-md-12">
    					<label class="col-md-3 control-lable" for="userProfiles">Roles</label>
    					<div class="col-md-7">
    						<form:select path="userProfiles" items="${roles}" multiple="true" itemValue="id" itemLabel="type" class="form-control input-sm" />
    						<div class="has-error">
    							<form:errors path="userProfiles" class="help-inline"/>
    						</div>
    					</div>
    				</div>
    			</div>
    	
    			<div class="row">
    				<div class="form-actions floatRight">
    					<c:choose>
    						<c:when test="${edit}">
    							<input type="submit" value="Update" class="btn btn-primary btn-sm"/> or <a href="<c:url value='/list' />">Cancel</a>
    						</c:when>
    						<c:otherwise>
    							<input type="submit" value="Register" class="btn btn-primary btn-sm"/> or <a href="<c:url value='/list' />">Cancel</a>
    						</c:otherwise>
    					</c:choose>
    				</div>
    			</div>
    		</form:form>
    	</div>
    </body>
    </html>
    

    WEB-INF/views/registrationsuccess.jsp

    <%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
    <%@ page isELIgnored="false" %>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    
    
    <html>
    <head>
    	<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
    	<title>Registration Confirmation Page</title>
    	<link href="<c:url value='/static/css/bootstrap.css' />" rel="stylesheet"></link>
    	<link href="<c:url value='/static/css/app.css' />" rel="stylesheet"></link>
    </head>
    <body>
    	<div class="generic-container">
    		<%@include file="authheader.jsp" %>
    		
    		<div class="alert alert-success lead">
    	    	${success}
    		</div>
    		
    		<span class="well floatRight">
    			Go to <a href="<c:url value='/list' />">Users List</a>
    		</span>
    	</div>
    </body>
    
    </html>
    
    如果用户不允许访问某些URL,拒绝访问页面将显示。

    WEB-INF/views/accessDenied.jsp

    <%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
    <%@ page isELIgnored="false" %>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
    <html>
    <head>
    	<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
    	<title>AccessDenied page</title>
    </head>
    <body>
    	<div class="generic-container">
    		<div class="authbar">
    			<span>Dear <strong>${loggedinuser}</strong>, You are not authorized to access this page.</span> <span class="floatRight"><a href="<c:url value="/logout" />">Logout</a></span>
    		</div>
    	</div>
    </body>
    </html>
    
    第10步:创建和填充数据库模式
    /*All User's gets stored in APP_USER table*/
    create table APP_USER (
       id BIGINT NOT NULL AUTO_INCREMENT,
       sso_id VARCHAR(30) NOT NULL,
       password VARCHAR(100) NOT NULL,
       first_name VARCHAR(30) NOT NULL,
       last_name  VARCHAR(30) NOT NULL,
       email VARCHAR(30) NOT NULL,
       PRIMARY KEY (id),
       UNIQUE (sso_id)
    );
      
    /* USER_PROFILE table contains all possible roles */ 
    create table USER_PROFILE(
       id BIGINT NOT NULL AUTO_INCREMENT,
       type VARCHAR(30) NOT NULL,
       PRIMARY KEY (id),
       UNIQUE (type)
    );
      
    /* JOIN TABLE for MANY-TO-MANY relationship*/  
    CREATE TABLE APP_USER_USER_PROFILE (
        user_id BIGINT NOT NULL,
        user_profile_id BIGINT NOT NULL,
        PRIMARY KEY (user_id, user_profile_id),
        CONSTRAINT FK_APP_USER FOREIGN KEY (user_id) REFERENCES APP_USER (id),
        CONSTRAINT FK_USER_PROFILE FOREIGN KEY (user_profile_id) REFERENCES USER_PROFILE (id)
    );
     
    /* Populate USER_PROFILE Table */
    INSERT INTO USER_PROFILE(type)
    VALUES ('USER');
     
    INSERT INTO USER_PROFILE(type)
    VALUES ('ADMIN');
     
    INSERT INTO USER_PROFILE(type)
    VALUES ('DBA');
     
     
    /* Populate one Admin User which will further create other users for the application using GUI */
    INSERT INTO APP_USER(sso_id, password, first_name, last_name, email)
    VALUES ('sam','$2a$10$WnZOXD/FO9qZo7aMkzmr.utH/dDH19jTsqJOs2loSnkojh7dRs9cC', 'Sam','Smith','samy@yiibai.com'); /* Populate JOIN Table */
    INSERT INTO APP_USER_USER_PROFILE (user_id, user_profile_id)
      SELECT user.id, profile.id FROM app_user user, user_profile profile
      where user.sso_id='sam' and profile.type='ADMIN';
    
    /* Create persistent_logins Table used to store rememberme related stuff*/
    CREATE TABLE persistent_logins (
        username VARCHAR(64) NOT NULL,
        series VARCHAR(64) NOT NULL,
        token VARCHAR(64) NOT NULL,
        last_used TIMESTAMP NOT NULL,
        PRIMARY KEY (series)
    ); 

    请注意,我们要手动插入一个用户(我们需要一个管理员用户来登录应用程序并创建更多的用户)。T这是一个真实世界的场景。请注意,这里是加密密码:“123456” 的结果。它用下述实用类[它甚至可以是一个脚本],它仅仅是用来生成一个初始生成管理员用户的密码。它完全可以从应用程序中删除。

    package com.yiibai.springsecurity.util;
     
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
     
    public class QuickPasswordEncodingGenerator {
     
        /**
         * @param args
         */
        public static void main(String[] args) {
                String password = "123456";
                BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
                System.out.println(passwordEncoder.encode(password));
        }
     
    }
    

    第11步:构建,部署和运行应用程序

    现在构造 war(通过 eclipse/m2eclipse)或通过Maven的命令行(mvn clean install)。部署WAR文件到Servlet3.0容器。由于这里我使用的是在 eclipse 中配置 Tomcat,可以直接发布到 Tomcat 服务容器中。如果不知道怎么使用,可以参考:http://www.yiibai.com/maven/create-a-maven-web-project-with-eclipse.html

    运行应用程序

    打开浏览器并访问 - http://localhost:8080/SpringMVCHibernateWithSpringSecurity/

    使用用户名:Sam 和密码:123456 来登录,同时选择 “记住我” 。 

    现在查看数据库。应该有一条记录在:persistent_logins表中。

    但是对于 APP_USER 表,它没有任何数据上的变化 - 

    现在,单击 ‘Add new user’ 链接。添加一个 ‘USER’  角色的用户。如下图中所示 - 

    点击注册(Register),用户应该就被添加了。

    点击  ‘Users List’ 链接。您应该看到刚才新添加的用户信息了。

    添加另外一个拥有 DBA & USER 角色的用户,如下图中所示 - 

    点击“Register",现在我们再来看一下用户列表 - 

    查看验证 APP_USER 表中的数据,如下所示 - 

    现在注销登录,如下图中所示 - 

    现在查看 persistent_logins 表,登录的相关记录条目应该是被删除了。
    使用用户“will” 作为“USER”角色用户登录。它没有添加/编辑/删除这些操作链接。
    现在,注销并使用‘bob’登录。也没有添加/删除这些操作链接显示给该用户。
    <
    现在尝试手动在浏览器栏中输入删除网址URL。您应该看到拒绝访问页面。
    本文章教程到此学习完成。正如我们看到的,这是相当简单的一个使用Spring MVC整合的Spring Security。随意评论,并提出改进意见。

    下载源代码  - 13-SpringMVCHibernateWithSpringSecurity.zip

    参考

  • 相关阅读:
    mysql升级8.0之后连接报错
    电压力锅溢锅问题解决
    StarRC 转XRC flow
    2022年2月工作资料
    Python中用Ctrl+C终止多线程程序
    leetcode 方法总结
    Java8中stream
    数据结构与JAVA(JCF)的结合
    git初学
    RedBlack Tree
  • 原文地址:https://www.cnblogs.com/hyl8218/p/6349501.html
Copyright © 2020-2023  润新知