Spring Security+Hibernate密碼編碼器Bcrypt實例

本教程介紹了在 Spring Security 使用 BCryptPasswordEncoder 來作密碼編碼。我們將使用 Spring MVC 4,Hibernate 4 & Spring Security 4 的一個實例來說明一個真實世界的設置涉及登錄認證和用戶創建。

基於註解 + XML這兩個項目的代碼,可這篇文章的結尾下載。

密碼編碼的字符

任何應用程序,這需要認真對待安全問題,千萬不要以純文本格式來存儲密碼。密碼應始終使用安全散列算法進行編碼。有許多標準算法如:SHA或MD5,這其中要一個適當的 SALT 字符串相結合,可爲密碼編碼提供一個不錯的選擇。Spring Security提供BCryptPasswordEncoder,並實現了 Spring 的 PasswordEncoder 接口,從而使用 BCrypt 強散列函數對密碼進行加密編碼。

需要在應用程序中的什麼地方進行密碼編碼?

1. 在密碼比較過程中。輸入密碼經過編輯加密與存儲在數據庫中密碼(它是經過編碼的)進行比較;

2. 在新用戶創建或現有用戶密碼需要更新。在保存或更新數據庫之前將輸入新密碼進行加密編碼; 

與之前的文章有哪些是變化的?

1. 創建和注入 PasswordEncoder 到 AuthenticationProvider並設置作爲身份驗證提供者在 AuthenticationManagerBuilder 

package com.yiibai.springsecurity.configuration;

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

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

@Autowired
@Qualifier("customUserDetailsService")
UserDetailsService userDetailsService;


@Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService);
    auth.authenticationProvider(authenticationProvider());
}


@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}


@Bean
public DaoAuthenticationProvider authenticationProvider() {
    DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
    authenticationProvider.setUserDetailsService(userDetailsService);
    authenticationProvider.setPasswordEncoder(passwordEncoder());
    return authenticationProvider;
}

@Override
protected void configure(HttpSecurity http) throws Exception {
  http.authorizeRequests()
      .antMatchers("/", "/home").permitAll()
      .antMatchers("/admin/\*\*","/newuser").access("hasRole('ADMIN')")
      .antMatchers("/db/\*\*").access("hasRole('ADMIN') and hasRole('DBA')")
      .and().formLogin().loginPage("/login")
      .usernameParameter("ssoId").passwordParameter("password")
      .and().csrf()
      .and().exceptionHandling().accessDeniedPage("/Access\_Denied");
}

}

上面的設置可在應用程序的任何地方來處理密碼比較認證過程。

以上安全配置以XML配置格式表示如下:

<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.0.xsd">

<http auto-config="true" >
    <intercept-url pattern="/" access="permitAll" />
    <intercept-url pattern="/home" access="permitAll" />
    <intercept-url pattern="/admin\*\*" access="hasRole('ADMIN')" />
    <intercept-url pattern="/dba\*\*" access="hasRole('ADMIN') and hasRole('DBA')" />
    <form-login  login-page="/login" 
                 username-parameter="ssoId" 
                 password-parameter="password" 
                 authentication-failure-url="/Access\_Denied" />
    <csrf/>
</http>

<authentication-manager >
    <authentication-provider user-service-ref="customUserDetailsService">
        <password-encoder ref="bcryptEncoder"/>
    </authentication-provider>
</authentication-manager>

<beans:bean id="bcryptEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />

<beans:bean id="customUserDetailsService" class="com.yiibai.springsecurity.service.CustomUserDetailsService" />

2. 更新 UserService 讓它實現在保存新的口令到數據庫中之前進行密碼編碼加密。

@Service("userService")
@Transactional
public class UserServiceImpl implements UserService{

@Autowired
private UserDao dao;

@Autowired
private PasswordEncoder passwordEncoder;


public void save(User user){
    user.setPassword(passwordEncoder.encode(user.getPassword()));
    dao.save(user);
}

public User findById(int id) {
    return dao.findById(id);
}

public User findBySso(String sso) {
    return dao.findBySSO(sso);
}

}

需要做的是在應用程序中使用 Spring Security BCrypt 來實現您的密碼編碼。

完整的實例

使用以下技術:

[blockquote]

  • Spring 4.1.6.RELEASE
  • Spring Security 4.0.1.RELEASE
  • Hibernate 4.3.6.Final
  • MySQL Server 5.6
  • Maven 3
  • JDK 1.8
  • Tomcat 8.0.21
  • Eclipse JUNO Service Release 2

[/blockquote]

第1步: 工程目錄結構

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

