- 演示工具版本
- 项目结构
- Maven
- 使用 Thymeleaf 模板引擎
- 使用静态内容
- 使用 JS 和 CSS 文件
- 使用自定义 favicon
- 使用 application.properties 配置数据库
- MySQL 表创建语句
- Spring Boot MVC 配置
- Spring Boot Security 配置
- 自定义登录和注销页面
- 自定义拒绝访问异常情况下的403错误页面
- 创建 DAO
- 创建 Service
- 创建 Controller
- 运行应用程序
- 1. 使用Eclipse
- 2. 使用Maven命令
- 3. 使用可执行的JAR
- 参考文献
- 源码下载
本页将介绍Spring Boot MVC安全验证登录和注销+Thymeleaf+CSRF+MySQL数据库+JPA+Hibernate实例。
Spring Boot根据类路径中存在的JAR来配置Spring功能。要在Spring Boot MVC中创建视图,我们应该选择模板引擎,而不是JSP,因为对于JSP来说,嵌入式servlet容器有已知的限制。
在我们的例子中,我们将使用Spring Boot MVC和Thymeleaf模板引擎。
我们将使用Thymeleaf创建自定义的登录、注销和其他页面。
我们将使用数据库进行用户认证。对于Spring Security,我们将创建一个安全配置文件,在那里我们将配置自定义的登录、注销和异常处理配置。
当我们为Spring Security使用JavaConfig配置时,它默认启用CSRF保护。
如果在Spring Security应用程序中启用了CSRF保护,Thymeleaf将自动在表单中包含CSRF令牌。
Spring Boot的所有默认设置都可以通过application.properties文件进行修改,比如与Spring MVC、Spring Security、Thymleaf和数据库有关的设置。
为了完全控制Spring MVC、Spring Security和数据库配置,我们应该分别创建JavaConfig。
在我们的例子中,我们将为Spring Security创建一个JavaConfig。
我们将创建自定义的登录和注销表单,用户将使用数据库进行验证。
为了与数据库进行交互,我们将使用JPA和Hibernate。
数据源和Hibernate属性将被配置在application.properties文件中。
现在让我们一步一步地讨论完整的例子。
演示工具版本- Java 8
- Spring Boot 1.5.3.RELEASE
- Maven 3.3
- MySQL 5.5
- Eclipse Mars
pom.xml
使用 Thymeleaf 模板引擎4.0.0 com.concretepage spring-boot-demo 0.0.1-SNAPSHOT jar spring-demo Spring Boot Demo Project org.springframework.boot spring-boot-starter-parent 1.5.3.RELEASE 1.8 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-security org.springframework.boot spring-boot-starter-data-jpa org.springframework.boot spring-boot-starter-thymeleaf mysql mysql-connector-java org.springframework.boot spring-boot-devtools true org.springframework.boot spring-boot-maven-plugin
为了提供动态HTML内容,Spring Boot更倾向于使用模板引擎,如FreeMarker、Groovy、Thymeleaf、Mustache。
要在Spring Boot MVC中创建视图,应避免使用JSP,因为在处理JSP时,嵌入式Servlet有一些已知的限制。
在我们的例子中,我们使用Thymeleaf模板引擎来创建视图。
要启用Thymeleaf,我们需要在构建文件中使用以下Spring Boot启动器。
spring-boot-starter-thymeleaf
如果Spring Boot安全系统启用了CSRF保护,Thymeleaf将在表单中自动包含CSRF标记。
现在,模板引擎的默认位置如下所示。
src/main/resources/templates
这意味着我们将把Thymeleaf文件放在上述路径中。如果我们想改变Thymeleaf的默认模板引擎路径,我们需要在application.properties中配置以下Spring Boot属性。
spring.thymeleaf.prefix=classpath:/templates/ spring.thymeleaf.suffix=.html
根据需要改变前缀和后缀。Spring使用ThymeleafAutoConfiguration来自动配置Thymeleaf。找到可以在application.properties中使用的属性,以改变Thymeleaf自动配置的设置。
spring.thymeleaf.cache: 启用模板缓存,默认值为true。
spring.thymeleaf.check-template: 在渲染前检查模板是否存在,默认为true。
spring.thymeleaf.check-template-location: 检查模板位置是否存在,默认值为true。
spring.thymeleaf.content-type: 配置内容类型,默认为text/html。
spring.thymeleaf.enabled: 启用MVC Thymeleaf视图解析。默认为true。
spring.thymeleaf.encoding: 配置模板编码。默认为UTF-8。
spring.thymeleaf.excluded-view-names: 配置应从解析中排除的视图名称(逗号分隔)。
spring.thymeleaf.mode: 配置模板模式。默认为HTML 5。
spring.thymeleaf.prefix: 在创建URL时,将前缀加到视图名称上。默认是classpath:/templates/。
spring.thymeleaf.suffix: 在URL创建中附加到视图名称的后缀。默认值为.html。
spring.thymeleaf.template-resolver-order: 模板解析器在链上的顺序。
spring.thymeleaf.view-names: 配置可以解析的视图名称(逗号分隔)。
使用静态内容默认情况下,Spring Boot为静态资源使用类路径中的/static目录。
如果我们使用可执行的JAR来运行我们的项目,那么我们就不能把静态资源放在src/main/webapp路径下,因为当JAR被打包时,它将被大多数构建工具默默地忽略掉。
当我们只想把项目打包成WAR文件时,可以使用src/main/webapp这个路径。
默认情况下,静态资源被映射到; INSERT INTO `users` (`username`, `password`, `full_name`, `role`, `country`, `enabled`) VALUES ('mukesh', '$2a$10$N0eqNiuikWCy9ETQ1rdau.XEELcyEO7kukkfoiNISk/9F7gw6eB0W', 'Mukesh Sharma', 'ROLE_ADMIN', 'India', 1), ('tarun', '$2a$10$QifQnP.XqXDW0Lc4hSqEg.GhTqZHoN2Y52/hoWr4I5ePxK7D2Pi8q', 'Tarun Singh', 'ROLE_USER', 'India', 1);
密码是使用BCrypt加密方案。要用BCrypt加密方案对密码进行加密,如下所示。
Main.java
package com.concretepage;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class Main {
public static void main(String[] args) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
System.out.println(encoder.encode("m123"));
}
}
现在查找与示例中使用的MySQL表对应的java实体。
Article.java
package com.concretepage.entity;
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="articles")
public class Article implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Column(name="article_id")
private int articleId;
@Column(name="title")
private String title;
@Column(name="category")
private String category;
public int getArticleId() {
return articleId;
}
public void setArticleId(int articleId) {
this.articleId = articleId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
}
UserInfo.java
package com.concretepage.entity;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name="users")
public class UserInfo implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@Column(name="username")
private String userName;
@Column(name="password")
private String password;
@Column(name="role")
private String role;
@Column(name="full_name")
private String fullName;
@Column(name="country")
private String country;
@Column(name="enabled")
private short enabled;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public short getEnabled() {
return enabled;
}
public void setEnabled(short enabled) {
this.enabled = enabled;
}
}
Spring Boot MVC 配置
为了启用Spring Boot MVC,我们需要在构建文件中使用以下启动器。
spring-boot-starter-web
当Spring Boot扫描类路径中的Spring Web时,它会自动配置Spring Web MVC。
要改变任何配置,Spring Boot在application.properties中提供了需要配置的属性。
找到这些属性。
spring.mvc.async.request-timeout: 异步请求的超时时间,单位为毫秒。
spring.mvc.date-format: 要使用的日期格式。
spring.mvc.favicon.enabled: 它决定启用和禁用favicon。默认值为true。
spring.mvc.locale: 要使用的区域设置。
spring.mvc.media-types.*: 将文件扩展名映射到媒体类型,以便进行内容协商。
spring.mvc.servlet.load-on-startup: 它配置了Spring Web Services Servlet的启动优先级。默认值是-1。
spring.mvc.static-path-pattern: 它配置了静态资源的路径模式。
spring.mvc.view.prefix: 它为Spring视图(如JSP)配置前缀。
spring.mvc.view.suffix: 它配置视图后缀。
为了完全控制Spring MVC的配置,我们可以创建一个带有@Configuration和@EnableWebMvc注释的配置类。
要重写任何设置,我们需要扩展WebMvcConfigurerAdapter类。
Spring Boot Security 配置为了配置Spring Boot Security,我们需要在构建文件中使用以下Spring Boot启动器。
spring-boot-starter-security
默认情况下,我们得到的是内存中的认证,只有一个名为user的用户和一个随机的默认密码,我们会在控制台中打印出来。
我们可以通过配置application.properties中的security属性来改变默认设置。
找到其中的一些。
security.user.name: 它配置用户名。默认的用户是user。
security.user.password: 它配置密码。
security.user.role: 它配置了角色。默认的角色是USER。
security.enable-csrf: 它启用CSRF。默认值为false。
当我们想要完全控制Spring Security时,我们需要创建带有@Configuration和@EnableWebSecurity注释的java配置。
要覆盖任何设置,我们需要扩展WebSecurityConfigurerAdapter类。
为了保护一个方法,我们需要用@EnableGlobalMethodSecurity来注释我们的配置类。
现在找到我们例子中使用的security配置。
SecurityConfig.java
package com.concretepage.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyAppUserDetailsService myAppUserDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/app/secure/**").hasAnyRole("ADMIN","USER")
.and().formLogin() //login configuration
.loginPage("/app/login")
.loginProcessingUrl("/app-login")
.usernameParameter("app_username")
.passwordParameter("app_password")
.defaultSuccessUrl("/app/secure/article-details")
.and().logout() //logout configuration
.logoutUrl("/app-logout")
.logoutSuccessUrl("/app/login")
.and().exceptionHandling() //exception handling configuration
.accessDeniedPage("/app/error");
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
auth.userDetailsService(myAppUserDetailsService).passwordEncoder(passwordEncoder);
}
}
在上述安全配置中,我们已经配置了自定义的登录、注销和异常处理。Spring Security JavaConfig默认启用CSRF保护。
通过使用@EnableWebSecurity注解,Thymeleaf在表单中自动包含CSRF令牌。对于密码编码,我们使用Spring BCryptPasswordEncoder类。
为了使用数据库对用户进行认证,我们需要实现UserDetailsService。
MyAppUserDetailsService.java
package com.concretepage.config;
import java.util.Arrays;
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.User;
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 com.concretepage.dao.IUserInfoDAO;
import com.concretepage.entity.UserInfo;
@Service
public class MyAppUserDetailsService implements UserDetailsService {
@Autowired
private IUserInfoDAO userInfoDAO;
@Override
public UserDetails loadUserByUsername(String userName)
throws UsernameNotFoundException {
UserInfo activeUserInfo = userInfoDAO.getActiveUser(userName);
GrantedAuthority authority = new SimpleGrantedAuthority(activeUserInfo.getRole());
UserDetails userDetails = (UserDetails)new User(activeUserInfo.getUserName(),
activeUserInfo.getPassword(), Arrays.asList(authority));
return userDetails;
}
}
自定义登录和注销页面
使用Thymeleaf模板引擎找到自定义的登录和注销页面。由于我们使用的是JavaConfig中配置的spring security,CSRF保护被默认启用。
在运行时,CSRF令牌将由Thymeleaf自动包含在表单中。
custom-login.html
Spring Boot MVC Security using Thymeleaf
Spring Boot MVC Security using Thymeleaf
Bad Credentials
articles.html
Spring Boot MVC Security using Thymeleaf
User Articles Details
Logged in user: [[${#httpServletRequest.remoteUser}]]
| Id | Title | Category |
当用户试图访问一个安全的、没有授权给请求的用户角色的方法时,就会抛出拒绝访问的异常。我们已经创建了一个带有自定义错误信息的错误页面。
403.html
Spring Boot MVC Security using Thymeleaf
Access Denied Exception
Logged in user: [[${#httpServletRequest.remoteUser}]]
Error
创建 DAO
找到我们例子中使用的DAO接口和类。
IUserInfoDAO.java
package com.concretepage.dao;
import java.util.List;
import com.concretepage.entity.Article;
import com.concretepage.entity.UserInfo;
public interface IUserInfoDAO {
UserInfo getActiveUser(String userName);
List getAllUserArticles();
}
UserInfoDAO.java
package com.concretepage.dao;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import com.concretepage.entity.Article;
import com.concretepage.entity.UserInfo;
@Repository
@Transactional
public class UserInfoDAO implements IUserInfoDAO {
@PersistenceContext
private EntityManager entityManager;
public UserInfo getActiveUser(String userName) {
UserInfo activeUserInfo = new UserInfo();
short enabled = 1;
List> list = entityManager.createQuery("SELECt u FROM UserInfo u WHERe userName=? and enabled=?")
.setParameter(1, userName).setParameter(2, enabled).getResultList();
if(!list.isEmpty()) {
activeUserInfo = (UserInfo)list.get(0);
}
return activeUserInfo;
}
@SuppressWarnings("unchecked")
@Override
public List getAllUserArticles() {
String hql = "FROM Article as atcl ORDER BY atcl.articleId";
return (List) entityManager.createQuery(hql).getResultList();
}
}
创建 Service
我们已经在我们的服务类中创建了一个安全的方法,可以由具有ADMIN角色的用户访问。找到我们例子中使用的服务接口和类。
IUserInfoService.java
package com.concretepage.service;
import java.util.List;
import org.springframework.security.access.annotation.Secured;
import com.concretepage.entity.Article;
public interface IUserInfoService {
@Secured ({"ROLE_ADMIN"})
List getAllUserArticles();
}
UserInfoService.java
package com.concretepage.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.concretepage.dao.IUserInfoDAO;
import com.concretepage.entity.Article;
@Service
public class UserInfoService implements IUserInfoService {
@Autowired
private IUserInfoDAO userInfoDAO;
@Override
public List getAllUserArticles(){
return userInfoDAO.getAllUserArticles();
}
}
创建 Controller
找到我们例子中使用的控制器。
UserInfoController.java
package com.concretepage.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import com.concretepage.service.IUserInfoService;
@Controller
@RequestMapping("app")
public class UserInfoController {
@Autowired
private IUserInfoService userInfoService;
@GetMapping("login")
public ModelAndView login() {
ModelAndView mav = new ModelAndView();
mav.setViewName("custom-login");
return mav;
}
@GetMapping("secure/article-details")
public ModelAndView getAllUserArticles() {
ModelAndView mav = new ModelAndView();
mav.addObject("userArticles", userInfoService.getAllUserArticles());
mav.setViewName("articles");
return mav;
}
@GetMapping("error")
public ModelAndView error() {
ModelAndView mav = new ModelAndView();
String errorMessage= "You are not authorized for the requested data.";
mav.addObject("errorMsg", errorMessage);
mav.setViewName("403");
return mav;
}
}
运行应用程序
要运行该应用程序,首先在MySQL中创建表,如例子中给出的。现在我们可以通过以下方式运行REST网络服务。
1. 使用Eclipse使用页面末尾的下载链接下载项目的源代码。
将该项目导入eclipse。
使用命令提示符,进入项目的根文件夹并运行。
mvn clean eclipse:eclipse
然后在eclipse中刷新该项目。点击Run as -> Java Application来运行主类MyApplication。主类的内容如下。
MyApplication.java
package com.concretepage;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
Tomcat服务器将被启动。
2. 使用Maven命令下载项目的源代码。使用命令提示符进入项目的根文件夹并运行命令。
mvn spring-boot:run
Tomcat服务器将被启动。
3. 使用可执行的JAR使用命令提示符,转到项目的根文件夹并运行该命令。
mvn clean package
我们将在目标文件夹中得到可执行的spring-boot-demo-0.0.1-SNAPSHOT.jar。以下列方式运行这个JAR。
java -jar target/spring-boot-demo-0.0.1-SNAPSHOT.jar
Tomcat服务器将被启动。
现在访问下面给出的网址。
http://localhost:8080/app/login
1. 访问结果如图所示
输入具有ADMIN角色的凭证mukesh/m123。
2. 成功后,如下图所示。
当我们点击注销按钮时,该页面会重定向到登录页面。
3. 现在使用具有USER角色的凭据tarun/t123登录应用程序。
我们将得到拒绝访问的页面,因为它试图访问没有授权给USER角色的安全服务方法。
4. 如果我们输入错误的凭证,我们将得到错误信息。
【1】Securing a Web Application
【2】Spring Boot Reference Guide
【3】Spring Boot Security REST + JPA + Hibernate + MySQL CRUD Example
【4】Spring Boot MVC Security Example
提取码:mao4
spring-boot-mvc-security-custom-login-and-logout-thymeleaf-csrf-mysql-database-jpa-hibernate-example.zip



