资源数据下载:

  • security
  • springbootdata
  • 前端文件

    使用Security依赖启动器自带登录业务

  1. 创建项目
  2. 引入Lombol、Spring Web、MySQL Driver、Thymeleaf
    1. image.png
  3. 自己配置好maven、lombok配置
  4. 引入第七章前端代码在resources的templates路径
    1. image.png
    2. index内代码查看 ```html
  5. <!DOCTYPE html>

    欢迎进入电影网站首页


    普通电影

    VIP专享

    ```

  6. 在项目创建controller包

    1. 包下创建FileController类,编写用于页面请求的控制类 ```java package com.example.demo.controller;

import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable;

@Controller public class FileController { //影片详情页 @GetMapping(“/detail/{type}/{path}”) //请求映射地址为/detail/{type}/{path},{}是占位符,里面指向的是方法体内的参数type和path //定义参数type和path为接收参数,返回具体的路径。实现向影片详情页面请求跳转. public String toDetail(@PathVariable(“type”) String type, @PathVariable(“path”) String path) { return “detail/“+type+”/“+path; //从resource内的templates找到detail/{type}/{path},返回视图显示 } }

  1. 6. 引入security依赖入pom.xmlmaven仓库)
  2. ```xml
  3. <dependency>
  4. <groupId>org.springframework.boot</groupId>
  5. <artifactId>spring-boot-starter-security</artifactId>
  6. <version>2.6.7</version>
  7. </dependency>
  1. 重新启动
    1. image.png
    2. 浏览器查看
    3. image.png
    4. image.png
    5. 输入控制台显示的Spring Security提供的默认的账户密码,登录
    6. image.png
    7. 这种默认安全管理方式存在诸多问题,例如:只有唯一的默认登录用户user、密码随机生成且过于暴露、登录页面及错误提示页面不是我们想要的等。
  2. 我们可以通过自定义WebSecurityConfigurerAdapter类型的Bean组件来覆盖默认访问规则。搜索WebSecurityConfigurerAdapter,可知是抽象类。我们通过继承该类后,重写或重载其中的方法来实现自定义。
    1. 主要的两个配置方法
      1. configure(AuthenticationManagerBuilder auth)
        1. 定制用户认证管理器来实现用户认证
      2. configure(HttpSecurity http)
        1. 定制基于HTTP请求的用户访问控制

实现自定义用户认证

