Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)

注:实践内容参考人民邮电出版社的教程《 Spring Boot企业级开发教程》作者:黑马程序员,上传本文仅以实践过程以供大家共同学习解决问题,如有侵权不当行为,请告知后,我会更正或删除。

7.1 Spring Security介绍

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第1张图片

7.2 Spring Security快速入门

这里通过一个设置一个网页访问,成功后添加Spring Security依赖启动器来查看效果。

步骤1:创建项目

  • 1)使用Spring Initializr方式创建一个Spring Boot项目chapter07,这里将Group设置为com.itheima, Artifact设置为chapter07,Package设置 为com.itheima,在Dependencies依赖选择项中Thymeleaf依赖和Spring Web依赖。依赖选择如图,设置存储路径后按Finish,完成项目创建:

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第2张图片

  • 2)引入页面html资源文件(资源包下载:https://pan.baidu.com/s/1E01FeSOo2KcdkmnOW0EhUA),分别给templates和static文件夹导入资源,如图:

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第3张图片

  • 3)查看index.html,拟实现功能:点击“飞驰人生”,调用detail/common/1.html,其他三个链接也则链接到不同页面。资源文件已导入

    
    	

    普通电影

    VIP专享

  • 4)编写Web控制层。在项目com.itheima下创建名为controller的包,并在该包下创建一个用于页面请求的控制类FileController,代码如下:

    package com.itheima.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    
    @Controller //步骤1:加入Controller注解
    public class FileController {
        @GetMapping("/detail/{type}/{path}")//步骤2:设置toDetail方法的访问路径
        //步骤3:编写方法:toDetail(),注意使用@PathVariable让两个参数分别对应路径中的type和path
        public String toDetail(@PathVariable("type") String type,@PathVariable("path") String path){
            //步骤4:完成页面的跳转
            return "/detail/"+type+"/"+path;
        }
    
    }
    
  • 5)启动项目主程序,访问http://localhost:8080,可以访问到主页index.html,如图,通过链接,可以看到四个链接均可实现指向正确页面:

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第4张图片

  • 6)开启安全管理效果测试: 在pom.xml添加引入Spring Security依赖启动器spring-boot-starter-security:

    
        org.springframework.boot
        spring-boot-starter-security
    
    
    
  • 7)重启项目,可以看到控制台会自动生成一个安全密码(每次启动项目都是随机生成),访问http://localhost:8080,网址自动变为:访问http://localhost:8080/login,说明添加依赖启动器后,项目实现了Spring Security的自动化配置 ,并且具有了一些默认的安全管理功能。效果如图:

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第5张图片

  • 8)需要正确登陆,其中用户名为user,密码为控制台可查看的随机生成的密码,登陆后会跳转到index.html.至此,我们就完成了Spring Security快速入门的效果设置。

7.3 MVC Security安全配置介绍

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第6张图片

7.4 自定义用户认证

通过定义WebSecurityConfigurerAdapter类型的Bean组件,并重写configure(AuthenticationManagerBuilder auth)方法,可以自定义用户认证。Spring Security提供了多种自定义认证方法,包括有:内存身份认证(In-Memory Authentication),JDBC身份认证(JDBC Authentication),LDAP身份认证(LDAP Authentication)、身份认证提供商(Authentication Provider)和身份详情服务(UserDatailService)。下面选取其中3个比较有代表性的方式讲解如何实现自定义用户认证。

7.4.1 内存身份认证

