7-Spring Boot的安全管理

一、Spring Security基础

1.Spring Security介绍

a.Spring Security是基于Spring生态圈的,用于提供安全访问控制解决方案的框架。

b.Spring Security的安全管理有两个重要概念,分别是Authentication(认证)和Authorization(授权)。

2.Spring Boot整合Spring Security实现的安全管理功能

a.MVC Security是Spring Boot整合Spring MVC框架搭建的Web应用的安全管理。

b.WebFlux Security是Spring Boot整合Spring WebFlux框架搭建的Web应用的安全管理。

c.OAuth2是大型项目的安全管理框架,可以实现第三方认证、单点登录等功能。

d.Actuator Security用于对项目的一些运行环境提供安全监控,例如Health健康信息、Info运行信息等,它主要作为系统指标供运维人员查看管理系统的运行情况。

二、Spring Security入门

1.基础环境搭建

a.创建Spring Boot项目

引入Web和Thymeleaf的依赖启动器

7-Spring Boot的安全管理_第1张图片

b.引入页面Html资源文件

在项目的resources下templates目录中,引入案例所需的资源文件,项目结构如下

7-Spring Boot的安全管理_第2张图片

c.编写Web控制层

@Controller
public class FilmController {

    //  影片详情页
    @GetMapping("/detail/{type}/{path}")
    public String toDetail(@PathVariable("type")String type, @PathVariable("path")String path) {

        return "detail/"+type+"/"+path;

    }

}

至此,使用Spring Boot整合Spring MVC框架实现了一个传统且简单的Web项目,

目前项目没有引入任何的安全管理依赖,也没有进行任何安全配置,

项目启动成功后,通过http://localhost:8080访问首页,单击影片进入详情详情页。

2.开启安全管理效果测试

a.添加spring-boot-starter-security启动器

一旦项目引入spring-boot-starter-security启动器,MVC Security和WebFlux Security负责的安全功能都会立即生效


    org.springframework.boot
    spring-boot-starter-security

b.项目启动测试

项目启动时会在控制台Console中自动生成一个安全密码

如果是热部署重启项目,可能不会有安全密码,那就关闭项目,再启动

浏览器访问http://localhost:8080/查看项目首页,会跳转到一个默认登录页面。

7-Spring Boot的安全管理_第3张图片

因为添加了Security依赖后,会进行spring security的自动化配置,需要先登录,才能访问首页,Spring Security会自带一个默认的登录页面。

随意输入一个错误的用户名和密码,会出现错误提示

7-Spring Boot的安全管理_第4张图片

Security会默认提供一个可登录的用户信息,其中用户名为user,密码随机生成,

这个密码会随着项目的每次启动随机生成并打印在控制台上,在登录页面输入用户名和密码。

这种默认安全管理方式存在诸多问题,例如:

只有唯一的默认登录用户user、密码随机生成且过于暴露、登录页面及错误提示页面不是我们想要的等。

三、MVC Security安全配置

1.MVC Security安全配置简介

项目引入spring-boot-starter-security依赖启动器,MVC Security安全管理功能就会自动生效,其默认的安全配置是在SecurityAutoConfiguration和UserDetailsServiceAutoConfiguration中实现的。

SecurityAutoConfiguration导入并自动化配置SpringBootWebSecurityConfiguration用于启动Web安全管理.

UserDetailsServiceAutoConfiguration用于配置用户身份信息.

这两个类的位置:

先看spring-boot-autoconfigure-2.0.7.RELEASE.jar下的/META-INF/spring.factories文件,发现org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\

org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\

发现在org.springframework.boot.autoconfigure.security.servlet这个包下,

7-Spring Boot的安全管理_第5张图片

2.关闭Sercurity提供的Web应用默认安全配置

1.要完全关闭Security提供的Web应用默认安全配置,可以自定义WebSecurityConfigurerAdapter类型的Bean组件以及自定义UserDetailsService、AuthenticationProvider或AuthenticationManager类型的Bean组件。

2.另外,可以通过自定义WebSecurityConfigurerAdapter类型的Bean组件来覆盖默认访问规则。

3.WebSecurityConfigurerAdapter类的主要方法及说明