第2步:更新pom.xml,包括所需的依懶


4.0.0

<groupId>com.yiibai.springsecurity</groupId>
<artifactId>SpringSecurityPasswordEncodingWithBcryptExample</artifactId>
<version>1.0.0</version>
<packaging>war</packaging>

<name>SpringSecurityPasswordEncodingWithBcryptExample</name>

<properties>
    <springframework.version>4.1.6.RELEASE</springframework.version>
    <springsecurity.version>4.0.1.RELEASE</springsecurity.version>
    <hibernate.version>4.3.6.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>

    <!-- 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>
    <!-- Hibernate validators -->
    <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>

    <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>SpringSecurityPasswordEncodingWithBcryptExample</warName>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </pluginManagement>
    <finalName>SpringSecurityPasswordEncodingWithBcryptExample</finalName>
</build>

數據庫表部分

第3步:創建數據庫模式並填充數據

/*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,
state 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, state)
VALUES ('sam','$2a$10$6e2mmsbKPVMRv1zCUTxcS.k2wPxqaXc6.wseLpYBB8qzfIMmKimBK', 'Sam','Smith','[email protected]', 'Active'); /* 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';  

請注意,這裏我們已經手動插入一個用戶(我們還得需要一個管理員用戶並登錄以及使用應用程序來創建更多的用戶)。這是一個真實的應用場景。需要注意一下密碼。它用下述工具類[它甚至可以是一個腳本],僅僅用來生成一個管理員用戶的初始密碼生成。

它完全可以從應用程序中刪除。

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 = "abc125";
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        System.out.println(passwordEncoder.encode(password));
}

}

上面的程序將使用上述模式產生編碼加密密碼。請注意 BCrypt散列算法生成的每個密碼編碼是一個長度爲 60 的哈希值,同樣的密碼可能會得到不同的值。

Security(安全)部分

第4步: 添加Spring Security配置類

package com.yiibai.springsecurity.configuration;

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

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

@Autowired
@Qualifier("customUserDetailsService")
UserDetailsService userDetailsService;


@Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService);
    auth.authenticationProvider(authenticationProvider());
}


@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}


@Bean
public DaoAuthenticationProvider authenticationProvider() {
    DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
    authenticationProvider.setUserDetailsService(userDetailsService);
    authenticationProvider.setPasswordEncoder(passwordEncoder());
    return authenticationProvider;
}

@Override
protected void configure(HttpSecurity http) throws Exception {
  http.authorizeRequests()
      .antMatchers("/", "/home").permitAll()
      .antMatchers("/admin/\*\*","/newuser").access("hasRole('ADMIN')")
      .antMatchers("/db/\*\*").access("hasRole('ADMIN') and hasRole('DBA')")
      .and().formLogin().loginPage("/login")
      .usernameParameter("ssoId").passwordParameter("password")
      .and().csrf()
      .and().exceptionHandling().accessDeniedPage("/Access\_Denied");
}

}

第5步: 使用 war 註冊 springSecurityFilter

下面指定的初始化類應用程序的 war 註冊 springSecurityFilter [第 3 步中創建的]。

package com.yiibai.springsecurity.configuration;

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

public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {

}

以上對應的XML配置格式的配置是:

springSecurityFilterChain org.springframework.web.filter.DelegatingFilterProxy springSecurityFilterChain /\*

第6步: 定義UserDetailsService實現

這個服務是負責提供身份驗證細節到驗證管理。

package com.yiibai.springsecurity.service;

import java.util.ArrayList;
import java.util.List;

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.springsecurity.model.User;
import com.yiibai.springsecurity.model.UserProfile;

@Service("customUserDetailsService")
public class CustomUserDetailsService implements UserDetailsService{

@Autowired
private UserService userService;

@Transactional(readOnly=true)
public UserDetails loadUserByUsername(String ssoId)
        throws UsernameNotFoundException {
    User user = userService.findBySso(ssoId);
    System.out.println("User : "+user);
    if(user==null){
        System.out.println("User not found");
        throw new UsernameNotFoundException("Username not found"); 
    }
        return new org.springframework.security.core.userdetails.User(user.getSsoId(), user.getPassword(), 
             user.getState().equals("Active"), true, true, true, getGrantedAuthorities(user));
}


private List<GrantedAuthority> getGrantedAuthorities(User user){
    List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();

    for(UserProfile userProfile : user.getUserProfiles()){
        System.out.println("UserProfile : "+userProfile);
        authorities.add(new SimpleGrantedAuthority("ROLE\_"+userProfile.getType()));
    }
    System.out.print("authorities :"+authorities);
    return authorities;
}

}

SpringMVC部分

第7步: 添加控制器

package com.yiibai.springsecurity.controller;

import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
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.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.yiibai.springsecurity.model.User;
import com.yiibai.springsecurity.model.UserProfile;
import com.yiibai.springsecurity.service.UserProfileService;
import com.yiibai.springsecurity.service.UserService;

@Controller
public class HelloWorldController {

@Autowired
UserProfileService userProfileService;

@Autowired
UserService userService;

@RequestMapping(value = { "/", "/home" }, method = RequestMethod.GET)
public String homePage(ModelMap model) {
    model.addAttribute("greeting", "Hi, Welcome to mysite");
    return "welcome";
}

@RequestMapping(value = "/admin", method = RequestMethod.GET)
public String adminPage(ModelMap model) {
    model.addAttribute("user", getPrincipal());
    return "admin";
}

@RequestMapping(value = "/db", method = RequestMethod.GET)
public String dbaPage(ModelMap model) {
    model.addAttribute("user", getPrincipal());
    return "dba";
}

@RequestMapping(value = "/Access\_Denied", method = RequestMethod.GET)
public String accessDeniedPage(ModelMap model) {
    model.addAttribute("user", getPrincipal());
    return "accessDenied";
}

@RequestMapping(value = "/login", method = RequestMethod.GET)
public String loginPage() {
    return "login";
}

@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);
    }
    return "redirect:/login?logout";
}


@RequestMapping(value = "/newUser", method = RequestMethod.GET)
public String newRegistration(ModelMap model) {
    User user = new User();
    model.addAttribute("user", user);
    return "newuser";
}

/\*
 \* This method will be called on form submission, handling POST request It
 \* also validates the user input
 \*/