内存身份认证(无法用于实际生产,只可作为初学者测试)

  1. 创建config包
    1. 包下创建SecurityConfig类 ```java package com.example.demo.config;

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 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;

//开启MVC Security安全支持 @EnableWebSecurity
//继承抽象类WebSecurityConfigurerAdapter,通过重写其中方法实现自定义 public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Override//@Override 注解用于指定方法必须重写,让编译器了解该方法必须重写,如果不重写就报错 protected void configure(AuthenticationManagerBuilder auth) throws Exception{ //密码设置需要设置编码器 BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); //1.使用内存用户信息,作为测试使用 auth.inMemoryAuthentication().passwordEncoder(encoder) .withUser(“shitou”).password(encoder.encode(“123456”)).roles(“common”) .and() .withUser(“李四”).password(encoder.encode(“123456”)).roles(“vip”); } }

  1. 2. 重启运行
  2. 1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/22384052/1653013908659-0e457a1d-347e-47c6-8056-7ada0df1afc9.png#clientId=ue82bcf67-d977-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=396&id=u14eda918&margin=%5Bobject%20Object%5D&name=image.png&originHeight=495&originWidth=1595&originalType=binary&ratio=1&rotation=0&showTitle=false&size=39892&status=done&style=none&taskId=ua1506837-16ae-44f9-acf8-36049e5a53a&title=&width=1276)
  3. 2. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/22384052/1653013923133-51ea45d6-1870-402b-b711-e455f16bda1d.png#clientId=ue82bcf67-d977-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=505&id=uf111cc30&margin=%5Bobject%20Object%5D&name=image.png&originHeight=631&originWidth=1249&originalType=binary&ratio=1&rotation=0&showTitle=false&size=36212&status=done&style=none&taskId=u266983ee-747b-4994-8aaf-d18c8ca8e09&title=&width=999.2)
  4. 3. 输入配置的自定义账户密码,可跳转到对应页面
  5. 4. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/22384052/1653014187026-3f70b084-996f-4605-a309-4596dbaee4f2.png#clientId=ue8dbb611-3f54-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=423&id=u031ccee6&margin=%5Bobject%20Object%5D&name=image.png&originHeight=529&originWidth=1715&originalType=binary&ratio=1&rotation=0&showTitle=false&size=52720&status=done&style=none&taskId=u999cd2eb-94ad-4ed2-a39f-77682adc9ad&title=&width=1372)
  6. 1. JDBC身份验证的实现
  7. 1. 本质:用数据库已有的用户信息在项目中实现用户认证服务,需要提前准备好相关数据
  8. 2. source代码引入数据库文件springboot.sqlsecurity.sql
  9. 3. <br />
  10. ```sql
  11. set character set utf8;
  12. drop database if exists springbootdata;
  13. # 创建数据库
  14. CREATE DATABASE springbootdata default character set utf8;
  15. # 选择使用数据库
  16. USE springbootdata;
  17. # 创建表t_article并插入相关数据
  18. DROP TABLE IF EXISTS `t_article`;
  19. CREATE TABLE `t_article` (
  20. `id` int(20) NOT NULL AUTO_INCREMENT COMMENT '文章id',
  21. `title` varchar(200) DEFAULT NULL COMMENT '文章标题',
  22. `content` longtext COMMENT '文章内容',
  23. PRIMARY KEY (`id`)
  24. ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
  25. set character set utf8;
  26. INSERT INTO `t_article` VALUES ('1', 'Spring Boot基础入门', '从入门到精通讲解...');
  27. INSERT INTO `t_article` VALUES ('2', 'Spring Cloud基础入门', '从入门到精通讲解...');
  28. # 创建表t_comment并插入相关数据
  29. DROP TABLE IF EXISTS `t_comment`;
  30. CREATE TABLE `t_comment` (
  31. `id` int(20) NOT NULL AUTO_INCREMENT COMMENT '评论id',
  32. `content` longtext COMMENT '评论内容',
  33. `author` varchar(200) DEFAULT NULL COMMENT '评论作者',
  34. `a_id` int(20) DEFAULT NULL COMMENT '关联的文章id',
  35. `deleted` int(20) DEFAULT 0,
  36. PRIMARY KEY (`id`)
  37. ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
  38. set character set utf8;
  39. INSERT INTO `t_comment` VALUES ('1', '很全、很详细', '狂奔的蜗牛', '1', '0');
  40. INSERT INTO `t_comment` VALUES ('2', '赞一个', 'tom', '1', '0');
  41. INSERT INTO `t_comment` VALUES ('3', '很详细', 'kitty', '1', '0');
  42. INSERT INTO `t_comment` VALUES ('4', '很好,非常详细', '张三', '1', '0');
  43. INSERT INTO `t_comment` VALUES ('5', '很不错', '张杨', '2', '0');
  1. # 选择使用数据库
  2. USE springbootdata;
  3. # 创建表t_customer并插入相关数据
  4. DROP TABLE IF EXISTS `t_customer`;
  5. CREATE TABLE `t_customer` (
  6. `id` int(20) NOT NULL AUTO_INCREMENT,
  7. `username` varchar(200) DEFAULT NULL,
  8. `password` varchar(200) DEFAULT NULL,
  9. # tinyint类型对应booolean类型
  10. `valid` tinyint(1) NOT NULL DEFAULT '1',
  11. PRIMARY KEY (`id`)
  12. ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
  13. INSERT INTO `t_customer` VALUES ('1', 'shitou', '$2a$10$d8tE/Eqw4e6/ormxMKBtAOgDxt3XY1QsfukGq.OXeSL1XyqhcJAH6', '1');
  14. INSERT INTO `t_customer` VALUES ('2', '李四', '$2a$10$5ooQI8dir8jv0/gCa1Six.GpzAdIPf6pMqdminZ/3ijYzivCyPlfK', '1');
  15. # 创建表t_authority并插入相关数据
  16. DROP TABLE IF EXISTS `t_authority`;
  17. CREATE TABLE `t_authority` (
  18. `id` int(20) NOT NULL AUTO_INCREMENT,
  19. `authority` varchar(20) DEFAULT NULL,
  20. PRIMARY KEY (`id`)
  21. ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
  22. INSERT INTO `t_authority` VALUES ('1', 'ROLE_common');
  23. INSERT INTO `t_authority` VALUES ('2', 'ROLE_vip');
  24. # 创建表t_customer_authority并插入相关数据
  25. DROP TABLE IF EXISTS `t_customer_authority`;
  26. CREATE TABLE `t_customer_authority` (
  27. `id` int(20) NOT NULL AUTO_INCREMENT,
  28. `customer_id` int(20) DEFAULT NULL,
  29. `authority_id` int(20) DEFAULT NULL,
  30. PRIMARY KEY (`id`)
  31. ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
  32. INSERT INTO `t_customer_authority` VALUES ('1', '1', '1');
  33. INSERT INTO `t_customer_authority` VALUES ('2', '2', '2');
  34. # 记住我功能中创建持久化Token存储的数据表
  35. create table persistent_logins (username varchar(64) not null,
  36. series varchar(64) primary key,
  37. token varchar(64) not null,
  38. last_used timestamp not null);
  1. pom.xml添加JDBC连接数据库的依赖启动器 ```xml
    1. <dependency>
    2. <groupId>org.springframework.boot</groupId>
    3. <artifactId>spring-boot-starter-jdbc</artifactId>
    4. </dependency>
    5. <dependency>
    6. <groupId>mysql</groupId>
    7. <artifactId>mysql-connector-java</artifactId>
    8. <scope>runtime</scope>
    9. </dependency>
  1. 3. 配置数据库连接
  2. ```properties
  3. #数据库连接配置
  4. spring.datasource.url=jdbc:mysql://localhost:3306/springbootdata?serverTimezone=UTC
  5. spring.datasource.username=root
  6. spring.datasource.password=root
  1. 重新编写SecurityConfig ```properties package com.example.demo.config;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 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;

//开启MVC Security安全支持 @EnableWebSecurity
//继承抽象类WebSecurityConfigurerAdapter,通过重写其中方法实现自定义 public class SecurityConfig extends WebSecurityConfigurerAdapter{
//Autowired相当于new,来自于springboot本身,用于注入组件 @Autowired //装配了DataSource数据源作为属性 private DataSource dataSource; //@Override 注解用于指定方法必须重写,让编译器了解该方法必须重写,如果不重写就报错 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception{

  1. /* 1. 内存实现身份认证
  2. //密码设置需要设置编码器
  3. BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
  4. //1.使用内存用户信息,作为测试使用。定义了一个common用户,一个vip用户,设定了默认账户密码
  5. auth.inMemoryAuthentication().passwordEncoder(encoder)
  6. .withUser("shitou").password(encoder.encode("123456")).roles("common")
  7. .and()
  8. .withUser("李四").password(encoder.encode("123456")).roles("vip");
  9. } */
  10. //2. JDBC进行身份验证
  11. //密码设置需要设置编码器
  12. BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
  13. //使用JDBC进行身份认证
  14. //根据浏览器所输入的用户名寻找名字,密码权限
  15. String userSQL = "select username,password,valid from t_customer "+
  16. "where username = ?";
  17. //SQL语句输错不报错,实际情况下不会直接敲SQL代码
  18. String authoritySQL = "select c.username,a.authority from t_customer c,"+
  19. "t_authority a,t_customer_authority ca where "+
  20. "ca.customer_id=c.id and ca.authority_id=a.id and c.username =?";
  21. //auth调用jdbc权限管理的方法.编码器.数据源.执行SQL语句
  22. auth.jdbcAuthentication().passwordEncoder(encoder)
  23. .dataSource(dataSource)
  24. .usersByUsernameQuery(userSQL)
  25. .authoritiesByUsernameQuery(authoritySQL);
  26. }

}

  1. 5. 重启启动,输入数据库的用户密码。
  2. <a name="WVKed"></a>
  3. ## UserDetailsService身份验证
  4. 1. 创建相关实体类CustomerAuthority——EntityDomain
  5. ```java
  6. package com.example.demo.entity;
  7. import javax.persistence.*;
  8. import lombok.Data;
  9. @Data //Lombok注解,自动创建get、set、toString
  10. @Entity(name = "t_customer") //需要引入jpa依赖包spring data jpa
  11. public class Customer {
  12. @Id //用于标注数据库主键为下方的id
  13. @GeneratedValue(strategy = GenerationType.IDENTITY) //设置表示主键是自增策略
  14. private Integer id;
  15. private String username;
  16. private String password;
  17. }
  1. package com.example.demo.entity;
  2. import javax.persistence.*;
  3. import lombok.Data;
  4. @Data
  5. @Entity(name = "t_authority ")
  6. public class Authority {
  7. @Id
  8. @GeneratedValue(strategy = GenerationType.IDENTITY)
  9. private Integer id;
  10. private String authority ;
  11. }
  1. 创建数据访问层Dao层(Repository层)的两个接口CustomerRepository和AuthorityRepository ```java package com.example.demo.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.example.demo.entity.Customer;

public interface CustomerRepository extends JpaRepository { Customer findByUsername(String username); }

  1. ```java
  2. package com.example.demo.repository;
  3. import java.util.List;
  4. import org.springframework.data.jpa.repository.JpaRepository;
  5. import org.springframework.data.jpa.repository.Query;
  6. import com.example.demo.entity.Authority;
  7. public interface AuthorityRepository extends JpaRepository<Authority,Integer> {
  8. @Query(value = "select a.* from t_customer c,t_authority a,"
  9. + "t_customer_authority ca where ca.customer_id=c.id and ca.authority_id=a.id and c.username =?1",
  10. nativeQuery = true)
  11. public List<Authority> findAuthoritiesByUsername(String username);
  12. }
  1. 引入Redis依赖

    1. <!-- 引入Redis依赖spring-boot-starter-data-redis -->
    2. <dependency>
    3. <groupId>org.springframework.boot</groupId>
    4. <artifactId>spring-boot-starter-data-redis</artifactId>
    5. <version>2.7.0</version>
    6. </dependency>
  2. 配置redis

    1. #Redis配置(默认的主机地址、端口)
    2. spring.redis.host=127.0.0.1
    3. spring.redis.port=6379
    4. spring.redis.password=
  3. 创建Service层,定义查询用户及角色信息的服务接口 ```java package com.example.demo.Service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate;

import com.example.demo.entity.Authority; import com.example.demo.entity.Customer; import com.example.demo.repository.AuthorityRepository; import com.example.demo.repository.CustomerRepository;

public class CustomerService { @Autowired private CustomerRepository customerRepository; @Autowired private AuthorityRepository authorityRepository; private RedisTemplate redisTemplate; public Customer getCustomer(String username){ Customer customer=null; Object o = redisTemplate.opsForValue().get(“customer“+username); if(o!=null){ customer=(Customer)o;//缓存空间内有值则转换值未Customer类型 }else { customer = customerRepository.findByUsername(username); //调用Dao层根据名字查找的方法
if(customer!=null){ redisTemplate.opsForValue().set(“customer
“+username,customer); } } return customer; }

  1. public List<Authority> getCustomerAuthority(String username){
  2. List<Authority> authorities=null;
  3. Object o = redisTemplate.opsForValue().get("authorities_"+username);
  4. if(o!=null){
  5. authorities=(List<Authority>)o;
  6. }else {
  7. authorities=authorityRepository.findAuthoritiesByUsername(username);
  8. if(authorities.size()>0){
  9. redisTemplate.opsForValue().set("authorities_"+username,authorities);
  10. } }
  11. return authorities;}

}

  1. 6. 自定义一个UserDetailsService接口的实现类,通过loadUserByUsername(String s)方法调用用户业务处理类中已有的方法进行用户详情封装
  2. ```java
  3. package com.example.demo.Service;
  4. import java.util.List;
  5. import java.util.stream.Collectors;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.security.core.authority.SimpleGrantedAuthority;
  8. import org.springframework.security.core.userdetails.*;
  9. import org.springframework.stereotype.Service;
  10. import org.springframework.web.client.HttpClientErrorException.NotFound;
  11. import com.example.demo.entity.Authority;
  12. import com.example.demo.entity.Customer;
  13. //自定义一个UserDetailsService接口实现类进行用户认证信息封装
  14. @Service
  15. public class UserDetailsServiceImpl implements UserDetailsService {
  16. @Autowired
  17. private CustomerService customerService;
  18. @Override
  19. //面向接口的类型,通过源码规定好的返回的类型进行修改。
  20. public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
  21. //通过业务方法获取用户以及权限信息
  22. Customer customer = customerService.getCustomer(s);
  23. List<Authority> authorities = customerService.getCustomerAuthority(s);
  24. //对用户权限进行封装
  25. List<SimpleGrantedAuthority> list = authorities.stream()
  26. .map(authority->new SimpleGrantedAuthority(authority.getAuthority()))
  27. .collect(Collectors.toList());
  28. //返回封装的UserDetails用户详情类
  29. if(customer!=null) {
  30. UserDetails userDetails = new User(customer.getUsername(),customer.getPassword(),list);
  31. return userDetails;
  32. }else {
  33. //如果查询的用户不存在(用户名不存在),必须抛出异常
  34. throw new UsernameNotFoundException("当前用户不存在!");
  35. }
  36. }
  37. }
  1. 使用UserDetailsService进行身份认证 ```java package com.example.demo.config;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 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 com.example.demo.Service.UserDetailsServiceImpl;

//开启MVC Security安全支持 @EnableWebSecurity
//继承抽象类WebSecurityConfigurerAdapter,通过重写其中方法实现自定义 public class SecurityConfig extends WebSecurityConfigurerAdapter{
/*//Autowired相当于new,来自于springboot本身,用于注入组件 @Autowired //装配了DataSource数据源作为属性 private DataSource dataSource; //@Override 注解用于指定方法必须重写,让编译器了解该方法必须重写,如果不重写就报错 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception{

  1. /*1. 内存实现身份认证
  2. //密码设置需要设置编码器
  3. BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
  4. //1.使用内存用户信息,作为测试使用。定义了一个common用户,一个vip用户,设定了默认账户密码
  5. auth.inMemoryAuthentication().passwordEncoder(encoder)
  6. .withUser("shitou").password(encoder.encode("123456")).roles("common")
  7. .and()
  8. .withUser("李四").password(encoder.encode("123456")).roles("vip");
  9. }
  10. //2. JDBC进行身份验证
  11. //密码设置需要设置编码器
  12. BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
  13. //使用JDBC进行身份认证
  14. //根据浏览器所输入的用户名寻找名字,密码权限
  15. String userSQL = "select username,password,valid from t_customer "+
  16. "where username = ?";
  17. String authoritySQL = "select c.username,a.authority from t_customer c,"+
  18. "t_authority a,t_customer_authority ca where "+
  19. "ca.customer_id=c.id and ca.authority_id=a.id and c.username =?";
  20. //auth调用jdbc权限管理的方法.编码器.数据源.执行SQL语句
  21. auth.jdbcAuthentication().passwordEncoder(encoder)
  22. .dataSource(dataSource)
  23. .usersByUsernameQuery(userSQL)
  24. .authoritiesByUsernameQuery(authoritySQL);
  25. */
  26. @Autowired
  27. private UserDetailsServiceImpl userDetailsService;
  28. @Override
  29. protected void configure(AuthenticationManagerBuilder auth)throws Exception{
  30. //密码设置需要编码器
  31. BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
  32. //3.使用UserDetailsService进行身份验证
  33. auth.userDetailsService(userDetailsService).passwordEncoder(encoder);

} }

```

  1. 效果测试

image.png
image.pngimage.pngimage.png