方法

描述

configure(AuthenticationManagerBuilder auth)

定制用户认证管理器来实现用户认证

configure(HttpSecurity http)

定制基于HTTP请求的用户访问控制

四、自定义用户认证

1.内存身份认证

a.自定义WebSecurityConfigurerAdapter配置类

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

}

注:

@EnableWebSecurity注解是一个组合注解,主要包括@Configuration注解、@Import({WebSecurityConfiguration.class, SpringWebMvcImportSelector.class})注解和@EnableGlobalAuthentication注解

b.使用内存进行身份认证

SecurityConfig类中重写configure(AuthenticationManagerBuilder auth)方法,并在该方法中使用内存身份认证的方式自定义了认证用户信息。定义用户认证信息时,设置了两个用户名和密码以及对应的角色信息。

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        auth.inMemoryAuthentication().passwordEncoder(encoder)
                .withUser("shitou").password(encoder.encode("123456")).roles("common")
                .and()
                .withUser("李四").password(encoder.encode("123456")).roles("vip");
    }

}

c.效果测试

重启项目进行效果测试,项目启动成功后,仔细查看控制台打印信息,发现没有默认安全管理时随机生成的密码了。通过浏览器访问http://localhost:8080/

2.JDBC身份认证

a.数据准备

7-Spring Boot的安全管理_第6张图片

# 选择使用数据库
USE springbootdata;

# 创建表t_customer并插入相关数据
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');

b.添加JDBC连接数据库的依赖启动器


    org.springframework.boot
    spring-boot-starter-jdbc


    mysql
    mysql-connector-java
    runtime

c.进行数据库连接配置

spring.datasource.url=jdbc:mysql://localhost:3306/springbootdata?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root

d.使用JDBC进行身份认证

在SecurityConfig 类中的configure(AuthenticationManagerBuilder auth)方法中使用JDBC身份认证的方式进行自定义用户认证,使用JDBC身份认证时,首先需要对密码进行编码设置(必须与数据库中用户密码加密方式一致);然后需要加载JDBC进行认证连接的数据源DataSource;最后,执行SQL语句,实现通过用户名username查询用户信息和用户权限。

    @Autowired
    private DataSource dataSource;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        BCryptPasswordEncoder encoder = 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(encoder).dataSource(dataSource)
      .usersByUsernameQuery(userSQL).authoritiesByUsernameQuery(authoritySQL);  

}

e.效果测试

先停止运行再启动或者直接relaunch下。在浏览器中http://localhost:8080/

如果热部署,可能出现下面错误

比如a bean of type 'javax.sql.DataSource' that could not be found.

3.UserDetailsService 身份认证

a.要用到jpa和redis,所以加入两个依赖,如下:


    org.springframework.boot
    spring-boot-starter-data-jpa


    org.springframework.boot
    spring-boot-starter-data-redis

b.处理类要用到其他类,项目结构如下

7-Spring Boot的安全管理_第7张图片

c.上述类的主要代码如下:

@Entity(name = "t_authority ")
public class Authority implements Serializable{

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private String authority ;
    
    
}
@Entity(name = "t_customer")
public class Customer implements Serializable{

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private String username;
    private String password;


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

}
public interface CustomerRepository extends JpaRepository {

    Customer findByUsername(String username);

}
//CustomerService业务处理类,用来通过用户名获取用户及权限信息
@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;
    }

d.定义UserDetailsService用于封装认证用户信息

UserDetailsService是Security提供的进行认证用户信息封装的接口,该接口提供的loadUserByUsername(String s)方法用于通过用户名加载用户信息。使用UserDetailsService进行身份认证的时,自定义一个UserDetailsService接口的实现类,通过loadUserByUsername(String s)方法调用用户业务处理类中已有的方法进行用户详情封装,返回一个UserDetails封装类,来供Security认证使用。

自定义一个接口实现类UserDetailsServiceImpl进行用户认证信息UserDetails封装,重写了UserDetailsService接口的loadUserByUsername(String s)方法,在该方法中,使用CustomerService业务处理类获取用户的用户信息和权限信息,并通过UserDetails进行认证用户信息封装。

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private CustomerService customerService;

    @Override
    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("当前用户不存在!");
        }

    }

}