内存身份认证(In-Memory Authentication)是最简单的身份认证方式主要用于Security安全认证体验和测试,我们在只需要发在重写的configure(AuthentictionManagerBulider auth)方法中定义测试用户即可。

  • 1)自定义WebSecurityConfigurerAdapter配置类

    在项目com.itheima中创建名为config的包,并在该包下创建一个配置类:SecurityConfig,内容如图:

    package com.itheima.config;
    
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    
    @EnableWebSecurity //开启MVC Security安全支持,
    public class SecurityConfig extends WebSecurityConfigurerAdapter {//用于MVC Security自定义配置
    }
    
  • 2)使用内存身份认证

    在SecurityConfig中重写configure方法,并在该方法中使用内存身份认证的方式进行自定义用户认证。

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //设置密码编码器
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
    
        //构建用户
        final InMemoryUserDetailsManagerConfigurer authenticationManagerBuilderInMemoryUserDetailsManagerConfigurer = auth.inMemoryAuthentication().passwordEncoder(bCryptPasswordEncoder);
        //模拟两用户,设置用户名withUser,设置密码password,引用密码编码器加密,设置用户权限roles()
        authenticationManagerBuilderInMemoryUserDetailsManagerConfigurer.withUser("shitou").password(bCryptPasswordEncoder.encode("123")).roles("comment");
        authenticationManagerBuilderInMemoryUserDetailsManagerConfigurer.withUser("李四").password(bCryptPasswordEncoder.encode("321")).roles("vip");
    
    }
    
    
  • 3)效果测试,重启项目,查看到控制台没有随机生成的密码了。访问http://localhost:8080,开始测试。用户名输错效果如图

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第7张图片

用户名输入正确跳转到index.html,可以查看四个影片链接。

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第8张图片

7.4.2 JDBC身份认证

JDBC身份认证(JDBC Authentication)是通过JDBC连接数据库对已有用户身份进行认证,下面通过一个案例讲解:

    1. 数据准备
    • (1)启动之前创建的名为springbootdata的数据库

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第9张图片

  • (2)在该数据库中创建 3个表t_customer(用户表)、t_authority(角色权限表)和t_customer_authority(中间表:用户对应的角色权限),并预先插入几条测试数据。对应的SQL语句:

    # 选择使用数据库
    USE springbootdata;
    # 创建表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);
    
    
  • (3)使用SQLyog运行效果 如图:

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第10张图片

    1. 在pom.xml中添加JDBC连接数据库的依赖启动器
    
    
        org.springframework.boot
        spring-boot-starter-jdbc
    
    
    
        mysql
        mysql-connector-java
        runtime
    
    
    
    1. 在全局配置application.properties文件进行数据库连接配置
    spring.datasource.url=jdbc:mysql://localhost:3306/springbootdata?serverTimezone=UTC
    spring.datasource.username=root
    spring.datasource.password=root
    
    
  • 4)使用JDBC进行身份认证

    在以上准备工作完成后,在configure方法中使用JDBC身份认证的方式 进行自定义用户认证。SecurityConfig.java内容 如下:

    package com.itheima.config;
    
    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 //开启MVC Security安全支持,
    public class SecurityConfig extends WebSecurityConfigurerAdapter {//用于MVC Security自定义配置
        @Autowired
        private DataSource dataSource;
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            //设置密码编码器,从Spring Security 5开始,自定义用户认证必须设置密码编码器用于保护密码,并提供了多种密码编码器
            BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
    
            /*注释内存身份认证//构建用户
            final InMemoryUserDetailsManagerConfigurer authenticationManagerBuilderInMemoryUserDetailsManagerConfigurer = auth.inMemoryAuthentication().passwordEncoder(bCryptPasswordEncoder);
            //模拟两用户,设置用户名withUser,设置密码password,引用密码编码器加密,设置用户权限roles()
            authenticationManagerBuilderInMemoryUserDetailsManagerConfigurer.withUser("shitou").password(bCryptPasswordEncoder.encode("123")).roles("comment");
            authenticationManagerBuilderInMemoryUserDetailsManagerConfigurer.withUser("李四").password(bCryptPasswordEncoder.encode("321")).roles("vip");
    */
            //使用JDBC进行身份认证
     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);
        }
    }
    
    
  • 5)重启项目,效果测试,当访问 http://localhost:8080/,要求输入用户名和密码,需要输入与数据库的一致,测试可以看到,当输入shitou,密码:123456时,正常进入index.html页面。

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第11张图片

