Spring MVC4 + Spring Security4 + Hibernate實例

在這篇教程文章中,我們將使用Spring Security,Hibernate+MySQL數據庫來集成構建一個成熟的Spring MVC應用程序。處理多對多映射關係,同時利用BCrypt格式加密密碼存儲,和使用自定義PersistentTokenRepository實現Hibernate HibernateTokenRepositoryImpl並提供了 "記住我" 功能,從數據庫檢索和更新記錄或在事務中刪除它們;在這個實例中全部採用註釋配置。

這個項目可以作爲您自己的Spring MVC項目集成 Spring Security 模板,免受搭建開發環境皮肉之苦。
Spring
Spring

注意:

這篇文章展示了一個完整的應用的完整代碼。爲了縮小本教程文章的篇幅,這裏需要省略一些基本的知識點文字描述。如果您有興趣瞭解這些細節,這個這個這個文章可能會幫助你。

先來做個概括:

這個項目顯示了一個簡單的用戶管理應用程序。您可以創建一個新用戶,編輯或刪除現有用戶,並列出所有用戶信息列表。一個用戶可以與一個或多個用戶配置(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步:創建目錄結構

以下將是最終的項目結構:
Spring
Spring

現在,讓我們解釋在上面提到的結構內容每個細節。

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

<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>

第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"%>

Login page
<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>
 

當用戶登錄成功後,將呈現列表頁面並顯示現有的所有用戶。要特別注意下面 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"%>

Users List
<%@include file="authheader.jsp" %>
List of Users
                </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>

上述頁面還包含一個函有歡迎辭和註銷鏈接的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" %>

User Registration Form
<%@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>

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" %>

Registration Confirmation Page
<%@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>

如果用戶不允許訪問某些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"%>

AccessDenied page
Dear ${loggedinuser}, You are not authorized to access this page. ">Logout

第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','[email protected]'); /* 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/
Spring

使用用戶名:Sam 和密碼:123456 來登錄,同時選擇 「記住我」 。 
Spring
Spring

現在查看數據庫。應該有一條記錄在:persistent_logins表中。
Spring

但是對於 APP_USER 表,它沒有任何數據上的變化 - 
Spring

現在,單擊 ‘Add new user’ 鏈接。添加一個 ‘USER’  角色的用戶。如下圖中所示 - 
Spring

點擊註冊(Register),用戶應該就被添加了。
Spring

點擊  ‘Users List’ 鏈接。您應該看到剛纔新添加的用戶信息了。
Spring

添加另外一個擁有 DBA & USER 角色的用戶,如下圖中所示 - 
Spring

點擊「Register",現在我們再來看一下用戶列表 - 
Spring

查看驗證 APP_USER 表中的數據,如下所示 - 
Spring

現在註銷登錄,如下圖中所示 - 
Spring

現在查看 persistent_logins 表,登錄的相關記錄條目應該是被刪除了。
Spring

使用用戶「will」 作爲「USER」角色用戶登錄。它沒有添加/編輯/刪除這些操作鏈接。
Spring

現在,註銷並使用‘bob’登錄。也沒有添加/刪除這些操作鏈接顯示給該用戶。
Spring

<

現在嘗試手動在瀏覽器欄中輸入刪除網址URL。您應該看到拒絕訪問頁面。

本文章教程到此學習完成。正如我們看到的,這是相當簡單的一個使用Spring MVC整合的Spring Security。隨意評論,並提出改進意見。

下載源代碼  - 13-SpringMVCHibernateWithSpringSecurity.zip

參考


Copyright © 2015-2023 億聚網.
Firstname Lastname Email SSO ID