e.在SecurityConfig中使用UserDetailsService进行身份认证

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        auth.userDetailsService(userDetailsService).passwordEncoder(encoder);

    }

f.效果测试

重启项目进行效果测试,项目启动成功后,通过浏览器访问http://localhost:8080/

五、自定义用户授权管理

1.自定义用户访问控制

a.在自定义配置类SecurityConfig中重写configure(HttpSecurity http)方法

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .antMatchers("/").permitAll()
        .antMatchers("/detail/common/**").hasRole("common")
        .antMatchers("/detail/vip/**").hasRole("vip")
        .anyRequest().authenticated()
        .and()
        .formLogin();
}

路径是“/”,直接放行。

路径是"/detail/common/**",只有用户角色是common才允许访问。

路径是"/detail/vip/**",只有用户角色是vip才允许访问。

其他请求要先登录认证后才放行。

b.效果测试

重启项目进行效果测试,项目启动成功后,通过浏览器访问http://localhost:8080/

7-Spring Boot的安全管理_第8张图片

项目首页单击普通电影或者VIP专享电影名称查询电影详情

7-Spring Boot的安全管理_第9张图片

在此登录界面输入普通用户的用户名和密码,访问普通电影

7-Spring Boot的安全管理_第10张图片

在项目首页中单击VIP专享电影名称查看影片详情,

在查看VIP电影详情时,页面会出现403 Forbidden的错误信息

7-Spring Boot的安全管理_第11张图片

2.自定义用户登录

a.在项目的resources/ templates目录下创建login/login.html,核心代码如下




    
    用户登录界面
    
    


    

通过

标签定义了一个用户登录功能,且登录数据以POST方式向“/userLogin”路径进行提交。

其中,登录表单中的用户名参数和密码参数可以自行定义;登录数据提交方式必须为post,提交的路径也可以自行定义。

b.拷贝静态资源文件到项目resources下的static目录中

7-Spring Boot的安全管理_第12张图片

c.自定义用户登录跳转

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

@GetMapping("/userLogin")
public String toLoginPage() {
    return "login/login";
}

注:Spring Security默认使用Get方式的“/login”请求用于向登录页面跳转,默认使用Post方式的“/login”请求用于对登录后的数据进行处理。因此,自定义用户登录控制时,需要提供向用户登录页面跳转的方法,且自定义的登录页跳转路径必须与数据处理提交路径一致

d.自定义用户登录控制

打开SecurityConfig类,重写configure(HttpSecurity http)方法实现用户登录控制

http.authorizeRequests().antMatchers("/").permitAll()
    // 需要对static文件夹下静态资源进行统一放行
    .antMatchers("/login/**").permitAll()
    .antMatchers("/detail/common/**").hasRole("common")
    .antMatchers("/detail/vip/**").hasRole("vip")
    .anyRequest().authenticated();

http.formLogin()
    .loginPage("/userLogin").permitAll()
    .usernameParameter("name").passwordParameter("pwd")
    .defaultSuccessUrl("/")
    .failureUrl("/userLogin?error");

上面.usernameParameter("name").passwordParameter("pwd")中的name和pwd与login.html中的文本框name属性值一致, 如下:

e.效果测试

重启项目进行效果测试,项目启动成功后,通过浏览器访问http://localhost:8080/

会直接进入到项目首页,在项目首页,单击普通电影或者VIP专享电影名称查询电影详情.

7-Spring Boot的安全管理_第13张图片

输入错误的账号信息

7-Spring Boot的安全管理_第14张图片

3.自定义用户退出

a.添加自定义用户退出链接

在index.html添加自定义用户退出链接


    

注:Spring Boot项目中引入Spring Security框架后会自动开启CSRF防护功能(跨站请求伪造防护,此处作为了解即可,后续小节将详细说明),用户退出时必须使用POST请求;如果关闭了CSRF防护功能,那么可以使用任意方式的HTTP请求进行用户注销。

b.自定义用户退出控制

在SecurityConfig类,在configure(HttpSecurity http)方法中添加如下代码进行用户退出控制

