Spring Security安全框架

Spring Security安全框架

学习目标

  • 了解Spring Security的安全管理功能
  • 了解Spring Security安全配置
  • 掌握Spring Security的用户认证
  • 掌握Spring Security的权限管理
  • Spring Security控制前端页面实现

1 Spring Security介绍

1.Shrio:也是一个权限认证的框架,不介绍。

2.Outh2:针对大型的系统而设计的,金融系统、银行业务。

3.Spring Security:Spring家族对应的官方推荐的安全认证框架。

2 Spring Security快速入门

Spring Security主要的概念:

  • 认证:Authentication
  • 授权:Authenrization

2.1 搭建Spring Security的基础环境

1.创建一个项目spring_security_demo。

2.导入Spring Security的相关依赖。

3.配置Spring Security安全管理。

2.2 搭建安全管理Spring Security测试环境

1.导入Spring Security的相关依赖:Spring提供了一个Spring Security的启动器。

一旦引入了Spring Security的启动器,Spring Security、WebFlux的相关的安全功能部分已经生效。

2.Spring Security的配置。

3.启动项目的主类。

Using generated security password: 91959a82-ae06-4055-a8dc-80690886c9b7

4.访问localhost:8080地址的时候,安全框架自动拦截了目标访问的资源,直接跳转到http://localhost:8080/login。

5.默认情况下,安全框架自动生成一个登录页面,还生成一组登录的用户信息。

用户名:user
密码:在启动主类的时候,控制台输出的密码

3 MVC Security介绍

使用Spring Boot与Spring MVC进行Web开发时,如果项目引入spring-boot- starter-security依赖启动器,MVC Security安全管理功能就会自动生效,其默认的安全配置是在SecurityAutoConfiguration和UserDetailsServiceAutoConfiguration中实现的。其中,SecurityAutoConfiguration会导入并自动化配置SpringBootWebSecurityConfiguration用于启动Web安全管理,UserDetailsServiceAutoConfiguration则用于配置用户身份信息。

通过自定义WebSecurityConfigurerAdapter类型的Bean组件,可以完全关闭Security提供的Web应用默认安全配置,但是不会关闭UserDetailsService用户信息自动配置类。如果要关闭UserDetailsService默认用户信息配置,可以自定UserDetailsService、AuthenticationProvider或AuthenticationManager类型的Bean组件。另外,可以通过自定义WebSecurityConfigurerAdapter类型的Bean组件覆盖默认访问规则。Spring Boot提供了非常多方便的方法,可用于覆盖请求映射和静态资源的访问规则。

自定义安全管理模块:

  • WebSecurityConfigurerAdapter用来管理安全框架的配置,如果开发者想去自定义安全管理,需要实现这个这接口。自定义一个类,然后实现接口或者其他类。配置通常会被@Configration注解修饰。
  • 实现抽象方法、或者重写父类或者父接口中的部分方法。
方法描述
configure(AuthenticationManagerBuilder auth)定制用户认证管理器来实现用户认证
configure(HttpSecurity http)定制基于HTTP请求的用户访问控制

4 自定义用户认证

WebSecurityConfigurerAdapter提供了5中用户自定义认证。使用configure(AuthenticationManagerBuilder auth)方法来完成用户的自定义认证。

自定义用户认证方式分类见下:学习前三种认证方式。

  • 内存身份认证
  • JDBC身份认证
  • 身份详情服务认证
  • LDAP身份认证(了解)
  • 身份认证提供商(了解)

自定义认证的开发步骤:

  • 1.自定义一个类,需要去继承WebSecurityConfigurerAdapter。
  • 2.自定义用户身份认证需要重写:configure(AuthenticationManagerBuilder auth)。
  • 3.在该方法中通过AuthenticationManagerBuilder对象来设置不同的身份认证方式。

4.1 内存身份认证

In-Memeory Authentication,内存身份认证。基于内存中数据来进行验证,用来体验权限认证配置。

4.1.1 自定义WebSecurityConfigurerAdapter

自定义一个类,去继承WebSecurityConfigurerAdapter。重写configure(AuthenticationManagerBuilder auth)方法即可。

注解@EnableWebSecurity源码:

// 元注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented

@Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class,
		HttpSecurityConfiguration.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {

	/**
	 * Controls debugging support for Spring Security. Default is false.
	 * @return if true, enables debug support with Spring Security
	 */
	boolean debug() default false;

}