7.4.3 UserDetailsService身份认证

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第12张图片

  • 1.准备工作1:pom.xml的dependency设置添加三个Security与Thymeleaf整合实现前端页面安全访问控制,Redis缓存启动器和Spring Data JPA操作数据库

    
    
        org.thymeleaf.extras
        thymeleaf-extras-springsecurity5
    
    
    
        org.springframework.boot
        spring-boot-starter-data-redis
    
    
    
        org.springframework.boot
        spring-boot-starter-data-jpa
    
    
    
  • 2.准备工作2:在com.itheima创建包service,创建定义查询用户及角色信息的服务接口,实现对用户数据结合Redis缓存进行业务处理,CustomerService.java内容如下:

    package com.itheima.service;
    
    import com.itheima.domain.Customer;
    import com.itheima.domain.Authority;
    import com.itheima.repository.CustomerRepository;
    import com.itheima.repository.AuthorityRepository;
    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 getCustomerAuthority(String username){
            List authorities=null;
            Object o = redisTemplate.opsForValue().get("authorities_"+username);
            if(o!=null){
                authorities=(List)o;
            }else {
                authorities=authorityRepository.findAuthoritiesByUsername(username);
                if(authorities.size()>0){
                    redisTemplate.opsForValue().set("authorities_"+username,authorities);
                }
            }
            return authorities;
        }
    }
    
    
  • 3.准备工作3:对于CustomerService.java中引用的CustomerRepository和AuthorityRepository,我们在com.itheima下新建repository包,并分别建立这两个类。CustomerRepository.java代码:

    package com.itheima.repository;
    
    import com.itheima.domain.Customer;
    import org.springframework.data.jpa.repository.JpaRepository;
    
    public interface CustomerRepository extends JpaRepository {
        Customer findByUsername(String username);
    }
    
    

    AuthorityRepository.java代码:

    package com.itheima.repository;
    
    import com.itheima.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 {
        @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 findAuthoritiesByUsername(String username);
    
    }
    
    
  • 4.准备工作4:由于reposity中用到实体类Customer和Authority,我们在com.itheima下建立包domain,并在包下建立实体类Customer和Authority,Customer.java代码:

    package com.itheima.domain;
    
    
    import javax.persistence.*;
    
    @Entity(name = "t_customer")
    public class Customer {
        @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 +
                    '}';
        }
    }
    
    

    Authority.java代码:

    package com.itheima.domain;
    
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    
    @Entity(name = "t_authority ")
    public class Authority {
        @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 + '\'' +
                    '}';
        }
    }
    
    
  • 5.准备工作5:运行redis服务程序:redis-server.exe,在application.properties添加redis连接

    spring.redis.host=127.0.0.1
    spring.redis.port=6379
    spring.redis.password=
    
  • 6.在service包中自定义一个UserDetailsService接口实现类进行用户认证信息封装,UserDetailsServiceImpl.java内容如下:

    package com.itheima.service;
    
    import com.itheima.domain.Authority;
    import com.itheima.domain.Customer;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.*;
    import org.springframework.stereotype.Service;
    import java.util.List;
    import java.util.stream.Collectors;
    
    /**
     * @Classname UserDetailsServiceImpl
     * @Description 自定义一个UserDetailsService接口实现类进行用户认证信息封装
     * @Date 2019-3-5 16:08
     * @Created by CrazyStone
     */
    @Service
    public class UserDetailsServiceImpl implements UserDetailsService {
        @Autowired
        private CustomerService customerService;
        @Override
        //重写loadUserByUsername方法用于借助CustomerService业务处理类获取用户信息和权限信息,并通过UserDetails进行认证用户信息封装
        public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
            // 通过业务方法获取用户及权限信息
            Customer customer = customerService.getCustomer(s);
            List authorities = customerService.getCustomerAuthority(s);
            // 对用户权限进行封装
            List list = authorities.stream().map(authority -> new SimpleGrantedAuthority(authority.getAuthority())).collect(Collectors.toList());
            // 返回封装的UserDetails用户详情类
            if(customer!=null){
                UserDetails userDetails= new User(customer.getUsername(),customer.getPassword(),list);
                return userDetails;
            } else {
                // 如果查询的用户不存在(用户名不存在),必须抛出此异常
                throw new UsernameNotFoundException("当前用户不存在!");
            }
        }
    }
    
    
  • 7.在configure方法中(先注释掉JDBC身份认证方式),使用UserDetailsService身份认证进行自定义用户认证:

    @EnableWebSecurity //开启MVC Security安全支持,
    public class SecurityConfig extends WebSecurityConfigurerAdapter {//用于MVC Security自定义配置
        @Autowired
        private DataSource dataSource;
        @Autowired
        private UserDetailsServiceImpl userDetailsService;
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            //设置密码编码器,从Spring Security 5开始,自定义用户认证必须设置密码编码器用于保护密码,并提供了多种密码编码器
            BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
    
            /*//构建用户
            final InMemoryUserDetailsManagerConfigurer authenticationManagerBuilderInMemoryUserDetailsManagerConfigurer = auth.inMemoryAuthentication().passwordEncoder(bCryptPasswordEncoder);
            //模拟两用户,设置用户名withUser,设置密码password,引用密码编码器加密,设置用户权限roles()
            authenticationManagerBuilderInMemoryUserDetailsManagerConfigurer.withUser("shitou").password(bCryptPasswordEncoder.encode("123")).roles("comment");
            authenticationManagerBuilderInMemoryUserDetailsManagerConfigurer.withUser("李四").password(bCryptPasswordEncoder.encode("321")).roles("vip");
    */
            //使用JDBC进行身份认证
           /* // 2、使用JDBC进行身份认证
           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);
        */
    
           //使用UserDetailsService身份认证
            auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
        }
    }
    
    
  • 8.重启项目,通过浏览器访问:http://localhost:8080,进行测试,

    使用账号和密码登陆,出现如图所示错误,因涉及缓存,需要进行序列化:

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第13张图片

