尚硅谷SpringSecuirity学习笔记

一、初始SpringSecurity

Spring 是非常流行和成功的 Java 应用开发框架,Spring Security 正是 Spring 家族中的成员。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。

1.1 相关概念

不管你有没有了解过安全框架,但这里都要了解以下两个概念:

白话:

  1. 授权:你能干什么? 通俗点讲就是系统判断用户是否有权限去做某些事情。
  2. 认证:你是谁? 通俗点说就是系统认为用户是否能登录

官方解释:

  1. 授权:
    验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。
  2. 认证:
    是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。

1.2 类似产品 PK🕵️

1.2.1 Shiro

在这里插入图片描述

Apache 旗下的轻量级权限控制框架。
官方网址:https://shiro.apache.org/
特点:

  • 轻量级。Shiro 主张的理念是把复杂的事情变简单。针对对性能有更高要求
    的互联网应用有更好表现。
  • 通用性。
    • 好处:不局限于 Web 环境,可以脱离 Web 环境使用。
    • 缺陷:在 Web 环境下一些特定的需求需要手动编写代码定制

Shiro在整合SSM框架的时候,相比于SpringSedcurity而言,比较简单,在早些年SpringBoot和SpringCloud没有流行的时候,Shiro很火爆

1.2.2 SpringSecurity

在这里插入图片描述

Spring 技术栈的组成部分。
官方网址:https://spring.io/projects/spring-security
特点:

  • 和 Spring 无缝整合。
  • 全面的权限控制。
  • 专门为 Web 开发而设计。
    • 旧版本不能脱离 Web 环境使用。
    • 新版本对整个框架进行了分层抽取,分成了核心模块和 Web 模块。单独引入核心模块就可以脱离 Web 环境。
  • 重量级。

二、SpringSecurity实现登录验证授权的三种方式

2.1 环境搭建

  1. 新建一个SpringBoot的项目
  2. 引入WEB模块和SpringSecurity模块
  3. 编写controller测试
  4. 输入映射地址,则会跳转至springsecurity提供的登录页,进行登录认证
  5. 输入用户名和密码

密码默认是在编辑器的控制台输出,用户名默认是user
在这里插入图片描述

2.1 方式一、通过配置文件

在application.properties/application.yml里编写配置

server.port: 8888
# SpringSecurity配置文件方式配置用户名和密码 【在实际用户中不用,都是查找数据库中的用户和密码】
spring.security.user.name=root
spring.security.user.password=root

2.2 方式二、通过编写配置类实现

1、新建一个Config的配置类
2、集成WebSecurityConfigurerAdapter的类
3、重写里面的方法·configure(AuthenticationManagerBuilder auth)
4、注意:如果使用密码加密进行加密密码,则必须要通过@Bean注解注入PasswordEncoder
【实例】:

@Configuration
public class SecurityAuthConfig extends WebSecurityConfigurerAdapter{

	/**
	 * 手动创建一个SpringSecurity的接口实现来完成对密码的加密操作
	 * @return
	 */
	@Bean
	PasswordEncoder password() {
		return new BCryptPasswordEncoder();
	}
	
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		// 加密操作 必须要PasswordEncoder这个接口的支持
		BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
		String encodePwd = bCryptPasswordEncoder.encode("123456");
		// 把密码和用户放到内存中
		auth.inMemoryAuthentication().withUser("lucy").password(encodePwd).roles("admin");
	}
}

2.3 方式三、通过数据库进行数据查询验证登录

1、新建一个数据库Student

CREATE DATABASE springsecurity;
DROP TABLE IF EXISTS `student`;

CREATE TABLE `student` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `username` VARCHAR(30) NOT NULL,
  `password` VARCHAR(200) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=INNODB;

2、导入MyBatis-Plus依赖、MySQL8.0.27依赖、Lombok依赖[可有可无]

		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.5.1</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.27</version><!--$NO-MVN-MAN-VER$-->
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>

3、新建一个vo实体Student

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class Student {
	private Integer id;
	private String username;
	private String password;
}

4、编写一个接口实现BaseMapper

@Repository("studentMapper")
public interface StudentMapper extends BaseMapper<Student>{
}

5、编写一个Service调用Mapper的方法

@Service("userDetailsService")
public class MyUserDetailService implements UserDetailsService {
	
	/**
	 * 注入StudentMapper接口
	 */
	@Autowired
	@Qualifier("studentMapper")
	private StudentMapper studentMapper;