语法解析:

  • @Import:表示根据当前的pom.xml文件引入的Web模块的依赖和安全框架的依赖进行自动的配置。

  • @EnableGlobalAuthentication:表示用于开启自动的安全的全局配置(表示在项目中任何位置都生效)。

  • @Configuration:表示当前被修饰的类是一个配置类。

  • @EnableWebSecurity:是一个组合注解。

4.1.2 使用内存进行身份认证

重写configure(AuthenticationManagerBuilder auth)方法即可。此方法内部完成认证的自定义配置(5种)。

1.重写了configure(AuthenticationManagerBuilder auth)方法。

/**
* 自定义用户认证的管理。
* 参数:AuthenticationManagerBuilder,表示配置用户的权限认证方式。
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  // 1.基于内存认证:
  /**
  * 默认的登录页面:用户的登录信息,包含用户的名称、密码。
  * 1.如何在内存中声明一组用户名车密码?
  * 2.将用户内存中的信息关联上AuthenticationManagerBuilder对象上。
  */
  // 表示获取指定的密码编码器,必须要指定,否则话启动程序会报错。
  BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
  // 1.inMemoryAuthentication()表示基于内存认证,用户的密码必须加密,指定密码的加密方式。
  auth.inMemoryAuthentication().passwordEncoder(encoder)
    // 设置用户名、设置按照指定密码器的初始化密码、设置了tom这个用户对应的角色(表示common目录下的的资源可以被访问)
    .withUser("tom").password(encoder.encode("123456")).roles("common")
    .and() // 表示同时声明一组用户信息的初始化配置
    .withUser("李四").password(encoder.encode("123456")).roles("vip");
}

2.重启系统,然后访问项目,通过内存中配置的用户信息,进行登录,可以完成对应的登录。

3.当输入错的信息,重定向到http://localhost:8080/login?error。

4.2 JDBC身份认证

JDBC Authentication, JDBC身份认证。通过JDBC连接数据库,对数据库中的用户信息自动校验。

4.2.1 准备数据

直接将我给到大家的表直接在数据库建表。

# 选择使用数据库
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 DEFAULT CHARSET=utf8;
# 表示$2a$10$5ooQI8dir8jv0/gCa1Six.GpzAdIPf6pMqdminZ/3ijYzivCyPlfK按照123456加密后的密码
INSERT INTO `t_customer` VALUES (1, 'tom', '$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 COMMENT '权限',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB 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(11) NOT NULL AUTO_INCREMENT,
  `customer_id` int(11) NOT NULL COMMENT '关联的用户id',
  `authority_id` int(11) NOT NULL COMMENT '关联的权限id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `t_customer_authority` VALUES ('1', '1', '1');
INSERT INTO `t_customer_authority` VALUES ('2', '2', '2');
4.2.2 添加JDBC的数据库连接

1.导入数据库连接的依赖:jdbc依赖、mysql驱动连接依赖。

<!-- MySQL数据连接驱动 -->
<dependency>
  <groupId>com.mysql</groupId>
  <artifactId>mysql-connector-j</artifactId>
  <scope>runtime</scope>
</dependency>
<!-- JDBC数据库连接启动器 -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

2.配置数据源文件。

  • properites文件:默认配置文件的后缀.properites文件扩展名,默认会先去加载此文件(application.properites)。代码较为冗余。
  • yml文件:通过key:value的键值对的形式来声明配置信息,可以通过缩紧进行声明,从而实现省略固定前缀重复编写的问题。此文件的名称application.yml。
# MySQL数据库连接配置
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/springbootdata?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
    username: root
    password: 123456
4.2.3 使用JDBC进行身份认证

在重写的configure方法中,去定义新的认证方式。

// 2.使用JDBC进行身份认证
// 2.1 查询数据库中的数据是否存在
String userSql = "select username, password, valid from t_customer where username=?";
// 查询用户的权限信息
String authentySql = "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=?";
// 2.2 结合安全框架的AuthenticationManagerBuilder的对象实现JDBC身份认证的判断
// (1) 表示需要声明密码的编码器
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
// (2) 构建JDBC认证的自定义配置
auth.jdbcAuthentication().passwordEncoder(encoder)
  .dataSource(dataSource) // 设置连接的数据源对象
  .usersByUsernameQuery(userSql) // 根据指定的用户名查询用户的数据信息
  .authoritiesByUsernameQuery(authentySql); // 根据指定的权限进行查询

4.3 UserDetailsService身份认证

如果编写了登录业务:

  • UserController - login(username, password)
  • UserService - login(username, password)
  • UserMapper - login(username, password)

UserDetailsService身份认证可以根据项目的MVC分层架构来实现对应的身份认证。

4.3.1 搭建持久层

1.ORM编写

1.导入JPA依赖。

<!-- 导入Spring Data JPA依赖 -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- 引入lombok依赖 -->
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
</dependency>

2.创建实体类。每一张表对应有实体类。

(1) 实体类Customer的映射。

package com.cy.pojo;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.io.Serializable;

/**
 * 用户实体类
 */
@Data
@Entity(name = "t_customer") // 表示与数据库中那一张表进行ORM映射
public class Customer implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String username;
    private String password;
    private Boolean valid;
}

