简介
在web开发中安全是第一位!
做网站:安全应该在设计之初就考虑到
参考文档手册:https://www.springcloud.cc/spring-security-zhcn.html#what-is-acegi-security
常见的框架:shiro、SpringSecurity:很像~除了类不一样,名字不一样;
作用:认证、授权(vip1,vip2,vip3)【与SpringBoot可以无缝集成】
作用范围:
- 功能权限
- 访问权限
- 菜单权限
- …拦截器、过滤器:解决大量的原生代码~冗余
使用(用户验证和授权)
导入SpringSecurity依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
固定架构(认证):
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
}
固定架构(授权):
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
}
}
简单实现:(认证不是从数据库授权!!!以下代码只用于理解,等会儿再展示从数据库认证)
package com.ldx.config;
import com.sun.org.apache.xpath.internal.operations.And;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
//AOP效果
//比拦截器更强大
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//链式编程
//认证,SpringBoot 2.1.x 可以直接使用~
//密码编码:PasswordEncoder
//在Spring Security 5.0+ 新增了很多的加密方法~
//passwordEncoder(new BCryptPasswordEncoder())用于密码加密
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//从内存中读
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("a").password(new BCryptPasswordEncoder().encode("1")).roles("vip2","vip3")//roles可以写多个
.and()
.withUser("b").password(new BCryptPasswordEncoder().encode("2")).roles("vip1","vip2","vip3")
.and()
.withUser("c").password(new BCryptPasswordEncoder().encode("3")).roles("vip1");//通过and可以给不同的用户设置不同的权限
//这些数据正常应该从数据库中读
}
//授权
@Override
protected void configure(HttpSecurity http) throws Exception {
//需求一、首页所有人可以访问,功能页只有对应有权限的人才能访问
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("资源名1").hasRole("vip1")
.antMatchers("资源名2").hasRole("vip2")
.antMatchers("资源名3").hasRole("vip3");
//没有权限默认回到登入页面
http.formLogin();
}
}
注销
http.logout().logoutUrl("/logout");//括号里指定注销的路径,与a标签href路径一致,
// http.logout().logoutSuccessUrl("/");也可以指定注销成功后的页面
Thymeleaf整合SpringSecurity
第一步:引入整合包:
<!--SpringSecurity与thymeleaf整合依赖-->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
第二步引入命名空间:(老地方引入)
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"
判断是否认证、获取角色和判断是否拥有角色
<!--判断认证-->
<!--未认证-->
<div sec:authorize="!isAuthenticated()">显示未认证内容</div>
<!--已认证-->
<div sec:authorize="isAuthenticated()">显示认证内容</div>
<!--获取用户和角色-->
<!--获取用户-->
<span sec:authentication="name"></span>
<!--获取角色-->
<span sec:authentication="principal.authorities"></span>
<!--判断是否拥有角色-->
<div sec:authorize="hasRole('vip1')"></div>
尚硅谷笔记!!!
1. SpringSecurity 框架简介
1.1 概要
Spring 是非常流行和成功的 Java 应用开发框架,Spring Security 正是 Spring 家族中的
成员。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方
案。
2、入门案例:(需要实操)
第一步:创建SpringBoot工程
老规矩勾选web模块和security模块
然后快速创建即可
第二步:引入相关依赖
这里我建议查看一下依赖是否完整:(当时没有跳转到默认页面是因为我依赖中没有自动导入spring-boot-starter-web这个依赖)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
第三步:编写controller进行测试
建立controller包,编写TestController 类进行测试:
@RestController
@RequestMapping("test")
public class TestController {
@GetMapping("hello")
public String hello(){
return "hello security";
}
}
测试结果:输入访问地址:http://localhost:8080/test/hello
进入默认页面
错误输入账号密码:
正确输入账号密码:账号默认:user,密码每次动态生成:
进入成功:
只要你不退出浏览器(一次会话),就可以免登入直接多次访问,只有退出浏览器才会要求重新登入
项目结构:(暂时只需要我勾出来的部分进行入门测试)
通过上面的例子可以看出,SpringSecurity底层是通过动态代理实现的,其思想是AOP
3、SpringSecurity基本原理(不需要实操,需要了解)
代码底层流程:重点看三个过滤器:
FilterSecurityInterceptor:是一个方法级的权限过滤器, 基本位于过滤链的最底部。
UserDetailsService 接口讲解
当什么也没有配置的时候,账号和密码是由 Spring Security 定义生成的。而在实际项目中
账号和密码都是从数据库中查询出来的。 所以我们要通过自定义逻辑控制认证逻辑。
如果需要自定义逻辑时,只需要实现 UserDetailsService 接口即可。接口定义如下:
UserDetailService接口:查询数据库用户名和密码过程
- 创建类继承UsernamePasswordAuthenticationFilter,重写三个方法
- 创建类实现UserDetailService,编写查询数据过程,返回User对象,这个User对象是安全框架提供对象
PasswordEncoder 接口讲解(数据加密接口,用于对返回的User对象的密码加密)
查用方法演示(可以自己试试)
@Test
public void test01(){
// 创建密码解析器
BCryptPasswordEncoder bCryptPasswordEncoder = new
BCryptPasswordEncoder();
// 对密码进行加密
String atguigu = bCryptPasswordEncoder.encode("atguigu");
// 打印加密之后的数据
System.out.println("加密之后数据:\t"+atguigu);
//判断原字符加密后和加密之前是否匹配
boolean result = bCryptPasswordEncoder.matches("atguigu", atguigu);
// 打印比较结果
System.out.println("比较结果:\t"+result);
}
SpringBoot 对 Security 的自动配置
https://docs.spring.io/spring-security/site/docs/5.3.4.RELEASE/reference/html5/#servlet-hello
4、SpringSecurity Web 权限方案(需要实操)
方案概括:(1)认证 ,(2)授权
在入门案例中用户名和密码都是系统提供的,而我们可以修改默认的用户名和登入密码
第一种方式:通过配置文件
第二种方式:通过配置类
第三种方式:自定义编写实体类(实际开发中连接数据库的方式,必须掌握)
方式一:通过配置文件
可以使用application.properties或application.yml文件配置,这里我使用yaml:
spring:
security:
user:
name: ldx
password: 123
测试结果:
方式二:编写类实现接口
注释方式一的yml设置
建立config包,编写SecurityConfig 类继承WebSecurityConfigurerAdapter 重写方法
密码必须加密!!!
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
BCryptPasswordEncoder passwordEncoder=new BCryptPasswordEncoder();
String password = passwordEncoder.encode("222");
//上面代码先进行对密码的加密,再设置用户名和密码
auth.inMemoryAuthentication().withUser("ldx").password(password).roles("");//roles为角色
}
@Bean
PasswordEncoder password(){
return new BCryptPasswordEncoder();//将PasswordEncoder对象在spring容器中注册,重要步骤
}
}
测试结果:
方式三:自定义实现类设置
注释掉方式一二。
- 创建配置类,设置使用哪个UserDetailService实现类
在config包下建立SecurityConfigTest继承WebSecurityConfigurerAdapter
@Configuration
public class SecurityConfigTest extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
@Bean
PasswordEncoder password(){
return new BCryptPasswordEncoder();//将PasswordEncoder对象在spring容器中注册,重要步骤
}
}
- 编写实现类实现UserDetailsService 接口,返回User对象,User对象里有用户名,密码和权限
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
List<GrantedAuthority> auths= AuthorityUtils.commaSeparatedStringToAuthorityList("role");
return new User("ldx",new BCryptPasswordEncoder().encode("123"),auths);
}
}
测试结果:
5、连接数据库完成认证(必须掌握的重点实操)
整合MybatisPlus完成数据库操作(不仅仅使用MybatisPlus,用Mybatis或JDBC即可)
第一步:引入相关依赖
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
第二步:创建简单数据库和表如下例(入门不需要多创建表)
create table users
(
id int auto_increment primary key,
username varchar(20) not null,
password varchar(20) not null
);
插入数据:
第三步:创建users表对应实体类
@Data
public class Users {
private Integer id;
private String username;
private String password;
}
第四步:建立mapper包,整合mybatis-plus,创建UsersMapper 接口,继承mybatis-plus的接口
@Repository
public interface UsersMapper extends BaseMapper<Users> {
}
第五步:在MyUserDetailService中调用mapper里面的方法查询数据库进行用户认证
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UsersMapper usersMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper<Users> wrapper=new QueryWrapper<>();
wrapper.eq("username",username);
Users users = usersMapper.selectOne(wrapper);
if(users==null){//数据库没有用户名,认证失败
throw new UsernameNotFoundException("用户名不存在");
}
List<GrantedAuthority> auths= AuthorityUtils.commaSeparatedStringToAuthorityList("role");//权限集合,实际开发是数据库中查询
//从查询数据库返回users对象,得到用户名和密码,返回
return new User(users.getUsername(),new BCryptPasswordEncoder().encode(users.getPassword()),auths);
}
}
第六步:在启动类添加注解MapperScan来扫描到mapper注入
@SpringBootApplication
@MapperScan("com.ldx.security.mapper")
public class SecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SecurityApplication.class, args);
}
}
第七步:在yaml文件中配置数据库信息:
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/demo?useUnicode=true&character=UTF-8&serverTimezone=GMT%2B8
username: root
password: 198810
项目结构如下:
测试运行
成功:
6、自定义用户登入界面
在配置类(这里是SecurityConfigTest)中实现相关的配置
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin() //自定义自己编写的登入页面
.loginPage("/login.html") //登入页面设置
.loginProcessingUrl("/user/login") //登入的路径【必须与表单提交路径一致,且必不可少】(可以看作一个中转站,前台界面提交表单之后跳转到这个路径进行UserDetailService的验证,简单来说就是做前后端分离的)
.defaultSuccessUrl("/test/index").permitAll() //未登入的情况下显示我们自定义的登入界面,登入成功之后,跳转路径,进入该控制器中
.and().authorizeRequests()
.antMatchers("/","/test/hello","user/login").permitAll()//设置哪些路径可以直接访问,不需要认证
.anyRequest().authenticated()
.and().csrf().disable();//关闭csrf防护
}
创建相关页面和controller
在resource里建立static文件夹,写上我们的自定义登入页面login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/user/login" method="post">
用户名: <input type="text" name="username"><br>
密 码: <input type="password" name="password"><br>
<input type="submit" value="login">
</form>
</body>
</html>
在TestController里追加配置:
@GetMapping("index")
public String index(){
return "hello index";
}
测试路径一:http://localhost:8080/test/hello
测试路径二:http://localhost:8080/test/index
毫无意外的进入验证页面:
验证成功再重新进入该地址,且浏览器关闭之前都可以不用验证访问
底层还是动态代理,思想仍然是AOP
7、基于角色和权限进行访问控制
1、hasAuthority 方法
如果当前的主体具有指定的权限,则返回 true,否则返回 false
第一步:在配置类设置当前访问地址有哪些权限
.antMatchers("/test/index").hasAuthority("admin")//当前登入的用户,只有具有admin权限才可以访问这个路径
第二步:在UserDetailService,把返回的User对象设置权限
List<GrantedAuthority> auths= AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
测试:
把权限改为abc
权限改回admin:
权限认证成功
2、hasAnyAuthority 方法(多个权限的用户访问)
如果当前的主体有任何提供的角色(给定的作为一个逗号分隔的字符串列表)的话,返回
true.
.antMatchers("/test/index").hasAnyAuthority("admin,manager")//支持多个权限用户访问该路径
具有admin测试访问:
8、基于角色访问控制
1、hasRole 方法
如果用户具备给定角色就允许访问,否则出现 403。
如果当前主体具有指定的角色,则返回 true。
.antMatchers("/test/index").hasRole("sale")
List<GrantedAuthority> auths= AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_sale");
注意点在我们设置的用户角色名不是sale而是ROLE_sale,代码底层要求加上ROLE_
测试:
2、hasAnyRole方法
表示用户具备任何一个条件都可以访问。
9、自定义403页面(可实操)
自定义403没有权限访问的页面
在配置类中配置即可,在static包下建立unauth.html页面
//配置没有权限访问跳转自定义页面
http.exceptionHandling().accessDeniedPage("/unauth.html");
测试访问:
改一下角色权限
10、注解使用(需要实操)
还原上次测试!
认证授权注解使用:
10.1、@Secured
判断是否具有角色,另外需要注意的是这里匹配的字符串需要添加前缀“ROLE_“。
第一步:使用注解先要开启注解功能!(启动类或配置类上)
@EnableGlobalMethodSecurity(securedEnabled=true)
第二步:在controller的方法上面使用注解(这里新建一个方法),设置角色
@GetMapping("update")
@Secured({"ROLE_sale","ROLE_manager"})
public String update(){
return "hello update";
}
第四步,在UserDetailService类中给用户设置权限:
测试:(仍要登入获取权限哦)访问地址:http://localhost:8080/test/update
登入后:
10.2、@PreAuthorize
@PreAuthorize:注解适合进入方法前的权限验证, @PreAuthorize 可以将登录用
户的 roles/permissions 参数传到方法中。
第一步:先开启注解功能:
@EnableGlobalMethodSecurity(prePostEnabled = true)
第二步:还是在controller类的方法中使用,这里还是沿用update
@GetMapping("update")
@PreAuthorize("hasAnyAuthority('admin')")
public String update(){
return "hello update";
}
测试:http://localhost:8080/test/update
第一个注解是角色认证,第二个是权限认证
10.3、@PostAuthorize
先开启注解功能:
@EnableGlobalMethodSecurity(prePostEnabled = true)
@PostAuthorize 注解使用并不多,在方法执行后再进行权限验证,适合验证带有返回值
的权限.
步骤上同!
@GetMapping("update")
@PostAuthorize("hasAnyAuthority('admins')")//这里权限是admins,我们并没有这个权限
public String update(){
System.out.println("update------");
return "hello update";
}
}
测试:http://localhost:8080/test/update
没有权限仍然能运行方法
10.4、@PostFilter(较少使用)
@PostFilter :权限验证之后对数据进行过滤 留下用户名是 admin1 的数据
表达式中的 filterObject 引用的是方法返回值 List 中的某一个元素
用户权限是admin1返回666,admin2返回888
10.5、@PreFilter(较少使用)
@PreFilter: 进入控制器之前对数据进行过滤
11、用户注销(需要实操)
不保留登入信息
第一步:在配置类中添加退出映射地址
//注销
http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/hello").permitAll();
第二步:在登录页面添加一个退出连接
1.修改配置类,登录成功之后跳转到成功页面
2.在成功页面添加超链接,设置退出路径
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
登入成功
<a href="/logout">退出</a>
</body>
</html>
3.条件:登入成功之后,在成功页面点击退出
之后再去访问其他controller是不能进行访问的
测试:访问路径 http://localhost:8080/login.html
访问其他路径:需要登入
12、自动登入
基于数据库的记住我功能
1、原来是用cookie完成的,但它会保存用户信息在浏览器中,信息是不安全的
2、现在我们用安全框架机制实现自动登入(对cookie数据加密实现)
框架帮我做了很多事情,而我们只需要简单的配置就好了。底层流程:
具体实现
第一步:创建数据库表(我们不手建,其底层也会帮我们建的)
CREATE TABLE `persistent_logins` (
`username` varchar(64) NOT NULL,
`series` varchar(64) NOT NULL,
`token` varchar(64) NOT NULL,
`last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE
CURRENT_TIMESTAMP,
PRIMARY KEY (`series`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
第二步:配置类,注入数据源,配置操作数据库对象(配置类重新写一个或者利用之前的也行)
//注入数据源
@Autowired
private DataSource dataSource;
//配置对象
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
/*jdbcTokenRepository.setCreateTableOnStartup(true);如果没有创建表这句话加上底层会帮我们创建表*/
return jdbcTokenRepository;
}
第三步:配置类自动自动登入
.and().rememberMe().tokenRepository(persistentTokenRepository())//操作数据库的对象
.tokenValiditySeconds(60)//设置有效时长单位是秒
.userDetailsService(userDetailsService)//用到查询数据库的服务层类
第四步:在登入页面添加复选框(与上面一样,其属性名固定为remember-me)
测试:访问地址:http://localhost:8080/success.html发现需要登入
点进去看看:
点击自动登入功能之后关闭浏览器,再次访问http://localhost:8080/success.html发现访问此资源不需要再次登入了
13、CSRF理解
加上这句话:
.and().csrf().disable();//关闭csrf防护
到此为止,SpringSecurity的入门以及基本使用就结束了!!!
14、SpringSecurity 微服务权限方案(未完待续)
什么是微服务
微服务认证与授权实现过程
1、认证授权过程分析
未完待续吧。。。