	/**
	 * 根据用户名实现操作 返回UserDetail
	 */
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		// 在这里面可以查询数据库 将查询到的结构封装成一个User对象 User对象是Security的一个内置的对象
		QueryWrapper<Student> warpper = new QueryWrapper<>();
		warpper.eq("username", username);
		Student student = studentMapper.selectOne(warpper);
		// 判断 数据是否存在
		if (student==null) { // 数据库无此对象 认证失败
			// 如果不存在直接抛出异常 用户不存在
			throw new UsernameNotFoundException("用户名不存在");
		}
		/**
		 * 权限添加一般是数据库中查找
		 */
		// 角色授予 授权
		List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
		
		/**
		 * 三个参数:1、用户名2、密码(一般都要加密)3、权限
		 */
		// 认证
		return new User(student.getUsername(), new BCryptPasswordEncoder().encode(student.getPassword()), auths);
	}
}

7、编写一个配置类

@Configuration
public class SecurityConstomAuthConfig extends WebSecurityConfigurerAdapter{

	// 通过 数据库查找完成自定义的登陆用户名和密码设置
	@Autowired
	private UserDetailsService userDetailsService;
	
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
	}
	
	@Bean
	PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}
}

8、配置核心配置文件 用于连接数据库的操作

# 配置数据库信息
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springsecurity?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456

9、测试
输入用户名和密码后即可登录
在这里插入图片描述

三、自定义登陆页面

1、在配置类里去实现一个configure(HttpSecurity http)的方法,基于http请求的自定义方式

/**
	 * 自定义登陆页面
	 */
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.formLogin()
			.loginPage("/login.html") // 指定登录页面
			.loginProcessingUrl("/stu/login") // 登陆表单action的值 登陆处理拦截的请求地址 SpringSecuirty帮我做好了 我们不需要在做
			.defaultSuccessUrl("/test/index").permitAll() // 登陆成功后跳转的路径 
			.and().authorizeRequests() // 授权请求
				.antMatchers("/","/test/hello","/stu/login").permitAll()  // 设置那些路径可以直接访问,无需认证
			.anyRequest().authenticated()
			.and().csrf().disable(); // 关闭csrf的防护
	}

2、创建一个controller 映射地址给index,即成功后跳转的操作映射

@RequestMapping("/index")
	public String secuirty() {
		return "hello111";
	}

3、新建一个登陆页面在static目录下。注意:static目录是springboot默认的静态资源目录,默认就是可以访问的。

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<form action="/stu/login" method="post">
		用户名:<input type="text" name="username"> <br>
		密码:<input type="text" name ="password"> <br>
		<input type="submit" value="提交">
	</form>
</body>
</html>

测试:访问/test/hello可以直接访问,访问test/index 跳转至login.html进行登录

三、基于角色或权限访问 RBAC

在这里插入图片描述

3.1 hasAuthority 方法

如果当前的主体具有指定的权限,则返回true,否则返回false
局限性:只能设置某一个权限 当权限查过一个则无法设置

在配置类的重写的configuration的类添加一个antMatchers("/test/index").hasAuthority("admin")的方法,允许只有admin的权限才可以登录

/**
	 * 自定义登陆页面
	 */
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.formLogin()
			.loginPage("/login.html") // 指定登录页面
			.loginProcessingUrl("/stu/login") // 登陆表单action的值 登陆处理拦截的请求地址 SpringSecuirty帮我做好了 我们不需要在做
			.defaultSuccessUrl("/test/index").permitAll() // 登陆成功后跳转的路径 
			.and().authorizeRequests() // 授权请求
				.antMatchers("/","/test/hello","/stu/login").permitAll()  // 设置那些路径可以直接访问,无需认证
				.antMatchers("/test/index").hasAuthority("admin") // 当前登录的admin角色权限的才可以访问
			.anyRequest().authenticated()
			.and().csrf().disable(); // 关闭csrf的防护
	}

然后再去service的类里去为查询到的用户添加角色,首先把授予的角色设置成abc,与自定义的登陆页面所规定的角色admin不一致,

List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("abc");

测试:如下图:出现403, (type=Forbidden, status=403).的错误,进制无权用户访问。
在这里插入图片描述
底层源码:

private static String hasAuthority(String authority) {
		return "hasAuthority('" + authority + "')";
	}

3.2 hasAnyAuthority 方法

102人
如果当前的主体有任何提供的角色(给定的作为一个逗号分隔的字符串列表)的话,
返回true.
访问ht://localhost:8090/find.

在配置类中添加一个hasAnyAuthority("admin,manager")可以实现基于多个权限的访问