http.logout()
    .logoutUrl("/mylogout")
    .logoutSuccessUrl("/");

c.效果测试

重启项目进行效果测试,项目启动成功后,通过浏览器访问http://localhost:8080/

先访问影片详情自动跳转到自定义的用户登录页面login.html,在登录界面输入正确的用户名和密码后,如下:

7-Spring Boot的安全管理_第15张图片

单击影片详情中的“返回”链接回到项目首页(此时用户仍处于登录状态),单击首页中的“注销”链接进行用户注销

7-Spring Boot的安全管理_第16张图片

4.登录用户信息获取

a.使用HttpSession获取用户信息及测试

在FilmeController类中新增一个用于获取当前会话用户信息的getUser()方法,在该方法中通过获取当前HttpSession的相关方法遍历并获取了会话中的用户信息。

在获取认证用户信息时,使用了Authentication的getPrincipal()方法,默认返回的也是一个Object对象,其本质是封装用户信息的UserDetails封装类,其中包括有用户名、密码、权限、是否过期等。

 /**
     * 通过传统的HttpSession获取Security控制的登录用户信息
     * @param session
     */
@GetMapping("/getuserBySession")
@ResponseBody
public void getUser(HttpSession session) {
    Enumeration names = session.getAttributeNames();
    while (names.hasMoreElements()) {
        String element = names.nextElement();
        if (session.getAttribute(element) instanceof SecurityContextImpl) {
            SecurityContextImpl attribute = (SecurityContextImpl) session.getAttribute(element);
            System.out.println("element: " + element);
            System.out.println("attribute: " + attribute);
            Authentication authentication = attribute.getAuthentication();
            UserDetails principal = (UserDetails) authentication.getPrincipal();
            System.out.println(principal);
            System.out.println("username: " + principal.getUsername());
        }
    }
}

以Debug模式重启项目,浏览器访问http://localhost:8080/随意查看一个影片详情进行用户登录。

登录成功后,在保证当前浏览器未关闭的情况下,使用同一浏览器执行http://localhost:8080/getuserBySession来获取用户详情。

7-Spring Boot的安全管理_第17张图片

b.使用SecurityContextHolder获取用户信息

在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());
    }

5.记住我功能(记住时长:登录之后,注销之前或token失效之前)

a.基于简单加密Token的方式

在之前创建的项目用户登录页login.html中新增一个记住我功能勾选框

打开SecurityConfig类,重写configure(HttpSecurity http)方法进行记住我功能配置

http.rememberMe()
    .rememberMeParameter("rememberme")
    .tokenValiditySeconds(200);

基于简单加密Token的方式效果测试

重启项目进行效果测试,通过浏览器访问http://localhost:8080/userLogin

7-Spring Boot的安全管理_第18张图片

在登录界面输入正确的用户名和密码信息,同时勾选记住我功能,

就会默认跳转到项目首页index.html,关闭再打开浏览器访问项目首页,直接查看影片详情

b.基于持久化Token方式

需要在数据库中创建一个存储cookie信息的持续登录用户表

create table persistent_logins (
    username varchar(64) not null,
    series varchar(64) primary key,
    token varchar(64) not null,
    last_used timestamp not null
);

打开SecurityConfig类,重写configure(HttpSecurity http)方法的记住我功能 

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

注入dataSource,增加一个方法

     @Autowired
    private DataSource dataSource;

    /**
     * 持久化Token存储
     * @return
     */
    @Bean
    public JdbcTokenRepositoryImpl tokenRepository(){
        JdbcTokenRepositoryImpl jr=new JdbcTokenRepositoryImpl();
        jr.setDataSource(dataSource);
        return jr;
    }

基于持久化Token方式效果测试

重启项目进行效果测试,通过浏览器访问项目首页,输入正确的账户信息,

勾选记住我后,跳转到项目首页index.html,

查看数据库中创建的存储cookie信息的持续登录用户表。

重新打开刚才使用的浏览器,访问项目首页并直接查看影片详情,会发现无需重新登录就可以直接访问。此时,再次查看数据库中表数据信息。Token更新过了