(2) 实体类Authrity映射。

package com.cy.pojo;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.io.Serializable;

@Data
@Entity(name = "t_authority")
public class Authority implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY
    private Integer id;
    private String authority;
}

2.编写Repository接口

1.需要实现一个接口JpaRepository,可以定义相关CRUD复杂方法。

2.自定义一个类实现JpaRepository接口。

4.3.2 UserDetailsService身份认证使用

UserDetailsService是Spring安全框架提供的一个接口。用来封装用户的自定义权限信息(Customer、Authority)。

UserDetailsService接口提供了一个方法:loadUserByUsername(String username)通过指定的用户名称来加载对应的用户信息,还可以进行权限的校验。

自定义一个类,然后实现UserDetailsService接口,并重写loadUserByUsername(String username),编写自定义的认证规则。

源码分析:

public final class SimpleGrantedAuthority implements GrantedAuthority {
  // 1.序列化ID:通过序列化的ID值来唯一的标识一个对象
  // 2.序列化ID:反序列化需要使用
  private static final long serialVersionUID = 570L;

  // 权限:期望接收一个权限的字符串的取值
  private final String role;

  // ...

  public SimpleGrantedAuthority(String role) {
    Assert.hasText(role, "A granted authority textual representation is required");
    this.role = role;
  }
}


public class Authority implements Serializable {
  private Integer id;
  private String authority; // 权限
}

源码解析:

// Spring安全框架提供的User实体类
public class User implements UserDetails, CredentialsContainer {
  private static final long serialVersionUID = 570L;
  private static final Log logger = LogFactory.getLog(User.class);
  
  // 用户名
  private String password;
  // 密码
  private final String username;
  // 权限
  private final Set<GrantedAuthority> authorities;
  
  private final boolean accountNonExpired;
  private final boolean accountNonLocked;
  private final boolean credentialsNonExpired;
  private final boolean enabled;

  public User(String username, String password, Collection<? extends GrantedAuthority> authorities) {
    this(username, password, true, true, true, true, authorities);
  }
  
  // ...
}

5 自定义用户授权管理

5.1 自定义用户访问控制

重写WebSecurityConfigurerAdapter类中的configure(HttpSecurity http)方法。此方法是基于HTTP请求的协议来进行权限控制(就是判断当前的请求地址是否可以被放行处理)。

方法中可以控制权限、Session管理配置、CSRF跨站的问题…

HttpSecurity类的主要方法及说明:

方法描述
authorizeRequests()开启基于HttpServletRequest请求访问的限制
formLogin()开启基于表单的用户登录
httpBasic()开启基于HTTP请求的Basic认证登录
logout()开启退出登录的支持
sessionManagement()开启Session管理配置
rememberMe()开启记住我功能
csrf()配置CSRF跨站请求伪造防护功能

用户请求控制相关的主要方法及说明:

方法描述
antMatchers(java.lang.String… antPtterns)开启Ant风格的路径匹配
mvcMatchers(java.lang.String… patterns)开启MVC风格的路径匹配(与Ant风格类似)
regexMatchers(java.lang.Sring… regexPatterns)开启正则表达式的路径匹配
and()功能连接符
anyRequest()匹配任何请求
rememberMe()开启记住我功能
access(String attribute)匹配给定的SpEL表达式计算结果是否为true
hasAnyRole(String… roles)匹配用户是否有参数中的任意角色
hasRole(Sring role)匹配用户是否有某一个角色
hasAnyAuthority(String… authorities)匹配用户是否有参数中的任意权限
hasAuthority(String authority)匹配用户是否有某一个权限
authenticated()匹配已经登录认证的用户
fullyAuthenticated()匹配完整登录认证的用户(非rememberMe登录用户)
hasIpAddress(String ipAddressExpression)匹配某IP地址的访问请求
permitAll()无条件对请求进行放行