/**
	 * 自定义登陆页面
	 */
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.formLogin()
			.loginPage("/login.html") // 指定登录页面
			.loginProcessingUrl("/stu/login") // 登陆表单action的值 登陆处理拦截的请求地址 SpringSecuirty帮我做好了 我们不需要在做
			.defaultSuccessUrl("/test/index").permitAll() // 登陆成功后跳转的路径 
			.and().authorizeRequests() // 授权请求
				.antMatchers("/","/test/hello","/stu/login").permitAll()  // 设置那些路径可以直接访问,无需认证
				// .antMatchers("/test/index").hasAuthority("admin") // 当前登录的admin角色权限的才可以访问
				.antMatchers("/test/index").hasAnyAuthority("admin,manager") // 支持多个角色
			.anyRequest().authenticated()
			.and().csrf().disable(); // 关闭csrf的防护
	}

底层源码:

private static String hasAnyAuthority(String... authorities) {
		String anyAuthorities = StringUtils.arrayToDelimitedString(authorities, "','");
		return "hasAnyAuthority('" + anyAuthorities + "')";
	}

参数是一个String类型的可变形参,根据形参的逗号作为分割符,来分配权限

3.3. hasrole 方法

如果用户具备指定角色及可以访问,否则出现403,如果当前主体具有指定的角色,则返回true

/**
	 * 自定义登陆页面
	 */
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.formLogin()
			.loginPage("/login.html") // 指定登录页面
			.loginProcessingUrl("/stu/login") // 登陆表单action的值 登陆处理拦截的请求地址 SpringSecuirty帮我做好了 我们不需要在做
			.defaultSuccessUrl("/test/index").permitAll() // 登陆成功后跳转的路径 
			.and().authorizeRequests() // 授权请求
				.antMatchers("/","/test/hello","/stu/login").permitAll()  // 设置那些路径可以直接访问,无需认证
				.antMatchers("/test/index").hasRole("sale") // 只能授予单个权限
			.anyRequest().authenticated()
			.and().csrf().disable(); // 关闭csrf的防护
	}

底层源码:

private static String hasRole(String rolePrefix, String role) {
		Assert.notNull(role, "role cannot be null");
		Assert.isTrue(rolePrefix.isEmpty() || !role.startsWith(rolePrefix), () -> "role should not start with '"
				+ rolePrefix + "' since it is automatically inserted. Got '" + role + "'");
		return "hasRole('" + rolePrefix + role + "')";
	}

发现这里需要给角色加上一个前缀Role_,因为底层进行了封装

@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		// 在这里面可以查询数据库 将查询到的结构封装成一个User对象 User对象是Security的一个内置的对象
		QueryWrapper<Student> warpper = new QueryWrapper<>();
		warpper.eq("username", username);
		Student student = studentMapper.selectOne(warpper);
		// 判断 数据是否存在
		if (student==null) { // 数据库无此对象 认证失败
			// 如果不存在直接抛出异常 用户不存在
			throw new UsernameNotFoundException("用户名不存在");
		}
		/**
		 * 权限添加一般是数据库中查找
		 */
		// List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
		// 角色授予 授权 给查出的用户服务admin的角色权限
		List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_sale");
		
		/**
		 * 三个参数:1、用户名2、密码(一般都要加密)3、权限
		 */
		//return new User("user", new BCryptPasswordEncoder().encode("123456"), auths);
		// 认证
		return new User(student.getUsername(), new BCryptPasswordEncoder().encode(student.getPassword()), auths);
	}

3.4 hasAnyRole 方法

同理和hasAnyAuthority