返回到浏览器首页,单击首页上方的用户“注销”连接,在Token有效期内进行用户手动注销。此时,再次查看数据库中表数据信息。

7-Spring Boot的安全管理_第19张图片

6.CSRF防护功能

a.简介

跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。

CSRF攻击攻击原理及过程如下:

其中Web A为存在CSRF漏洞的网站,Web B为攻击者构建的恶意网站,User C为Web A网站的合法用户。

       1. 用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;

       2.在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;

       3. 用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;

       4. 网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;

       5. 浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行。

参考:CSRF攻击与防御(写得非常好)_挺住我先走的博客-CSDN博客_csrf漏洞

b.CSRF防护功能演示

创建数据修改页面

7-Spring Boot的安全管理_第20张图片



用户修改

    
        用户名:
        密  码:
             

编写后台控制层方法,编写的toUpdate()方法用于向用户修改页面跳转,updateUser()方法用于对用户修改提交数据处理。其中,在updateUser()方法中只是演示了获取的请求参数,没有具体的业务实现。

7-Spring Boot的安全管理_第21张图片

@Controller
public class CSRFController {

    // 向用户修改页跳转
    @GetMapping("/toUpdate")
    public String toUpdate() {
        return "csrf/csrfTest";
    }

    // 用户修改提交处理
    @ResponseBody
    @PostMapping(value = "/updateUser")
    public String updateUser(@RequestParam String username, @RequestParam String password,
                             HttpServletRequest request) {

        System.out.println(username);
        System.out.println(password);
        String csrf_token = request.getParameter("_csrf");
        System.out.println(csrf_token);
        return "ok";
    }

}

重启chapter07项目,通过浏览器访问http://localhost:8080/toUpdate,

由于前面配置了请求拦截,会先被拦截跳转到用户登录页面。

在用户登录页面输入正确的用户信息后,就会自动跳转到用户修改页面。

7-Spring Boot的安全管理_第22张图片

7-Spring Boot的安全管理_第23张图片

数据修改请求中没有携带CSRF Token(CSRF令牌)相关的参数信息,所以被认为是不安全的请求。

整合Spring Security安全框架后,项目默认启用了CSRF安全防护功能,项目中所有涉及到数据修改方式的请求都会被拦截。

7-Spring Boot的安全管理_第24张图片

c.直接关闭CSRF功能

配置类SecurityConfig,在重写的configure(HttpSecurity http)方法中进行关闭配置即可

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable();
    ...
}

d.配置Security需要的CSRF Token

先开启CSRF再测试

Spring Security提供的CSRF Token配置,主要有:

针对Form表单数据修改的CSRF Token配置

用户名:
密  码:

针对Ajax数据修改请求的CSRF Token配置(不做演示)

在页面标签中添加子标签,并配置CSRF Token信息


    
        
        
    

在具体的Ajax请求中获取子标签中设置的CSRF Token信息并绑定在HTTP请求头中进行请求验证

$(function () {
    var token = $("meta[name='_csrf']").attr("content");
    var header = $("meta[name='_csrf_header']").attr("content");
    
    $(document).ajaxSend(function(e, xhr, options) {
        xhr.setRequestHeader(header, token);
    });
});

演示:添加jquery.min.js,如下图

修改csrfTest.html,重启项目后再次访问/toUpdate



    
        
        
        
        
        
        用户修改
    
    
        
        
用户名:
密  码:

六、Security管理前端页面

之前我们只是对前端页面做了权限控制,并没有做任何处理,用户体验差。所以我们使用security与thymeleaf整合实现前端页面的管理。

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


    org.thymeleaf.extras
    thymeleaf-extras-springsecurity5

2.修改前端页面,使用Security相关标签进行页面控制

在index.html页面中引入Security安全标签

页面顶部通过“xmlns:sec”引入了Security安全标签,页面中根据需要编写了4个

模块.

游客您好,如果想查看电影请登录

您好,您的用户权限为 ,您有权观看以下电影

3.效果测试

重启项目进行效果测试,项目启动成功后,通过浏览器访问http://localhost:8080/

作业:Security管理前端页面

你可能感兴趣的:(springboot,spring,boot)