文章目录
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控制前端页面的权限(那一些页面可以被访问)