@RequestMapping(value = "/newUser", method = RequestMethod.POST)
public String saveRegistration(@Valid User user,
        BindingResult result, ModelMap model) {

    if (result.hasErrors()) {
        System.out.println("There are errors");
        return "newuser";
    }
    userService.save(user);

    System.out.println("First Name : "+user.getFirstName());
    System.out.println("Last Name : "+user.getLastName());
    System.out.println("SSO ID : "+user.getSsoId());
    System.out.println("Password : "+user.getPassword());
    System.out.println("Email : "+user.getEmail());
    System.out.println("Checking UsrProfiles....");
    if(user.getUserProfiles()!=null){
        for(UserProfile profile : user.getUserProfiles()){
            System.out.println("Profile : "+ profile.getType());
        }
    }

    model.addAttribute("success", "User " + user.getFirstName() + " has been registered successfully");
    return "registrationsuccess";
}




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



@ModelAttribute("roles")
public List<UserProfile> initializeProfiles() {
    return userProfileService.findAll();
}

}

第8步: 添加SpringMVC配置類

package com.yiibai.springsecurity.configuration;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
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;

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.yiibai.springsecurity")
public class HelloWorldConfiguration extends WebMvcConfigurerAdapter {

@Autowired
RoleToUserProfileConverter roleToUserProfileConverter;


@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);
}

}

這個類負責註冊轉換器並將ID轉換成一個對象。這是必需的,以處理JSP中的一對多的關係。在用戶創造過程中,用戶可以分配多個角色/UserProfiles,所以我們需要一個轉換到一個特定的角色/UserProfiles映射到基於配置文件ID的用戶。轉換器類如下所示。

上面的設置轉換爲XML配置如下圖中所示 - 

<mvc:annotation-driven conversion-service="conversionService"/>

<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">

    <property name="converters">
        <list>
            <bean id="roleToUserProfile" class="com.yiibai.springsecurity.configuration.RoleToUserProfileConverter" />
        </list>
    </property>
</bean>

第9步: 添加SpringMVC轉換器類

package com.yiibai.springsecurity.configuration;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;

import com.yiibai.springsecurity.model.UserProfile;
import com.yiibai.springsecurity.service.UserProfileService;

@Component
public class RoleToUserProfileConverter implements Converter<Object, UserProfile>{

@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);
    System.out.println("Profile : "+profile);
    return profile;
}

/\*
 \* Gets UserProfile by type
 \* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
 \*/
/\*
public UserProfile convert(Object element) {
    String type = (String)element;
    UserProfile profile= userProfileService.findByType(type);
    System.out.println("Profile ... : "+profile);
    return profile;
}
\*/

}

第10步: 添加初始化類

package com.yiibai.springsecurity.configuration;

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

public class SpringMvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