下面在自定义用户认证案例的基础上,配置用户访问控制,演示Security授权管理的用法。

5.2 自定义用户登录

1.将登录页面方到项目中,构建了一个自定义的用户登录页面。

2.Spring Security安全框架默认会拦截自定义的请求url。

3.自定义用户登录控制配置。重写了configure(HttpSecurity http)方法。

// 2.自定义用户登录的控制
http.formLogin() // 表示基于表单的用户登录验证开启
  // 表示用户的登录页面
  .loginPage("/userLogin").permitAll() // 放行当前请求
  // 默认情况下,表单中的用户username、密码password,如果按照默认规则编写表单,会将参数自动传递给后台安全框架
  .usernameParameter("name")
  .passwordParameter("pwd")
  .defaultSuccessUrl("/") // 如果用户和密码验证成功,则打开那一个页面
  // 默认如果登录失败"/login?error"地址打开登录页面
  // 在表单中需要使用param.error来获取默认封装的错误信息
  .failureUrl("/userLogin?error"); // 如果登录的用户或密码输出错误,则打开登录页面

4.静态资源需要配置放行处理。

// 1.自定义用户的授权管理的配置
http.authorizeRequests() // 表示开启基于HttpServletRequest请求的访问认证权限控制
  // 表示基于Ant的请求风格的url都要进行权限的验证
  .antMatchers("/").permitAll() // 无条件对请求放行
  // 需要对static目录的静态资源进行放行的处理,无条件可以访问
  .antMatchers("/login/**").permitAll()
  // hasRole方法:用来判断指定的url请求是否有指定的权限,如果有则放行该请求
  .antMatchers("/detail/common/**").hasRole("common")
  .antMatchers("/detail/vip/**").hasRole("vip")
  // anyRequest()表示获取到所有的请求
  .anyRequest().authenticated(); // authenticated()表示必须在登录的状态下
//.and() // 功能连接符号
//.formLogin(); // 开启基于表单的用户登录认证

5.3 自定义退出登录

HttpSecrity对象中提供一个logout()方法,用来处理用户的退出登录的相关自定义以及设置。

logout()方法中涉及用户退出的主要方法及说明见下表。

方法描述
logoutUrl(String logoutUrl)用户退出处理控制URL,默认为post请求的/logout
logoutSuccessUrl(String logoutSuccessUrl)用户退出成功后的重定向地址
logoutSuccessHandler(LogoutSuccessHandler logoutSuccessHandler)用户退出成功后的处理器设置
deleteCookies(String… cookieNamesToClear)用户退出后删除指定Cookie
invalidateHttpSession(boolean invalidateHttpSession)用户退出后是否立即清除Session(默认为true)
clearAuthentication(boolean clearAuthentication)用户退出后是否立即清除Authentication用户认证信息(默认为true)

1.在index.html文件中,可以定义一个退出登录的按钮"注销"。

div>
<!-- 点击注销按钮,发送了一个POST请求,地址是mylogout的请求地址(默认退出请求地址是:logout)。 -->
<form th:action="@{/mylogout}" method="post">
  <input th:type="submit" th:value="注销" />
</form>
</div>

2.打开configure()处理退出登录的请求业务。

// 3.自定义用户退出登录
http.logout() // 表示自定义用户的退出功能
  .logoutUrl("/mylogout") // 表示什么样的url请求会触发退出登录
  .logoutSuccessUrl("/userLogin"); // 表示如果用户退出登录操作成功,跳转到指定的页面(login.html页面)

5.4 登录用户信息获取

5.4.1 使用HttpSession方式获取用户

通过访问HttpSession对象可以获取到用户的相关信息,例如:用户名、用户的权限信息登录。

在Controller层中编写一个请求处理方法,方法的参数是HttpSession对象。

CSRF:跨站请求(网络的攻击),CSCF_TOKEN。

5.4.2 使用SecurityContextHolder获取用户

