spring security学习笔记
1、spring security简介
spring security的核心功能主要包括
- 认证(你是谁)
- 授权(你能干什么)
- 攻击防护(防止伪造身份)
其核心就是一组过滤器链,项目启动后将会自动配置。最核心的就是Basic Authentication Filter 用来认证用户的身份,一个在spring security中一种过滤器处理一种认证方式。
2、spring security Web权限方案
当没有设置登录系统的用户名和密码时,默认的账号时user,项目启动时,系统会给控制台发送一条密码,用这个账号加密码就可以登录系统
//系统给控制台发送的密码
Using generated security password: dae47545-7130-4fa1-aacf-aa8b8c76f40a
2.1、设置登录系统的账号密码
方式一:配置文件(application.properties)修改登录的账号和密码
# 通过配置文件的方式配置security的用户名和密码
spring.security.user.name=atguigu #用户名
spring.security.user.password=atguigu #密码
方式二:通过配置类
注意:在使用密码加密的时候必须得将BCryptPasswordEncoder对象加入到容器中,否则会报错
package com.atguigu.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration//声明该类是一个配置类
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//重写configure方法
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
//密码加密对象,BCrypt强哈希方法 每次加密的结果都不一样。
BCryptPasswordEncoder passwordEncoder=new BCryptPasswordEncoder();
//对密码进行加密
String passwprd = passwordEncoder.encode("123");
//设置用户名、密码、以及角色
auth.inMemoryAuthentication().withUser("root").password(passwprd).roles("admin");
}
//使用了密码加密,必须得把BCryptPasswordEncoder对象添加到容器中
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
方式三:自定义编写实现类
- 1、创建配置类,设置使用哪个userDetailsService实现类
package com.atguigu.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.crypto.password.PasswordEncoder;
@Configuration//声明该类是一个配置类
public class SecurityConfigTest extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
//重写configure方法
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
//设置成自己编写的实现类
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
//使用了密码加密,必须得把BCryptPasswordEncoder对象添加到容器中
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
- 2、编写实现类,返回User对象,User对象有用户名密码和操作权限
package com.atguigu.service;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
@Service("userDetailsService")//这里的名字要和配置类里面注入对象的名字相同
public class MyUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
List<GrantedAuthority> auths= AuthorityUtils.commaSeparatedStringToAuthorityList("role");
return new User("mary",new BCryptPasswordEncoder().encode("123"),auths);
}
}
3、查询数据库完成用户认证
整合mybatisPlus完成数据库操作
第一步:引入相关依赖
<!--mybitisplus依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<!--mysql依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--Lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
第二步:创建数据库表
user表:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I6ifPm1j-1631511850127)(C:\Users\fei\AppData\Roaming\Typora\typora-user-images\image-20210910193046775.png)]
第三步:创建user表对应的实体类
package com.atguigu.entity;
import lombok.Data;
@Data
public class User {
private Integer id;
private String username;
private String password;
}
第四步:整合mybatisPlus,创建接口,继承mybatisPlus的接口
package com.atguigu.mapper;
import com.atguigu.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
@Repository//防止MyUserDetailsService中注入UserMapper对象报红,报红也能正常使用
public interface UserMapper extends BaseMapper<User> {
}
第五步:在MyUserDetailsService中调用mapper里面的方法查询数据库进行用户认证
package com.atguigu.service;
import com.atguigu.mapper.UserMapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//创建一个条件构造器
QueryWrapper<com.atguigu.entity.User> wrapper=new QueryWrapper();
//根据用户名查询数据库 == where username=?
wrapper.eq("username",username);
//selectOne():得到某一条记录
com.atguigu.entity.User user = userMapper.selectOne(wrapper);
//判断
if (user==null){//数据库没有用户名,认证失败
throw new UsernameNotFoundException("用户名不存在,认证失败");
}
//从查询数据库返回user对象,返回用户名和密码
List<GrantedAuthority> auths= AuthorityUtils.commaSeparatedStringToAuthorityList("role");
return new User(user.getUsername(),new BCryptPasswordEncoder().encode(user.getPassword()),auths);
}
}
第六步:在启动类上添加MapperScan注解
package com.atguigu;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.atguigu.mapper")
public class Springbootweb1Application {
public static void main(String[] args) {
SpringApplication.run(Springbootweb1Application.class, args);
}
}
第七步:配置数据库信息
# 配置数据库连接信息
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/student?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456
4、自定义登录页面,不需要认证也可以访问
第一步:在配置类(SecurityConfigTest)中实现相关的配置
在SecurityConfigTest配置类中重写下面这个方法
@Override
protected void configure(HttpSecurity http) throws Exception{
//自定义自己编写的登录页面
http.formLogin()
.loginPage("/login.html") //跳转到登录页面设置
.loginProcessingUrl("/user/login") //登录访问路径
.defaultSuccessUrl("/test/index").permitAll() //登录成功之后 访问路径
.and().authorizeRequests()
.antMatchers("/","/user/login,/static/**").permitAll() //设置哪些路径可以直接访问,不需要认证
.anyRequest().authenticated()
.and().csrf().disable(); //关闭csrf防护
}
第二步:创建相关页面(login.html)和Controller
注意:表单中的用户名的name必须是username,表单中的密码的name必须是password,提交方式必须是post,否则无法识别
<!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="登录">
</form>
</body>
</html>
创建Controller
package com.atguigu.controller;
import com.atguigu.entity.User;
import com.atguigu.service.MyUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/test")
public class testController {
@Autowired
ApplicationContext applicationContext;
@GetMapping("hello")
public String hello(){
return "hello security";
}
@GetMapping("aaa")
public String hello1(){
return "hello aaa";
}
@GetMapping("index")
public Object index(){
System.out.println(applicationContext.getBean("userBean").toString());
return applicationContext.getBean("userBean");
}
}
5、基于角色或权限进行访问控制
5.1、hasAuthority方法与hasAnyAuthority方法
如果当前的主体具有指定的权限,则返回true,否则返回false
第一步:在配置类设置当前访问地址有哪些权限
//设置当前登录用户只有具有某个权限才可以访问这个路径,例如admins权限
.antMatchers("/test/index").hasAuthority("admins")
第二步:在UserDetailsService中,设置返回User对象的权限
1、设置单个
//创建用户权限对象,并设置对象的权限
List<GrantedAuthority> auths= AuthorityUtils.commaSeparatedStringToAuthorityList("admins");
2、设置多个对象权限,之间用逗号隔开
//设置多个用户权限,多个权限之间用逗号隔开
.antMatchers("/test/index").hasAnyAuthority("admin,admins")
5.2、hasRole方法
如果用户具备给的角色就允许访问,否则出现403
如果当前主体具有指定的角色,则返回true
1、设置角色权限
//hasRole方法
.antMatchers("/test/index").hasRole("sale")
2、手动添加角色权限到UserDetailsService中,注意添加角色权限必须得带上前缀 ROLE_
//创建用户权限对象,并设置对象的权限s
List<GrantedAuthority> auths= AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_sale");
5.3、hasAnyRole
表示用户具备任何一个条件都可以访问
1、给用户添加角色,可以设置多个,用逗号隔开
//创建用户权限对象,并设置对象的权限s
List<GrantedAuthority> auths= AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_sale,ROLE_admin");
2、设置用户权限
//hasAnyRole方法
.antMatchers("/test/index").hasAnyRole("sale")
6、自定义403页面
6.1、修改访问配置类
//没有权限跳转到自定义403页面
http.exceptionHandling().accessDeniedPage("/unauth.html");
7、认证授权注解的使用
7.1、@Secured
判断是否具有角色,另外需要注意的是这里匹配的字符串需要添加前缀 “ROLE_”
第一步:使用注解先要开启注解功能
@EnableGlobalMethodSecurity(securedEnabled = true) 注解,可以放在配置类上也可以放在启动类上
@EnableGlobalMethodSecurity(securedEnabled = true)
public class Springbootweb1Application {
第二步:在Controller的方法上使用注解,设置角色
@Secured 中可以写一个或多个权限,多个用大括号包起来,逗号分隔,且设置权限必须得带前缀 “ROLE_”
@GetMapping("update")
@Secured({"ROLE_role","ROLE_manager"})
public String update(){
return "hello update";
}
第三步:在userDetailsService中设置用户角色
List<GrantedAuthority> auths= AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_role");
7.2、@PreAuthorize
@PreAuthorize注解适合进入方法前的权限验证,@PreAuthorize 可以将登录用户的roles/permissions参数传到方法中
第一步:使用注解先要开启注解功能
@EnableGlobalMethodSecurity(securedEnabled = true) 注解,可以放在配置类上也可以放在启动类上
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class Springbootweb1Application {
第二步:在Controller的方法上使用注解,设置角色
登录的用户具有admins权限才能访问
@GetMapping("a")
@PreAuthorize("hasAnyAuthority('admins')")
public String update1(){
return "hello PreAuthorize";
}
第三步:在userDetailsService中设置用户角色
List<GrantedAuthority> auths= AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_role");
7.3、@PostAuthorize
@PostAuthorize 注解使用并不多,在方法执行后再进行权限验证,适合验证带有返回值的权限
第一步:使用注解先要开启注解功能
@EnableGlobalMethodSecurity(securedEnabled = true) 注解,可以放在配置类上也可以放在启动类上
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class Springbootweb1Application {
第二步:在Controller的方法上使用注解,设置角色
登录的用户不具有admin权限也能进入方法,运行方法,先进方法再判断
@GetMapping("b")
@PostAuthorize("hasAnyAuthority('admin')")
public String update2(){
System.out.println("方法运行了.......");
return "hello PreAuthorize";
}
第三步:在userDetailsService中设置用户角色
List<GrantedAuthority> auths= AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_role");
7.4、@PostFilter
这个注解不常用,对方法返回的数据进行过滤
使用:
@GetMapping("c")
@PostAuthorize("hasAnyAuthority('admins')")
@PostFilter("filterObject.username=='admin1'")//只返回admin1的数据
public List<User> update3(){
List<User> list=new ArrayList<>();
list.add(new User(11,"admin1","123"));
list.add(new User(12,"admin2","456"));
System.out.println(list);
return list;
}
7.5、@PreFilter
这个注解不常用,对传入方法的数据进行过滤
使用:
@GetMapping("d")
@PostAuthorize("hasAnyAuthority('admins')")
@PreFilter(value = "filterObject.id%2==0")//只接受id能被2整除的用户传进来的数据
public List<User> update4(@RequestBody List<User> list){
return list;
}
8、用户注销
在配置类中进行配置
logoutUrl:设置一个退出的地址
logoutSuccessUrl:退出之后跳转的地址
http.logout().logoutUrl("/logout").logoutSuccessUrl("/index").permitAll();
用户注销测试
第一步:修改配置类,登录成功之后跳转到成功页面
- 修改配置类中的登录成功访问地址
.defaultSuccessUrl("/success.html").permitAll() //登录成功之后 访问路径
第二步:在成功页面上添加超链接,写出推出路径地址
在static文件夹中新建success.html页面,也就是成功页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>这是登录成功页面</h1>
<a href="/logout">退出</a>
</body>
</html>
第三步:登录成功之后,在成功页面点击退出,在访问其它Controller,不能进行访问
9、自动登录
9.1、自动登录原理
9.2、具体实现
第一步:创建数据库表
使用自动创建表,在配置类里配置
jdbcTokenRepository.setCreateTableOnStartup(true);//自动生成数据库表
第二步:配置类,注入数据源,配置操作数据库对象
//注入数据源
@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)//设置查询数据库对象
第四步:在登录页面添加复选框
在login.html添加复选框
注意:此处name属性值必须为**“remember-me”**,不能改为其它值
<input type="checkbox" name="remember-me">自动登录
<br>
第三步:配置类中配置自动登录
.and().rememberMe().tokenRepository(persistentTokenRepository())//设置自动登录
.tokenValiditySeconds(60)//设置自动登录有效时长,以秒为单位
.userDetailsService(userDetailsService)//设置查询数据库对象
第四步:在登录页面添加复选框
在login.html添加复选框
注意:此处name属性值必须为**“remember-me”**,不能改为其它值
<input type="checkbox" name="remember-me">自动登录
<br>