Spring Security+Hibernate記住我實例
本教程介紹了使用Spring Security與Hibernate實現"記住我"認證功能。
"記住我" 是持久登錄認證,應用程序會記住會話用戶的身份。基本上,在登錄時當你使用"記住我"功能支持,應用程序會將一個cookie在登錄成功後發送到瀏覽器。這個 cookie 將被存儲在瀏覽器端,並繼續在一定期限(由cookie生命過期時間定義)。 下一次,當您嘗試訪問該應用程序,瀏覽器將檢測到的cookie(如果仍然有效)那麼些用戶將會自動登錄,無需提供如用戶ID/密碼。
Spring Security提供了兩種 「記住我」的實現:
簡單基於散列標記的方法:它使用散列來保護基於cookie標記的安全性
持久化標記方法:它使用一個數據庫或其他持久化存儲機制來保存生成的標記
在這篇文章中,我們將討論關於持久化標記方法
相對於正常登錄教程文章作了一些以下的修改:
1.持久化標記方法,數據庫中添加一個名爲:persistent_logins 的表,可以創建使用下面的SQL:
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)
);
此表包含用戶名,「記住我」的最後一次活動時間(last_used時間戳),Bcrypt內部實現安全令牌和一系列信息。
2.在Spring Security配置"記住我"
package com.yiibai.springsecurity.configuration;
import javax.sql.DataSource;
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.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.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("customUserDetailsService")
UserDetailsService userDetailsService;
@Autowired
DataSource dataSource;
@Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.antMatchers("/admin/\*\*").access("hasRole('ADMIN')")
.antMatchers("/db/\*\*").access("hasRole('ADMIN') and hasRole('DBA')")
.and().formLogin().loginPage("/login")
.usernameParameter("ssoId").passwordParameter("password")
.and().rememberMe().rememberMeParameter("remember-me").tokenRepository(persistentTokenRepository()).tokenValiditySeconds(86400)
.and().csrf()
.and().exceptionHandling().accessDeniedPage("/Access\_Denied");
}
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl tokenRepositoryImpl = new JdbcTokenRepositoryImpl();
tokenRepositoryImpl.setDataSource(dataSource);
return tokenRepositoryImpl;
}
}
請注意,我們可調用 rememberMe() 來配置記住我,在視圖中提供一個「記住我」複選框身份驗證的HTTP參數名稱(我們將在視圖中看到)。我們還指定要使用 tokenRepository(令牌將被存儲),並將該令牌的時間(以秒爲單位)仍然有效。要配置存儲庫時我們要注入了一個數據源。
這是所有你需要激活"記住我",在Spring Security基礎的應用程序中。
或者,可以使用 Spring Security內置表達以及Spring security標籤在您的視圖根據特定的邏輯定製/顯示/隱藏 "記住我"或使用安全認證。
注意:如果你不願意使用JDBC,可能在應用程序是基於Hibernate,現在你可以參考 Spring MVC+Spring Security+hibernate實例,在這裏我創建了一個基於Hibernate實現PersistentTokenRepository,它被命名爲 HibernateTokenRepositoryImpl。
以上安全配置以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"/>
</authentication-manager>
<remember-me
remember-me-parameter="remember-me"
remember-me-cookie="remember-me"
token-validity-seconds="86400"
data-source-ref="dataSource" />
<beans:bean id="customUserDetailsService" class="com.yiibai.springsecurity.service.CustomUserDetailsService" />
完整的代碼示例如下所示。
完整的示例
需要使用到以下技術:
- Spring 4.1.6.RELEASE
- Spring Security 4.0.1.RELEASE
- Hibernate 4.3.6.Final
- MySQL Server 5.6
- Maven 3
- JDK 1.7
- Tomcat 8.0.21
- Eclipse JUNO Service Release 2
現在就讓我們開始吧!
第1步: 工程目錄結構
這時是最終的項目結構:
第2步:更新 pom.xml 以包括所需的相關性
<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.version>5.1.31</mysql.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>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.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>SpringSecurityRememberMeAnnotationExample</warName>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<finalName>SpringSecurityRememberMeAnnotationExample</finalName>
</build>
數據庫表部分
第3步:創建數據庫模式並填充數據
/* For Remember-Me token storage purpose */
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)
);
/*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. We need only one user to demonstrate this example. You can add more as done in previous posts*/
INSERT INTO APP_USER(sso_id, password, first_name, last_name, email, state)
VALUES ('sam','abc125', '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';
安全部分
第4步:添加 Spring Security 配置類
package com.yiibai.springsecurity.configuration;
import javax.sql.DataSource;
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.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.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("customUserDetailsService")
UserDetailsService userDetailsService;
@Autowired
DataSource dataSource;
@Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.antMatchers("/admin/\*\*").access("hasRole('ADMIN')")
.antMatchers("/db/\*\*").access("hasRole('ADMIN') and hasRole('DBA')")
.and().formLogin().loginPage("/login")
.usernameParameter("ssoId").passwordParameter("password")
.and().rememberMe().rememberMeParameter("remember-me").tokenRepository(persistentTokenRepository()).tokenValiditySeconds(86400)
.and().csrf()
.and().exceptionHandling().accessDeniedPage("/Access\_Denied");
}
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl tokenRepositoryImpl = new JdbcTokenRepositoryImpl();
tokenRepositoryImpl.setDataSource(dataSource);
return tokenRepositoryImpl;
}
}
第5步:使用war註冊springSecurityFilter
下面指定的初始化類註冊 springSecurityFilter 與應用程序 war [步驟3中創建]。
package com.yiibai.springsecurity.configuration;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
}
以上配置以XML配置格式爲:
第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 javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class HelloWorldController {
@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";
}
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;
}
}
第8步: 添加 SpringMVC 配置類
package com.yiibai.springsecurity.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.yiibai.springsecurity")
public class HelloWorldConfiguration extends WebMvcConfigurerAdapter {
@Bean(name="HelloWorld")
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
/\*
\* Configure ResourceHandlers to serve static resources like CSS/ Javascript etc...
\*
\*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/\*\*").addResourceLocations("/static/");
}
}
第9步: 添加初始化類
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配置部分
第10步: 創建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 = myuser
jdbc.password = mypassword
hibernate.dialect = org.hibernate.dialect.MySQLDialect
hibernate.show_sql = true
hibernate.format_sql = true
DAO, Model & Service服務
第11步: 創建模型類
用戶可以有多個角色[DBA,ADMIN,USER]。而角色可以被分配給一個以上的用戶。因此,有一個用戶和用戶配置[角色]之間存在多對多的關係。我們保持這種關係單向[用戶到用戶配置],因爲我們只是在查找對於給用戶的角色。我們將使用使用連接表來實現多一對多關聯。
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;
@Entity
@Table(name="APP_USER")
public class User {
@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
@Column(name="SSO\_ID", unique=true, nullable=false)
private String ssoId;
@Column(name="PASSWORD", nullable=false)
private String password;
@Column(name="FIRST\_NAME", nullable=false)
private String firstName;
@Column(name="LAST\_NAME", nullable=false)
private String lastName;
@Column(name="EMAIL", nullable=false)
private String email;
@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();
}
}
第12步: 創建 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 {
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 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();
}
}
第13步: 創建Service層
package com.yiibai.springsecurity.service;
import com.yiibai.springsecurity.model.User;
public interface UserService {
User findById(int id);
User findBySso(String sso);
}
package com.yiibai.springsecurity.service;
import org.springframework.beans.factory.annotation.Autowired;
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;
public User findById(int id) {
return dao.findById(id);
}
public User findBySso(String sso) {
return dao.findBySSO(sso);
}
}
視圖部分
第14步:添加視圖
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"%>
<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>
正如你所看到的,CSRF參數需要在JSP中的EL表達式訪問,所以還需要通過添將以下的代碼添加JSP的頂部來強行執行EL表達式解析編譯:
<%@ page isELIgnored="false"%>
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"%>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>
<sec:authorize access="isFullyAuthenticated()">
<label><a href="#">Create New User</a> | <a href="#">View existing Users</a></label>
</sec:authorize>
<sec:authorize access="isRememberMe()">
<label><a href="#">View existing Users</a></label>
</sec:authorize>
<a href="<c:url value="/logout" />">Logout</a>
以下提到的視圖在這個帖子中不需要,只是在這裏提及,作爲一個完整的補充。
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"%>
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"%>
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"%>
第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/SpringSecurityRememberMeAnnotation/admin
系統將提示您登錄,如下圖中所示 -
使用"記住我" 驗證登錄(使用用戶:kenny/123456),您將獲得管理員視圖,如下所示 -
驗證查看 Cookies。打開谷歌瀏覽器設置/顯示高級設置/內容設置/所有Cookie和網站數據顯示本地主機。你會發現有兩個 cookies ,一個用於當前會話[JSESSIONID],一個用於"記住我"的身份驗證。
點擊"記住我"(remember-me),查看cookie詳細信息。你可以看到 cookie 的有效期是一天(如在安全配置設置)。
現在查看 persistent_logins 表中數據。應該有當前用戶的 Cookies 記錄信息。
現在退出登錄,如下圖中所示 -
現在再試嘗試打開:http://localhost:8080/SpringSecurityRememberMeAnnotation/admin,應該直接登錄,而不再需要使用輸入用戶名和密碼登錄。
還可以看到 「Create New User」 選項不可用,這是因爲一個是僅當用戶(使用登錄名/密碼,例如)有權限的認證才能使用此功能,現在從Chrome中刪除 "記住我" 的cookie(如前所示),並再次嘗試訪問: http://localhost:8080/SpringSecurityRememberMeAnnotation/admin,程序應該提示您登錄。
就這麼多, 下一篇文章我們將來學習使用 Spring Security 的 @PreAuthorize,@PostAuthorize,@Secured和Spring EL表達式的方法級的安全控制。
下載源代碼
11-SpringSecurityRememberMeAnnotation.zip