使用SecurityContextHolder获取用户的名称、用户的权限、用户的密码等信息。

/** 使用SecurityContextHolder获取用户信息 */
@GetMapping("/sch")
@ResponseBody
public String getUserBySecurityContextHolder() {
  // 1.上下文对象:SecurityContext -- 保存有用户的信息
  SecurityContext context = SecurityContextHolder.getContext();
  // 2.获取用户的认证信息
  Authentication authentication = context.getAuthentication(); // 获取用户的认证信息
  // 3.通过Authentication对象来获取用户的详情信息(UserDetailsService)
  UserDetails userDetails = (UserDetails) authentication.getPrincipal();
  System.out.println("userDetails=" + userDetails);
  System.out.println("username=" + userDetails.getUsername());
  System.out.println("password=" + userDetails.getPassword());
  return "success";
}

5.5 记住我功能

5.5.1 记住我相关的API介绍

提供了一个可以勾选复选框,如果用户勾选了“记住我/自动登录”,在指定的时间范围内,用户即便关闭了客户端,再次访问系统内部资源,可以免登录直接进入系统。

formLogin().rememberMe(): 表示开启记住我的功能。

rememberMe()记住我功能相关涉及记住我的主要方法及说明如下表所示。

方法描述
rememberMeParameter(String rememberMeParameter)指示在登录时记住用户的HTTP参数,是表单中”记住我“复选框name属性的取值
key(String key)记住我认证生成的Token令牌标识
tokenValiditySeconds(int tokenValiditySeconds)记住我Token令牌有效期,单位为秒
tokenRepository(PersistentTokenRepository tokenRepository)指定要使用的PersistentTokenRepository,用来配置持久化Token令牌。使用JdbcTokenRepositoryImpl类来实现Token持久化保存。
alwaysRemember(boolean alwaysRemember)是否应该始终创建记住我Cookie,默认为false
clearAuthentication(boolean clearAuthentication)是否设置Cookie为安全的,如果设置为true,则必须通过HTTPS进行连接请求

Token:表示是一个随机的字符串。

钥匙:Token(随机的字符串)。
请求地址:/users/login
请求类型:POST
请求参数:username, password
响应结果:ResultResponse<Void>
5.5.2 记住我实现

1.基于简单加密Token实现

1.登录的表单中,定义一个复选框按钮,需要指定name属性的取值“remeberme”。如果没有指定name属性的取值,安全默认记住的name属性的取值“remeber-me”。

2.匹配权限。需要在confrigure(HttpSecurity http)方法中配置开启记住我的功能。

// 4.自定义记住我的功能
http.rememberMe() // 开启自定义记住我的功能
  // 表示表单中"记住我"复选框对应name属性的取值。<input type="checkbox" name="rememberme"> 记住我
  .rememberMeParameter("rememberme")
  .tokenValiditySeconds(200); // 记住我的时间单位为秒,如果超过此时间,下次访问需要登录

2.基于持久化Token的方式

1.在数据库中,需要去建立一张表,用来保存用户的Token的信息。

# 创建表persistent_logins
CREATE TABLE persistent_logins (
	series varchar(64) PRIMARY KEY, # 存储一个随机生成序列化
	username varchar(64) NOT NULL,  # 登录的用户名
	token varchar(64) NOT NULL,     # 存储每次访问的最新Token
	last_used timestamp  NOT NULL   # 表示Token最后使用时间(最后一次登录的时间)
);

注意:官网推荐的使用是persistent_logins表,来管理Token的信息。所以不需要去编写操作改变的SQL语句。

2.需要在confrigure(HttpSecurity http)方法中配置基于持久化Token的加载方式。

  • 当用户退出当前的登录(注销),persistent_logins表中当前维护的此用户对应的Token数据会被自动清空。

  • 当前用户在有效期内,如果二次登录,Token会自动的刷新(每次登录都会生成新的Token)。

  • 基于持久化的Token是可以实现记住我功能的。

package com.cy.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;

import javax.sql.DataSource;