这里提示的是Customer没有实现序列化,修改实体类Customer.java,添加implements Serializable,实现序列化,如图:

public class Customer implements Serializable {//注释序列化

同时对另一实体类Authority也实现序列化,如图:

public class Authority implements Serializable {//注意序列化

重启项目测试:

错误用户名登陆:

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第14张图片

正确用户名如shitou,密码:123456登陆:

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第15张图片

至此,完成UserDetailsService身份认证实践。

7.5 自定义用户授权管理

从上面的案例演示中,我们看到可以实现不同权限的用户访问不同的效果或页面,接下来,我们针对Web应用中常见的自定义用户授权管理进行介绍:

7.5.1 自定义用户访问控制

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第16张图片
Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第17张图片
Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第18张图片

演示:

  • 1.在SecurityConfig.java中重写configure(HttpSecurity http)方法,代码如下:

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/").permitAll()//对“/”路径开头的不拦截
    .antMatchers("/detail/common/**").hasRole("common")//对路径为"/detail/common/"开头的进行拦截,角色为common的不拦截
    .antMatchers("detail/vip/**").hasRole("vip")//对路径为"/detail/vip/"开头的进行拦截,角色为vip的不拦截
    .anyRequest().authenticated()//除去以上的3个映射路径以外,其他的均要求登录
    .and().formLogin();//配置默认登陆页面
    }
    
    
  • 2.停止之前的项目后,重新启动项目启动类。浏览器访问:http://localhost:8080/,因为允许访问,效果如图:

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第19张图片

使用用户名:shitou,密码:123456,对应的权限:ROLE_common,登陆效果如图:

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第20张图片

访问“飞驰人生”链接,效果如图:

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第21张图片

7.5.2 自定义用户登录

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第22张图片Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第23张图片

演示实践:

  • 1.自定义用户登陆界面:这里我们导入相关的静态资源,请确保login和static文件夹已导入。如图:
    Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第24张图片

