1. Spring Data JPA简介 Spring Data JPA 中文文档 (springdoc.cn)
Spring Data JPA 是 Spring Data 项目的一部分,它提供了一种简化的数据访问方式,用于与关系型数据库进行交互。它基于 Java Persistence API(JPA) 标准,并提供了一套简洁的 API 和注解,使开发人员能够通过简单的 Java 对象来表示数据库表,并通过自动生成的 SQL 语句执行常见的 CRUD 操作。Spring Data JPA 通过封装 JPA 的复杂性,简化了数据访问层的开发工作,使开发人员能够更专注于业务逻辑的实现。它还提供了丰富的查询方法的定义、分页和排序支持、事务管理等功能,使开发人员能够更方便地进行数据访问和操作。简单说就是和mybatis差不错的东西,实现上有所区别。
2. Thymeleaf模板引擎 Thymeleaf **一个html模板引擎,简化开发流程
1 2 3 4 5 6 7 8 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-thymeleaf</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency >
文件路径
路径映射
1 2 3 4 5 6 7 8 spring: thymeleaf: suffix: .html cache: false prefix: classpath:/templates/ web: resources: static-locations: classpath:/static/
访问接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package com.kdd.chapter07.controller;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;@Controller public class FileController { @Autowired private RedisTemplate redisTemplate; @GetMapping("/detail/{type}/{path}") public String toDetail (@PathVariable("type") String type, @PathVariable("path") String path) { return "/detail/" + type + "/" + path; } @GetMapping("/userLogin") public String toLoginPage () { return "/login/login" ; } }
3. Spring Security安全管理 3.1 简介 Spring Security 中文文档 :: Spring Security Reference (springdoc.cn)
Spring Security是一个Java框架,用于保护应用程序的安全性。它提供了一套全面的安全解决方案,包括身份验证、授权、防止攻击等功能。Spring Security基于过滤器链的概念,可以轻松地集成到任何基于Spring的应用程序中。它支持多种身份验证选项和授权策略,开发人员可以根据需要选择适合的方式。此外,Spring Security还提供了一些附加功能,如集成第三方身份验证提供商和单点登录,以及会话管理和密码编码等。总之,Spring Security是一个强大且易于使用的框架,可以帮助开发人员提高应用程序的安全性和可靠性。简单来说就是一个提供登录拦截等等用户管理的。
3.2 依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-security</artifactId > </dependency > <dependency > <groupId > org.springframework.security</groupId > <artifactId > spring-security-test</artifactId > <scope > test</scope > </dependency > <dependency > <groupId > org.thymeleaf.extras</groupId > <artifactId > thymeleaf-extras-springsecurity5</artifactId > </dependency > <dependency > <groupId > org.thymeleaf.extras</groupId > <artifactId > thymeleaf-extras-springsecurity5</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-jpa</artifactId > </dependency >
此时光导入不配置任何会提供一个默认的login页面,可以在后续进行配置替换这个登录页面,用户名是user,密码会在控制台中随机生成。
3.3 连接数据库 1 2 3 4 datasource: url: jdbc:mysql://localhost:3306/springbootdata?useSSL=false username: root password: 123456
JPA的实体类和mybatis的实体类定义有不一样,使用implements Serializable将用户数据进行反序列化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package com.kdd.chapter07.domain;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;import java.io.Serializable;@Entity(name = "t_authority ") public class Authority implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String authority ; public Integer getId () { return id; } public void setId (Integer id) { this .id = id; } public String getAuthority () { return authority; } public void setAuthority (String authority) { this .authority = authority; } @Override public String toString () { return "Authority{" + "id=" + id + ", authority='" + authority + '\'' + '}' ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package com.kdd.chapter07.domain;import javax.persistence.*;import java.io.Serializable;@Entity(name = "t_customer") public class Customer implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String username; private String password; public Integer getId () { return id; } public void setId (Integer id) { this .id = id; } 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; } @Override public String toString () { return "Customer{" + "id=" + id + ", username='" + username + '\'' + ", password=" + password + '}' ; } }
在以上准备工作完成后,在configure方法中使用JDBC身份认证的方式 进行自定义用户认证。SecurityConfig.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package com.kdd.chapter07;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;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;import javax.sql.DataSource;@EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private DataSource dataSource; @Override protected void configure (AuthenticationManagerBuilder auth) throws Exception { BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder (); String userSQL = "select username,password,valid from t_customer where username = ?" ; String authoritySQL="select c.username,a.authority from t_customer c,t_authority a,t_customer_authority ca where ca.customer_id=c.id and ca.authority_id=a.id and c.username =?" ; auth.jdbcAuthentication().passwordEncoder(bCryptPasswordEncoder).dataSource(dataSource).usersByUsernameQuery(userSQL).authoritiesByUsernameQuery(authoritySQL); } }
此时可以使用数据库中的用户信息进行登录了。
3.4 连接redis减少IO访问 1 2 3 4 5 6 7 8 9 10 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-redis</artifactId > </dependency > <dependency > <groupId > org.apache.commons</groupId > <artifactId > commons-pool2</artifactId > <version > 2.11.1</version > </dependency >
1 2 3 4 5 6 7 8 9 10 11 12 redis: host: localhost port: 6379 password: '' database: 0 timeout: 0 lettuce: pool: max-active: 8 max-idle: 8 min-idle: 0 max-wait: -1ms
创建查询语句,在JPA中叫做Repository,和mybatis中的mapper一样的效果
1 2 3 4 5 6 7 8 9 10 11 12 13 package com.kdd.chapter07.repository;import com.kdd.chapter07.domain.Authority;import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.data.jpa.repository.Query;import java.util.List;public interface AuthorityRepository extends JpaRepository <Authority,Integer> { @Query(value = "select a.* from t_customer c,t_authority a,t_customer_authority ca where ca.customer_id=c.id and ca.authority_id=a.id and c.username =?1",nativeQuery = true) public List<Authority> findAuthoritiesByUsername (String username) ; }
1 2 3 4 5 6 7 8 package com.kdd.chapter07.repository;import com.kdd.chapter07.domain.Customer;import org.springframework.data.jpa.repository.JpaRepository;public interface CustomerRepository extends JpaRepository <Customer,Integer> { Customer findByUsername (String username) ; }
创建定义查询用户及角色信息的服务接口,实现对用户数据结合Redis缓存进行业务处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 package com.kdd.chapter07.service;import com.kdd.chapter07.domain.Authority;import com.kdd.chapter07.domain.Customer;import com.kdd.chapter07.repository.AuthorityRepository;import com.kdd.chapter07.repository.CustomerRepository;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.stereotype.Service;import java.util.List;@Service public class CustomerService { @Autowired private CustomerRepository customerRepository; @Autowired private AuthorityRepository authorityRepository; @Autowired private RedisTemplate redisTemplate; public Customer getCustomer (String username) { Customer customer=null ; Object o = redisTemplate.opsForValue().get("customer_" +username); if (o!=null ){ customer=(Customer)o; }else { customer = customerRepository.findByUsername(username); if (customer!=null ){ redisTemplate.opsForValue().set("customer_" +username,customer); } } return customer; } public List<Authority> getCustomerAuthority (String username) { List<Authority> authorities=null ; Object o = redisTemplate.opsForValue().get("authorities_" +username); if (o!=null ){ authorities=(List<Authority>)o; }else { authorities=authorityRepository.findAuthoritiesByUsername(username); if (authorities.size()>0 ){ redisTemplate.opsForValue().set("authorities_" +username,authorities); } } return authorities; } }
在configure方法中(先注释掉JDBC身份认证方式),使用UserDetailsService身份认证进行自定义用户认证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private DataSource dataSource; @Autowired private UserDetailsServiceImpl userDetailsService; @Override protected void configure (AuthenticationManagerBuilder auth) throws Exception { BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder (); auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder); } }
此时可以查看redis中的登录数据,乱码是因为传输的二进制数据,如果需要的话可以使用json传输中文,可以查看前面的redis使用。
4. 进阶 4.1 配置拦截 在该项目案例中,用户有两个身份,我们需要对不同身份的对象展示不同的数据就可以对configure进行进一步的配置,配置登录用户中的不同身份限制其范围访问。在SecurityConfig中添加重载。
1 2 3 4 5 6 7 8 9 10 protected void configure (HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers("/" ).permitAll() .antMatchers("/login/**" ).permitAll() .antMatchers("/detail/common/**" ).hasRole("common" ) .antMatchers("detail/vip/**" ).hasRole("vip" ) .anyRequest().authenticated() .and().formLogin(); }
4.2 登录替换 在4.1重装的配置中添加http.formLogin()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 protected void configure (HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers("/" ).permitAll() .antMatchers("/login/**" ).permitAll() .antMatchers("/detail/common/**" ).hasRole("common" ) .antMatchers("detail/vip/**" ).hasRole("vip" ) .anyRequest().authenticated() .and().formLogin(); http.formLogin() .loginPage("/userLogin" ).permitAll() .usernameParameter("name" ).passwordParameter("pwd" ) .defaultSuccessUrl("/" ) .failureUrl("/userLogin?error" ); }
同样的可以添加退出和记住我功能。
1 2 3 4 5 6 7 8 http.logout() .logoutUrl("/mylogout" ) .logoutSuccessUrl("/" ); http.rememberMe() .rememberMeParameter("rememberme" ) .tokenValiditySeconds(200 );
5. 文件备份 5.1 pom.xml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.5.6</version > <relativePath /> </parent > <groupId > com.kdd</groupId > <artifactId > chapter07</artifactId > <version > 0.0.1-SNAPSHOT</version > <name > chapter07</name > <description > chapter07</description > <url /> <licenses > <license /> </licenses > <developers > <developer /> </developers > <scm > <connection /> <developerConnection /> <tag /> <url /> </scm > <properties > <java.version > 17</java.version > </properties > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-thymeleaf</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-security</artifactId > </dependency > <dependency > <groupId > org.springframework.security</groupId > <artifactId > spring-security-test</artifactId > <scope > test</scope > </dependency > <dependency > <groupId > org.thymeleaf.extras</groupId > <artifactId > thymeleaf-extras-springsecurity5</artifactId > </dependency > <dependency > <groupId > com.mysql</groupId > <artifactId > mysql-connector-j</artifactId > <version > 8.0.33</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-jdbc</artifactId > </dependency > <dependency > <groupId > org.thymeleaf.extras</groupId > <artifactId > thymeleaf-extras-springsecurity5</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-redis</artifactId > </dependency > <dependency > <groupId > org.apache.commons</groupId > <artifactId > commons-pool2</artifactId > <version > 2.11.1</version > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-jpa</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > </plugin > </plugins > </build > </project >
5.2 application.yml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 spring: thymeleaf: suffix: .html cache: false prefix: classpath:/templates/ web: resources: static-locations: classpath:/static/ datasource: url: jdbc:mysql://localhost:3306/springbootdata?useSSL=false username: root password: 123456 redis: host: localhost port: 6379 password: '' database: 0 timeout: 0 lettuce: pool: max-active: 8 max-idle: 8 min-idle: 0 max-wait: -1ms
5.3 redis启动 双击运行即可
5.4 sql表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 # 选择使用数据库 USE chapter07; # 创建表t_customer并插入相关数据,密码为123456 DROP TABLE IF EXISTS `t_customer`; CREATE TABLE `t_customer` ( `id` int (20 ) NOT NULL AUTO_INCREMENT, `username` varchar (200 ) DEFAULT NULL , `password` varchar (200 ) DEFAULT NULL , `valid` tinyint(1 ) NOT NULL DEFAULT '1' , PRIMARY KEY (`id`) ) ENGINE= InnoDB AUTO_INCREMENT= 4 DEFAULT CHARSET= utf8; INSERT INTO `t_customer` VALUES ('1' , 'shitou' , '$2a$10$5ooQI8dir8jv0/gCa1Six.GpzAdIPf6pMqdminZ/3ijYzivCyPlfK' , '1' ); INSERT INTO `t_customer` VALUES ('2' , '李四' , '$2a$10$5ooQI8dir8jv0/gCa1Six.GpzAdIPf6pMqdminZ/3ijYzivCyPlfK' , '1' ); # 创建表t_authority并插入相关数据 DROP TABLE IF EXISTS `t_authority`; CREATE TABLE `t_authority` ( `id` int (20 ) NOT NULL AUTO_INCREMENT, `authority` varchar (20 ) DEFAULT NULL , PRIMARY KEY (`id`) ) ENGINE= InnoDB AUTO_INCREMENT= 3 DEFAULT CHARSET= utf8; INSERT INTO `t_authority` VALUES ('1' , 'ROLE_common' ); INSERT INTO `t_authority` VALUES ('2' , 'ROLE_vip' ); # 创建表t_customer_authority并插入相关数据 DROP TABLE IF EXISTS `t_customer_authority`; CREATE TABLE `t_customer_authority` ( `id` int (20 ) NOT NULL AUTO_INCREMENT, `customer_id` int (20 ) DEFAULT NULL , `authority_id` int (20 ) DEFAULT NULL , PRIMARY KEY (`id`) ) ENGINE= InnoDB AUTO_INCREMENT= 5 DEFAULT CHARSET= utf8; INSERT INTO `t_customer_authority` VALUES ('1' , '1' , '1' ); INSERT INTO `t_customer_authority` VALUES ('2' , '2' , '2' ); # 记住我功能中创建持久化Token存储的数据表 create table persistent_logins (username varchar (64 ) not null , series varchar (64 ) primary key, token varchar (64 ) not null , last_used timestamp not null );
5.5 项目结构