```java
/**
	 * 自定义登陆页面
	 */
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.formLogin()
			.loginPage("/login.html") // 指定登录页面
			.loginProcessingUrl("/stu/login") // 登陆表单action的值 登陆处理拦截的请求地址 SpringSecuirty帮我做好了 我们不需要在做
			.defaultSuccessUrl("/test/index").permitAll() // 登陆成功后跳转的路径 
			.and().authorizeRequests() // 授权请求
				.antMatchers("/","/test/hello","/stu/login").permitAll()  // 设置那些路径可以直接访问,无需认证
				.antMatchers("/test/index").hasRole("sale") // 只能授予单个权限
			.anyRequest().authenticated()
			.and().csrf().disable(); // 关闭csrf的防护
	}

底层源码

private static String hasAnyRole(String rolePrefix, String... authorities) {
		String anyAuthorities = StringUtils.arrayToDelimitedString(authorities, "','" + rolePrefix);
		return "hasAnyRole('" + rolePrefix + anyAuthorities + "')";
	}

3.5 自定义403页面

403:即没有权限访问的页面
设置只需要在configure(HttpSecurity http)方法中配置一个http.exceptionHandling().accessDeniedPage("/unAuth.html");的方法,参数集为指定的403页面
在这里插入图片描述

四、认证授权注解

在这里插入图片描述

4.1 @Secured

判断是否具有角色,另外需要注意的是这里匹配的字符串需要添加前缀“ROLE_”
使用注解先要开启注解功能@EnableGlobalMethodSecurity(securedEnabled = true)

在controller层中方法上添加上该注解,注解的参数可以添加角色,是一个数组

@RequestMapping("/update")
	@Secured({"ROLE_sale","ROLE_manager"})
	public String update() {
		return "hello update";
	}

测试访问:http:localhost:8088/test/update,输入用户名和密码;
在这里插入图片描述

4.2 @PreAuthorize

  1. 在启动类上开启注解支持@EnableGlobalMethodSecurity(prePostEnabled = true)
  2. 在controller的方法上加上该注解@PreAuthorize("hasAnyAuthority('menu')")
  3. 在servcie层设置权限
    List auths = AuthorityUtils.commaSeparatedStringToAuthorityList(“menu”);
	@RequestMapping("/update")
	@PreAuthorize("hasAnyAuthority('menu')")
	public String update() {
		return "hello update";
	}

4.3 @PostAuthorize

在方法之后进行校验

  1. 在启动类上开启注解支持@EnableGlobalMethodSecurity(prePostEnabled = true)
  2. 在controller的方法上加上该注解@PostAuthorize("hasAnyAuthority('menus')")
@RequestMapping("/update")
	// 在方法执行之后校验
	@PostAuthorize("hasAnyAuthority('menus')")
	public String update() {
		System.out.println("update...");
		return "hello update";
	}
  1. 在servcie层设置权限menu

理论上讲是访问无权限的,这是看控制台输出结果,看是否有update。。。的输出结果

在这里插入图片描述

五、用户注销功能

  1. 在配置类中添加退出的page
// 配置退出功能 logoutUrl("")退出的映射url logoutSuccessUrl退出成功后的执行映射 permitAll允许所有权限
http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();
  1. 修改配置类,登陆成功后,跳转至成功的页面
  2. 在成功的页面里添加一个超链接,链接地址为刚才设置的logoutUrl中的地址映射

原理类似于Session,注销将session销毁

六、基于数据库自动登录

传统方式cookie:
缺点:存储在客户端安全性不高,且当cookie众多的时候,容易造成系统性能下降的缺点

6.1 springsecurity的自动登录

底层实现原理:
在这里插入图片描述

1.创建一张实体表:

CREATE TABLE persistent_logins (
	`username` VARCHAR(64) NOT NULL, 
	`series` VARCHAR(64) PRIMARY KEY, 
	`token` VARCHAR(64) NOT NULL, 
	`last_used` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE
	CURRENT_TIMESTAMP
) ENGINE=INNODB DEFAULT CHARSET=UTF8;
  1. 在配置类中诸如数据源,配置操作数据库的对象,创建JdbcTokenRepositoryImpl
	// 注入数据源
	@Autowired
	private DataSource dataSource;
	
	//创建对象PersistentTokenRepository
	@Bean
	public PersistentTokenRepository getPersistentTokenRepository() {
		JdbcTokenRepositoryImpl tokenRepositoryImpl = new JdbcTokenRepositoryImpl();
		tokenRepositoryImpl.setDataSource(dataSource);
		// tokenRepositoryImpl.setCreateTableOnStartup(true);
		return tokenRepositoryImpl;
	}
  1. 在配置类里面配置自动登录
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		// 配置退出功能 logoutUrl("")退出的映射url logoutSuccessUrl退出成功后的执行映射 permitAll允许所有权限
		http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();
		
		// 配置没有权限访问的也面
		http.exceptionHandling().accessDeniedPage("/unAuth.html");
		
		http.formLogin()
			.loginPage("/login.html") // 指定登录页面
			.loginProcessingUrl("/stu/login") // 登陆表单action的值 登陆处理拦截的请求地址 SpringSecuirty帮我做好了 我们不需要在做
			.defaultSuccessUrl("/success.html").permitAll() // 登陆成功后跳转的路径 
			.and().authorizeRequests() // 授权请求
				.antMatchers("/","/test/hello","/stu/login").permitAll()  // 设置那些路径可以直接访问,无需认证
				.antMatchers("/test/index").hasAnyRole("admin,sale")
			.anyRequest().authenticated()
			.and()
			.rememberMe().tokenRepository(getPersistentTokenRepository()) 
			.tokenValiditySeconds(60) // 设置token的过期时间 单位是以秒为单位
			.userDetailsService(userDetailsService) // 设置服务
			.and().csrf().disable(); // 关闭csrf的防护
	}
  1. 在登陆页面中完成复选框是实现自动登录,在登录页上设置复选框的name属性值必须为remember-me
	<form action="/stu/login" method="post">
		用户名:<input type="text" name="username"> <br>
		密码:<input type="text" name ="password"> <br>
		<input type="submit" value="提交">
		<input type="checkbox" name="remember-me"> 自动登录
	</form>
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@WAT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值