@Override
protected Class<?>\[\] getRootConfigClasses() {
    return new Class\[\] { HelloWorldConfiguration.class };
}

@Override
protected Class<?>\[\] getServletConfigClasses() {
    return null;
}

@Override
protected String\[\] getServletMappings() {
    return new String\[\] { "/" };
}

}

Hibernate配置部分

第11步: 創建Hibernate配置

Hibernate的配置類包含數據源層,SessionFactory和事務管理的 @Bean 方法。數據源屬性是取自 application.properties 文件,包含MySQL數據庫連接詳細信息。

package com.yiibai.springsecurity.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.springsecurity.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.springsecurity.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;
}

}

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

DAO, Model & Service部分

第12步: 創建模型類

用戶可以有多個角色[DBA,ADMIN,USER]。而角色可以被分配給一個以上的用戶。因此,一個用戶和用戶配置[角色]之間存在多對多的關係。我們保持這種關係單向[User 到 UserProfile],因爲我們只是在尋找指定用戶的角色(而不反之亦然)。我們將使用連接(Join)表來實現多對多關聯。

package com.yiibai.springsecurity.model;

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 {

@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
private int 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
@Column(name="STATE", nullable=false)
private String state=State.ACTIVE.getState();

@ManyToMany(fetch = FetchType.EAGER)
@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 int getId() {
    return id;
}

public void setId(int 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 String getState() {
    return state;
}

public void setState(String state) {
    this.state = state;
}

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;
    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 != other.id)
        return false;
    if (ssoId == null) {
        if (other.ssoId != null)
            return false;
    } else if (!ssoId.equals(other.ssoId))
        return false;
    return true;
}

@Override
public String toString() {
    return "User \[id=" + id + ", ssoId=" + ssoId + ", password=" + password
            + ", firstName=" + firstName + ", lastName=" + lastName
            + ", email=" + email + ", state=" + state + ", userProfiles=" + userProfiles +"\]";
}

}

package com.yiibai.springsecurity.model;

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 {

@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;    

@Column(name="TYPE", length=15, unique=true, nullable=false)
private String type = UserProfileType.USER.getUserProfileType();

public int getId() {
    return id;
}

public void setId(int 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;
    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 != 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.springsecurity.model;

public enum UserProfileType {
USER("USER"),
DBA("DBA"),
ADMIN("ADMIN");

String userProfileType;

private UserProfileType(String userProfileType){
    this.userProfileType = userProfileType;
}

public String getUserProfileType(){
    return userProfileType;
}

}

package com.yiibai.springsecurity.model;

public enum State {

ACTIVE("Active"),
INACTIVE("Inactive"),
DELETED("Deleted"),
LOCKED("Locked");

private String state;

private State(final String state){
    this.state = state;
}

public String getState(){
    return this.state;
}

@Override
public String toString(){
    return this.state;
}


public String getName(){
    return this.name();
}

}

第13步: 創建Dao層

package com.yiibai.springsecurity.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 delete(T entity) {
    getSession().delete(entity);
}

protected Criteria createEntityCriteria(){
    return getSession().createCriteria(persistentClass);
}

}

package com.yiibai.springsecurity.dao;

import com.yiibai.springsecurity.model.User;

public interface UserDao {

void save(User user);

User findById(int id);

User findBySSO(String sso);

}

package com.yiibai.springsecurity.dao;

import org.hibernate.Criteria;
import org.hibernate.criterion.Restrictions;
import org.springframework.stereotype.Repository;

import com.yiibai.springsecurity.model.User;

@Repository("userDao")
public class UserDaoImpl extends AbstractDao<Integer, User> implements UserDao {

public void save(User user) {
    persist(user);
}

public User findById(int id) {
    return getByKey(id);
}

public User findBySSO(String sso) {
    Criteria crit = createEntityCriteria();
    crit.add(Restrictions.eq("ssoId", sso));
    return (User) crit.uniqueResult();
}

}

package com.yiibai.springsecurity.dao;

import java.util.List;

import com.yiibai.springsecurity.model.UserProfile;

public interface UserProfileDao {

List<UserProfile> findAll();

UserProfile findByType(String type);

UserProfile findById(int id);

}

package com.yiibai.springsecurity.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.springsecurity.model.UserProfile;

@Repository("userProfileDao")
public class UserProfileDaoImpl extends AbstractDao<Integer, UserProfile>implements UserProfileDao{

@SuppressWarnings("unchecked")
public List<UserProfile> findAll(){
    Criteria crit = createEntityCriteria();
    crit.addOrder(Order.asc("type"));
    return (List<UserProfile>)crit.list();
}

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();
}

}