/**
 * Security安全框架的配置类:
 * 1.继承WebSecurityConfigurerAdapter类。
 * 2.重写方法configure(AuthenticationManagerBuilder auth)。
 * 3.开启Security的安全配置的自定义支持,使用一个注解:@EnableWebSecurity。
 */
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  // 定义数据源对象
  @Autowired // 自动装配
  private DataSource dataSource; // 连接目标数据库对象

  // 声明一个UserDetailsService对象
  @Autowired
  private UserDetailsService userDetailsService; // byType类型自动装配(UserDetailsServiceImpl)

  /**
     * 自定义用户认证的管理。
     * 参数:AuthenticationManagerBuilder,表示配置用户的权限认证方式。
     */
  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    // 1.基于内存认证:
    /**
         * 默认的登录页面:用户的登录信息,包含用户的名称、密码。
         * 1.如何在内存中声明一组用户名车密码?
         * 2.将用户内存中的信息关联上AuthenticationManagerBuilder对象上。
         */
    // 表示获取指定的密码编码器,必须要指定,否则话启动程序会报错。
    //BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
    // 1.inMemoryAuthentication()表示基于内存认证,用户的密码必须加密,指定密码的加密方式。
    //auth.inMemoryAuthentication().passwordEncoder(encoder)
    // 设置用户名、设置按照指定密码器的初始化密码、设置了tom这个用户对应的角色(表示common目录下的的资源可以被访问)
    //.withUser("tom").password(encoder.encode("123456")).roles("common")
    //.and() // 表示同时声明一组用户信息的初始化配置
    //.withUser("李四").password(encoder.encode("123456")).roles("vip");

    // 2.使用JDBC进行身份认证
    // 2.1 查询数据库中的数据是否存在
    //String userSql = "select username, password, valid from t_customer where username=?";
    // 查询用户的权限信息
    //String authentySql = "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=?";
    // 2.2 结合安全框架的AuthenticationManagerBuilder的对象实现JDBC身份认证的判断
    // (1) 表示需要声明密码的编码器
    //BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
    // (2) 构建JDBC认证的自定义配置
    //auth.jdbcAuthentication().passwordEncoder(encoder)
    //.dataSource(dataSource) // 设置连接的数据源对象
    //.usersByUsernameQuery(userSql) // 根据指定的用户名查询用户的数据信息
    //.authoritiesByUsernameQuery(authentySql); // 根据指定的权限进行查询

    // 3.使用UserDetailsService权限认证
    // 3.1 构建一个密码的编码器
    BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
    // 3.2 设置认证方式为UserDetailsService的方式
    auth.userDetailsService(userDetailsService).passwordEncoder(encoder);
  }

  // 配置请求处理的控制(某个指定的请求是否有权限访问)
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    // 1.自定义用户的授权管理的配置
    http.authorizeRequests() // 表示开启基于HttpServletRequest请求的访问认证权限控制
      // 表示基于Ant的请求风格的url都要进行权限的验证
      .antMatchers("/").permitAll() // 无条件对请求放行
      // 需要对static目录的静态资源进行放行的处理,无条件可以访问
      .antMatchers("/login/**").permitAll()
      // hasRole方法:用来判断指定的url请求是否有指定的权限,如果有则放行该请求
      .antMatchers("/detail/common/**").hasRole("common")
      .antMatchers("/detail/vip/**").hasRole("vip")
      // anyRequest()表示获取到所有的请求
      .anyRequest().authenticated(); // authenticated()表示必须在登录的状态下
    //.and() // 功能连接符号
    //.formLogin(); // 开启基于表单的用户登录认证

    // 2.自定义用户登录的控制
    http.formLogin() // 表示基于表单的用户登录验证开启
      // 表示用户的登录页面
      .loginPage("/userLogin").permitAll() // 放行当前请求
      // 默认情况下,表单中的用户username、密码password,如果按照默认规则编写表单,会将参数自动传递给后台安全框架
      .usernameParameter("name")
      .passwordParameter("pwd")
      .defaultSuccessUrl("/") // 如果用户和密码验证成功,则打开那一个页面
      // 默认如果登录失败"/login?error"地址打开登录页面
      // 在表单中需要使用param.error来获取默认封装的错误信息
      .failureUrl("/userLogin?error"); // 如果登录的用户或密码输出错误,则打开登录页面

    // 3.自定义用户退出登录
    http.logout() // 表示自定义用户的退出功能
      .logoutUrl("/mylogout") // 表示什么样的url请求会触发退出登录
      .logoutSuccessUrl("/userLogin"); // 表示如果用户退出登录操作成功,跳转到指定的页面(login.html页面)

    // 4.自定义记住我的功能 - 简单Token的加密方式
    // http.rememberMe() // 开启自定义记住我的功能
    // 表示表单中"记住我"复选框对应name属性的取值。<input type="checkbox" name="rememberme"> 记住我
    //.rememberMeParameter("rememberme")
    //.tokenValiditySeconds(200); // 记住我的时间单位为秒,如果超过此时间,下次访问需要登录

    // 5.自定义记住我的功能 - 基于持久化Token的加密方式
    http.rememberMe()
      .rememberMeParameter("rememberme")
      .tokenValiditySeconds(300)
      .tokenRepository(tokenRepository()); // 对Token进行持久化处理
  }

  // 持久化Token的存储
  @Bean // 表示将方法的返回值对应的Java对象,交给Spring容器管理
  public JdbcTokenRepositoryImpl tokenRepository() {
    // 创建一个使用JDBC来持久化Token到数据库的一个实现类对象
    JdbcTokenRepositoryImpl jtri = new JdbcTokenRepositoryImpl();
    jtri.setDataSource(dataSource); // 在JdbcTokenRepositoryImpl对象上绑定数据源对象
    return jtri;
  }
}