  • 2.自定义用户登录跳转

    在之前的FileController类中添加一个跳转到登录页面login.html的方法

    @GetMapping("/userLogin")//与login.html中的表单跳转th:action="@{/userLogin}的路径一致
    public String toLoginPage(){
        return "/login/login";//跳转到静态资源login文件夹的login.html页面
    }
    
    
  • 3.自定义用户登录控制

    打开SecurityConfig类,重写configure方法:

    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/").permitAll()//对“/”路径开头的不拦截
                // 需要对static文件夹下login文件夹对应的静态资源进行统一放行
                .antMatchers("/login/**").permitAll()
                .antMatchers("/detail/common/**").hasRole("common")//对路径为"/detail/common/"开头的进行拦截,角色为common的不拦截
                .antMatchers("detail/vip/**").hasRole("vip")//对路径为"/detail/vip/"开头的进行拦截,角色为vip的不拦截
                .anyRequest().authenticated()//除去以上的3个映射路径以外,其他的均要求登录
                .and().formLogin();//配置默认登陆页面
    
        // 自定义用户登录控制
        http.formLogin()
                .loginPage("/userLogin").permitAll()//配置登陆路径,跟页面中的设置一致
                .usernameParameter("name").passwordParameter("pwd")//接收前台传递的用户名写密码,跟login.html页面的保持一致
                .defaultSuccessUrl("/")//登陆成功跳转到首页
                .failureUrl("/userLogin?error");//登陆失败返回登陆页面参数为?error
    }
    
    
  • 重启项目,浏览器访问:http://localhost:8080,效果测试:

    1)首页要求登录:

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第25张图片

2)点击"请登录",输入错误账号密码,效果如图。注意网址变化:
Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第26张图片

3)用shitou,密码:123456登陆,效果跟前面的登陆一样。

7.5.3 自定义用户退出

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第27张图片

  • 1.添加自定义用户退出链接,这部分体现在index.html页面

在这里插入图片描述

  • 2.自定义用户退出控制

    在SecurityConfig类中添加自定义用户退出控制方法:

    // 自定义用户退出控制
    http.logout()
            .logoutUrl("/mylogout")//跟index.html"注销"表单中的路径一致
            .logoutSuccessUrl("/");//注销成功后的跳转页面为根目录的首页
    
    
  • 3.重启服务,测试注销是否正常使用。

7.5.4 登录用户信息获取

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第28张图片

  • 使用HttpSession获取用户信息,在FilmController控制类中新增一个用于获取当前会话用户信息的getUser()方法,代码如下:

    /**
         * 通过传统的HttpSession获取Security控制的登录用户信息
         */
        @GetMapping("/getuserBySession")
        @ResponseBody
        public void getUser(HttpSession session) {
            // 从当前HttpSession获取绑定到此会话的所有对象的名称
            Enumeration<String> names = session.getAttributeNames();
            while (names.hasMoreElements()){
                // 遍历获取HttpSession中会话名称
                String element = names.nextElement();
                // 获取HttpSession中的应用上下文
                SecurityContextImpl attribute = (SecurityContextImpl) session.getAttribute(element);//默认返回一个Object对象,本质是SecurityContextImpl类,所以进行强制转换
                System.out.println("element: "+element);
                System.out.println("attribute: "+attribute);
                // 获取用户相关信息
                Authentication authentication = attribute.getAuthentication();
                UserDetails principal = (UserDetails)authentication.getPrincipal();//默认返回一个Object对象,本质是UserDetails类,进行强制转换,其中包括有用户名、密码、权限、是否过期等
                System.out.println(principal);
                System.out.println("username: "+principal.getUsername());
            }
        }
    
  • 2.使用SecurityContextHolder获取用户信息

    Spring Security针对拦截的登录用户专门提供了一个SecurityContextHolder类,来获取Spring Security的应用上下文SecurityContex,进而获取封装的用户信息。在FilmController控制类中新增一个获取当前会话用户信息的getUser2()方法,代码如下:

    /**
         * 通过Security提供的SecurityContextHolder获取登录用户信息
         */
        @GetMapping("/getuserByContext")
        @ResponseBody
        public void getUser2() {
            // 获取应用上下文
            SecurityContext context = SecurityContextHolder.getContext();
            System.out.println("userDetails: "+context);
            // 获取用户相关信息
            Authentication authentication = context.getAuthentication();
            UserDetails principal = (UserDetails)authentication.getPrincipal();
            System.out.println(principal);
            System.out.println("username: "+principal.getUsername());
        }
    
  • 3.重启项目,浏览器运行http://localhost:8080,使用用户名shitou和密码:123456进行登陆,如图:

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第29张图片