第14步: 創建Service層

package com.yiibai.springsecurity.service;

import java.util.List;

import com.yiibai.springsecurity.model.UserProfile;

public interface UserProfileService {

List<UserProfile> findAll();

UserProfile findByType(String type);

UserProfile findById(int id);

}

package com.yiibai.springsecurity.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.springsecurity.dao.UserProfileDao;
import com.yiibai.springsecurity.model.UserProfile;

@Service("userProfileService")
@Transactional
public class UserProfileServiceImpl implements UserProfileService{

@Autowired
UserProfileDao dao;

public List<UserProfile> findAll() {
    return dao.findAll();
}

public UserProfile findByType(String type){
    return dao.findByType(type);
}

public UserProfile findById(int id) {
    return dao.findById(id);
}

}

package com.yiibai.springsecurity.service;

import com.yiibai.springsecurity.model.User;

public interface UserService {

void save(User user);

User findById(int id);

User findBySso(String sso);

}

package com.yiibai.springsecurity.service;

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.springsecurity.dao.UserDao;
import com.yiibai.springsecurity.model.User;

@Service("userService")
@Transactional
public class UserServiceImpl implements UserService{

@Autowired
private UserDao dao;

@Autowired
private PasswordEncoder passwordEncoder;


public void save(User user){
    user.setPassword(passwordEncoder.encode(user.getPassword()));
    dao.save(user);
}

public User findById(int id) {
    return dao.findById(id);
}

public User findBySso(String sso) {
    return dao.findBySSO(sso);
}

}

視圖部分

第15步: 添加視圖

login.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

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

正如你所看到的,CSRF參數需要在JSP中的EL表達式訪問,所以還需要通過添將以下的代碼添加JSP的頂部來強行執行EL表達式解析編譯:

<%@ page isELIgnored="false"%>

welcome.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

Welcome page
Greeting : ${greeting} This is a welcome page.

admin.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

Admin page
Dear ${user}, Welcome to Admin Page.
Would you like to Add Some Users to keep yourself busy?
">Logout

dba.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

DBA page
Dear ${user}, Welcome to DBA Page.
">Logout

newuser.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

User Registration Form
 <div class="form-container">

 <h1>New User Registration Form</h1>

<form:form method="POST" modelAttribute="user" class="form-horizontal">

    <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">
                <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>
            </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">
            <input type="submit" value="Register" class="btn btn-primary btn-sm"> or <a href="<c:url value='/admin' />">Cancel</a>
        </div>
    </div>
</form:form>
</div>

registrationsuccess.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

User Registration Form
Confirmation message : ${success}
Would you like to Add More Users?
Go to Admin Page OR ">Logout

accessDenied.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

AccessDenied page Dear ${user}, You are not authorized to access this page.
">Go to home OR ">Logout

第16步 - 構建和部署應用程序

現在構造 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/SpringSecurityPasswordEncodingWithBcrypt/

Spring

現在嘗試訪問本地主機: http://localhost:8080/SpringSecurityPasswordEncodingWithBcrypt/admin,系統將提示您進行登錄,提供管理員角色憑據(sam,abc123)(在這一刻僅有的系統用戶)。

Spring

提交後,如下所示 - 

Spring

點擊 "Add Some Users" 鏈接,如下所示 - 

Spring

添加一個名爲:Bill 的用戶[密碼:abc123],選擇 USER  作爲用戶角色,如下圖所示 - 

Spring

提交後,如下所示 - 
Spring

我們再次點擊 "Add Some Users" 鏈接,  填寫一個用戶:kenny [密碼 : abc125] , 選擇 ADMIN,DBA 作爲此用戶的角色,如下圖中所示 - 
Spring

提交後,如下圖中所示 - 
Spring

點擊註銷。添加 DBA 用戶信息(kenny,abc123)並提交,如在上一步中創建的一樣,現在使用 kenny 用戶名來登錄系統 - 
Spring

提交後,現在訪問 - http://localhost:8080/SpringSecurityPasswordEncodingWithBcrypt/db
Spring

最後,我們註銷登錄,如下圖所示 - 
Spring

查看數據庫表的記錄信息,如下圖所示 - 

Spring
Spring
Spring

到這裏整個教程學習完成,在 下一篇文章中我們將學習如何使用 Spring Security 以及 Hibernate 來實現 "記住我" 的認證。

下載源代碼

基於註釋實例 - 10.1-SpringSecurityPasswordEncodingWithBcrypt.zip

基於XML實例 - 10.2-SpringSecurityPasswordEncodingWithBcryptXML.zip

參考