5.6 CSRF

CSRF:Cross-site request forgery,跨站请求伪造。需要限制伪造请求,需进行防范。

推荐防止CSRF的方式:

  • 在请求中添加Token的验证。
  • 基于HTTP请求(在请求头中添加约束的字段)。
  • 在HTTP请求头中自定义属性(_csrf=“sdhdsd2=sde8w87”)。
5.6.1 CSRF防护功能关闭

提供有方法:disable()用于关闭防护功能。

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

在表单中自定义个控件(input),去Token的数据内容。将这个控件的可见性设置为隐藏。

<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>

_csrf是一个对象,在此对象中包含两部数据内容。

  • token属性:表示安全框架传递给客户端Token的取值(jshdk12978sdjyw8e784)。
  • parameterName属性: 表示此Token的name属性名称。
  • csrf_token=jshdk12978sdjyw8e784

将表单的属性action设置成Thymeleaf模版的方式来提交请求。

<form class="form-signin" th:action="@{/updateUser}" method="post">

</form>

6 Security控制页面

1.导入依赖:

<!-- Security与Thymeleaf整合实现前端页面安全访问控制 -->
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>

2.跟新index.html页面的访问控制。

<html xmlns:th="http://www.thymeleaf.org"
	  xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">

3.在表单上设置的属性见下:页面顶部通过xmlns:sec引入了Security安全标签,页面中根据需要编写了4个<div>模块。

1.使用sec:authorize="isAnonymous()"属性判断用户是否未登录,只有匿名用户(未登录用户)才会显示“请登录”链接提示。

2.使用sec:authorize="isAuthenticated()"属性来判断用户是否已登录,只有认证用户(登录用户)才会显示登录用户信息和注销链接等提示。

3.使用sec:authorize="hasRole('common')"属性,定义了只有角色为common(对应权限Authority为ROLE_common)且登录的用户才会显示普通电影列表信息。

4.使用sec:authorize="hasAuthority('ROLE_vip')"属性,定义了只有权限为ROLE_vip(对应角色Role为vip)且登录的用户才会显示VIP电影列表信息。

5.在进行登录用户信息获取展示时,使用sec:authentication="name"sec:authentication="principal.authorities"两个属性分别显示了登录用户名name和权限authority。

7 小结

1.Spring Security安全框架介绍:实现用户的认证、权限控制。

2.如何进行认证:

  • 基于内存进行认证
  • 基于JDBC进行认证
  • 基于UserDetailsService进行认证
  • LDAP进行认证
  • 基于认证提供商

3.认证开发步骤:

  • 自定义一个类,继承WebSecurityConfigurerAdapter。
  • 重写的方式configure(AuthenticationManagerBuilder auth)。
  • 在该方法中进行认证(5种方式)

4.自定义用户的授权管理:

  • 自定义一个类,继承WebSecurityConfigurerAdapter。
  • 重写的方法 configure(HttpSecurity http)。
  • 在该方法中编写权限管理的配置。

5.具体权限的管理配置:

  • 自定义用户的登录控制
  • 自定义退出登录
  • 自定义记住我

6.CSRF跨站请求(Token)

7.Security控制前端页面的权限(那一些页面可以被访问)

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值