访问http://localhost:8080/getuserByContext,效果如图

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第30张图片

以Debug模式启动项目(application.properties中添加一句debug=true,或命令行java –jar xxx.jar --debug,重启项目即可),在登陆用户后同一浏览器访问http://localhost:8080/getuserBySession,则在控制台可以查看到效果如图:

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第31张图片

7.5.5 记住我功能

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第32张图片

  • 1.基于简单加密Token的方式
    Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第33张图片

    • 1)login.html页面新增一个记住我的复选框,导入的静态资源已存在,代码如下:

    • 2)打开SecurityConfig类,重写configure(HttpSecurity http)方法进行记住 我的功能配置。代码如下:

              // 定制Remember-me记住我功能
              http.rememberMe()
                      .rememberMeParameter("rememberme")
                      .tokenValiditySeconds(200);
      
      
    • 重启项目,浏览器访问http://localhost:8080,并登陆,勾选“记住我”:
      Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第34张图片

      访问http://localhost:8080/detail/common/1正常,关闭浏览器再打开,直接访问http://localhost:8080/detail/common/1,仍正常访问,说明cookie起作用

  • 2.基于持久化Token的方式
    Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第35张图片

    1)在数据库中对记住我功能中创建持久化Token存储的数据表

# 记住我功能中创建持久化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);


效果如图:

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第36张图片

  • 2)打开SecurityConfig类,重写configure(HttpSecurity http)方法中的进行记住我的功能。代码如下:

     // 定制Remember-me记住我功能
            http.rememberMe()
                    .rememberMeParameter("rememberme")
                    .tokenValiditySeconds(200)
            // 对cookie信息进行持久化管理
                    .tokenRepository(tokenRepository());
        }
    
    

    新增一个持久化Token存储方法tokenRepository(),代码如下:

    /**
     * 持久化Token存储
     */
    @Bean
    public JdbcTokenRepositoryImpl tokenRepository(){
        JdbcTokenRepositoryImpl jr=new JdbcTokenRepositoryImpl();
        jr.setDataSource(dataSource);
        return jr;
    }
    
    
  • 重启项目,测试过程类似上面。效果如图:

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第37张图片

可以看到,数据表实现持久化Token存储。
  • 7.5.6 CSRF防护功能

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第38张图片

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第39张图片

  • 1.CSRF防护功能关闭

