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

7.1 Spring Security介绍

springboot 安全配置es spring boot安全管理_java

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,完成项目创建:

springboot 安全配置es spring boot安全管理_springboot 安全配置es_02

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

springboot 安全配置es spring boot安全管理_spring_03

  • 3)查看index.html,拟实现功能:点击“飞驰人生”,调用detail/common/1.html,其他三个链接也则链接到不同页面。资源文件已导入
<!--<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>-->
  • 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,如图,通过链接,可以看到四个链接均可实现指向正确页面:

springboot 安全配置es spring boot安全管理_spring boot_04

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

springboot 安全配置es spring boot安全管理_springboot 安全配置es_05

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

7.3 MVC Security安全配置介绍

springboot 安全配置es spring boot安全管理_数据库_06

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<AuthenticationManagerBuilder> 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,开始测试。用户名输错效果如图

springboot 安全配置es spring boot安全管理_spring_07

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

springboot 安全配置es spring boot安全管理_spring_08

7.4.2 JDBC身份认证

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

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

springboot 安全配置es spring boot安全管理_java_09

  • (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运行效果 如图:

springboot 安全配置es spring boot安全管理_java_10

  1. 在pom.xml中添加JDBC连接数据库的依赖启动器
<!--JDBC数据库连接启动器-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--MySQL数据加接驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
  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<AuthenticationManagerBuilder> 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页面。

springboot 安全配置es spring boot安全管理_spring_11

7.4.3 UserDetailsService身份认证

springboot 安全配置es spring boot安全管理_数据库_12

  • 1.准备工作1:pom.xml的dependency设置添加三个Security与Thymeleaf整合实现前端页面安全访问控制,Redis缓存启动器和Spring Data JPA操作数据库
<!-- Security与Thymeleaf整合实现前端页面安全访问控制 -->
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<!-- Redis缓存启动器-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Spring Data JPA操作数据库  -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
  • 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<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;
    }
}
  • 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,Integer> {
    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<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);

}
  • 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<Authority> authorities = customerService.getCustomerAuthority(s);
        // 对用户权限进行封装
        List<SimpleGrantedAuthority> 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<AuthenticationManagerBuilder> 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,进行测试,
    使用账号和密码登陆,出现如图所示错误,因涉及缓存,需要进行序列化:

springboot 安全配置es spring boot安全管理_数据库_13

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

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

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

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

重启项目测试:

错误用户名登陆:

springboot 安全配置es spring boot安全管理_spring_14

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

springboot 安全配置es spring boot安全管理_springboot 安全配置es_15

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

7.5 自定义用户授权管理

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

7.5.1 自定义用户访问控制

springboot 安全配置es spring boot安全管理_java_16


springboot 安全配置es spring boot安全管理_spring boot_17


springboot 安全配置es spring boot安全管理_spring boot_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/,因为允许访问,效果如图:

springboot 安全配置es spring boot安全管理_spring_19

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

springboot 安全配置es spring boot安全管理_spring_20

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

springboot 安全配置es spring boot安全管理_spring_21

7.5.2 自定义用户登录

springboot 安全配置es spring boot安全管理_spring_22

springboot 安全配置es spring boot安全管理_spring boot_23

演示实践:

  • 1.自定义用户登陆界面:这里我们导入相关的静态资源,请确保login和static文件夹已导入。如图:
  • 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)首页要求登录:

springboot 安全配置es spring boot安全管理_数据库_24

2)点击"请登录",输入错误账号密码,效果如图。注意网址变化:

springboot 安全配置es spring boot安全管理_数据库_25

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

7.5.3 自定义用户退出

springboot 安全配置es spring boot安全管理_数据库_26

  • 1.添加自定义用户退出链接,这部分体现在index.html页面
<form th:action="@{/mylogout}" method="post">
   <input th:type="submit" th:value="注销" />
</form>

springboot 安全配置es spring boot安全管理_springboot 安全配置es_27

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

springboot 安全配置es spring boot安全管理_数据库_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进行登陆,如图:

springboot 安全配置es spring boot安全管理_数据库_29

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

springboot 安全配置es spring boot安全管理_java_30

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

springboot 安全配置es spring boot安全管理_springboot 安全配置es_31

7.5.5 记住我功能

springboot 安全配置es spring boot安全管理_数据库_32

  • 1.基于简单加密Token的方式
  • 1)login.html页面新增一个记住我的复选框,导入的静态资源已存在,代码如下:
<div class="checkbox mb-3">
    <label>
        <input type="checkbox" name="rememberme"> 记住我
    </label>
</div>
  • 2)打开SecurityConfig类,重写configure(HttpSecurity http)方法进行记住 我的功能配置。代码如下:
// 定制Remember-me记住我功能
        http.rememberMe()
                .rememberMeParameter("rememberme")
                .tokenValiditySeconds(200);
  • 重启项目,浏览器访问http://localhost:8080,并登陆,勾选“记住我”:

    访问http://localhost:8080/detail/common/1正常,关闭浏览器再打开,直接访问http://localhost:8080/detail/common/1,仍正常访问,说明cookie起作用
  • 2.基于持久化Token的方式

    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);

效果如图:

springboot 安全配置es spring boot安全管理_数据库_33

  • 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;
}
  • 重启项目,测试过程类似上面。效果如图:

springboot 安全配置es spring boot安全管理_springboot 安全配置es_34

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

springboot 安全配置es spring boot安全管理_java_35

springboot 安全配置es spring boot安全管理_java_36

  • 1.CSRF防护功能关闭

springboot 安全配置es spring boot安全管理_spring boot_37

  • 1)在resources/templates目录中创建一个名为csrf的文件夹,并创建模拟修改用户账号信息的Thymeleaf页面csrfTest.html用来进行CSRF测试,内容如下:
<!DOCTYPE html>
<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="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>-->
        用户名: <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,登录后再访问,效果如图:

springboot 安全配置es spring boot安全管理_spring_38

  • 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值。

<form method="post" th:action="@{/updateUser}">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
    用户名: <input type="text" name="username" /><br />
    密  码: <input type="password" name="password" /><br />
    <button type="submit">修改</button>
</form>

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

springboot 安全配置es spring boot安全管理_java_39

点击修改报错,如图:

springboot 安全配置es spring boot安全管理_springboot 安全配置es_40

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

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

```

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

springboot 安全配置es spring boot安全管理_java_41

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

springboot 安全配置es spring boot安全管理_springboot 安全配置es_42


springboot 安全配置es spring boot安全管理_数据库_43


springboot 安全配置es spring boot安全管理_spring boot_44

7.6 Security管理前端页面

  • 1.添加thymeleaf-extras-springsecurity5依赖启动器
<!-- Security与Thymeleaf整合实现前端页面安全访问控制 -->
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
  • 2.修改前端页面,使用Security相关标签进行页面控制
    打开index.html,引入Security安全标签,并在页面中根据需要使用Security标签进行显示控制,修改后的index.html内容如下
<!DOCTYPE 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')"><!--判断用户权限是否是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.重启服务器,可以看到不同权限用户显示不同的电影。
    普通游客:

    普通用户:

springboot 安全配置es spring boot安全管理_springboot 安全配置es_45

VIP用户:

springboot 安全配置es spring boot安全管理_spring boot_46