在这里插入图片描述

  • 1)在resources/templates目录中创建一个名为csrf的文件夹,并创建模拟修改用户账号信息的Thymeleaf页面csrfTest.html用来进行CSRF测试,内容如下:

    
    <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>用户修改title>
    head>
    <body>
    <div align="center">
        <form method="post" th:action="@{/updateUser}">
          
            用户名: <input type="text" name="username" /><br />  码: <input type="password" name="password" /><br />
            <button type="submit">修改button>
        form>
    div>
    body>
    html>
    
  • 2)编写后台控制层方法,在controller的包下,创建一个用于CSRF页面请求测试的控制类CSRFController,内容如下:

    package com.itheima.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.*;
    
    import javax.servlet.http.HttpServletRequest;
    
    @Controller
    public class CSRFController {
        //向用户修改页面跳转
        @GetMapping("/toUpdate")
        public String toUpdate(){
            return "csrf/csrfTest";
        }
        //接收页面请求的方法
        @ResponseBody
        @PostMapping("/updateUser")
        public String updateUse(@RequestParam String username,@RequestParam String password,@RequestParam HttpServletRequest request){
            System.out.println("username:"+username);
            System.out.println("password:"+password);
            return "ok";
        }
    }
    
    
  • 3)重启项目,浏览器访问:localhost:8080//toUpdate测试,需要登录,使用李四,密码123456,登录后再访问,效果如图:

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第40张图片

  • 4)点击修改,因默认的CSRF防护原因,访问失败。

  • 5)需要关闭有两方法:(1)在SecurityConfig的configure(HttpSecurity http)方法中添加一行代码关闭,但不建议这样操作,安全性会降低:

    // 可以关闭Spring Security默认开启的CSRF防护功能
    //        http.csrf().disable();
    
    

    (2)在csrfTest.html中添加隐藏域来携带Security提供的CSRF Token信息,其中,th:name="${_csrf.parameterName}" 会获取Security默认提供的CSRF Token对应的key值,

    th:value="${_csrf.token}"会获取Security默认随机生成的CSRF Token对应的value值。

    用户名:
    密  码:

    重启项目,浏览器访问http://localhost:8080/toUpdate,使用账号:李四,密码:123456,登录后如图:

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第41张图片

点击修改报错,如图:

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第42张图片

request参数默认为必传参数,对CSRFController中的updateUser方法头部修正如下:

```
public String updateUser(@RequestParam String username,@RequestParam String password, @RequestParam(required = false) HttpServletRequest request){

```

重启项目,再行测试,点修改时会返回OK

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第43张图片

  • 针对Ajax类型的数据修改请求请参考课本:

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第44张图片
Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第45张图片
Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第46张图片

7.6 Security管理前端页面

  • 1.添加thymeleaf-extras-springsecurity5依赖启动器

    
    
        org.thymeleaf.extras
        thymeleaf-extras-springsecurity5
    
    
    
  • 2.修改前端页面,使用Security相关标签进行页面控制

    打开index.html,引入Security安全标签,并在页面中根据需要使用Security标签进行显示控制,修改后的index.html内容如下

    
    <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
    <head>
       <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
       <title>影视直播厅title>
    head>
    <body>
    <h1 align="center">欢迎进入电影网站首页h1>
    <div sec:authorize="isAnonymous()">
       <h2 align="center">游客您好,如果想查看电影<a th:href="@{/userLogin}">请登录a>h2>
    div>
    <div sec:authorize="isAuthenticated()">
       <h2 align="center"><span sec:authentication="name" style="color: #007bff">span>您好,您的用户权限为<span sec:authentication="principal.authorities" style="color:darkkhaki">span>,您有权观看以下电影h2>
       <form th:action="@{/mylogout}" method="post">
          <input th:type="submit" th:value="注销" />
       form>
    div>
    <hr>
    <div sec:authorize="hasRole('common')">
       <h3>普通电影h3>
       <ul>
          <li><a th:href="@{/detail/common/1}">飞驰人生a>li>
          <li><a th:href="@{/detail/common/2}">夏洛特烦恼a>li>
       ul>
    div>
    <div sec:authorize="hasAuthority('ROLE_vip')">
       <h3>VIP专享h3>
       <ul>
          <li><a th:href="@{/detail/vip/1}">速度与激情a>li>
          <li><a th:href="@{/detail/vip/2}">猩球崛起a>li>
       ul>
    div>
    body>
    html>
    
  • 3.重启服务器,可以看到不同权限用户显示不同的电影。

    普通游客:
    Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第47张图片

    普通用户:

Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第48张图片

VIP用户:
Spring Boot 实践之十 Spring Boot 安全管理(Spring Security快速入门/自定义用户认证/自定义用户授权管理/Security管理前端页面)_第49张图片

你可能感兴趣的:(Spring,Boot)