本篇博客是基于B站尚硅谷 Spring Security 框架教程学习并整理的个人学习笔记,作者是初学者,笔记中若有错误的地方欢迎大家留言评论、批评指正。
Spring Scurity 本质是一个过滤器链。
使用 SpringSecurity 配置过滤器 DelegatingFilterProxy;
在 DelegatingFilterProxy 的 doFilter() 方法中调用了 initDelegate() 方法初始化成员变量 delegate;
delegateToUse = this.initDelegate(wac);
this.delegate = delegateToUse;
在 initDelegate() 方法中通过 getTargetBeanName() 从 Spring 容器中获取到 Filter 的实现类 FilterChainProxy;
在 FilterChainProxy 中的 doFilter() 方法中,最终调用了doFilterInternal()方法;
在 doFilterInternal() 方法中以列表的形式获取到了过滤器链;
List<Filter> filters = this.getFilters((HttpServletRequest)firewallRequest);
使用方法
数据加密接口,用于加密 User 对象里面的密码。
创建 Spring Boot 项目,并在 pom.xml 文件中导入相关依赖
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-securityartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.3.0version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.25version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.16.20version>
dependency>
dependencies>
在数据库中建立一个数据库,然后执行下列 sql 语句创建表
drop table if exists `tb_user_info`;
create table `tb_user_info` (
`id` int(11) auto_increment,
`username` varchar(20) not null,
`password` varchar(20) not null,
primary key(`id`)
)engine=INNODB default charset=utf8;
在项目文件夹下新建包 pojo,然后创建 UserInfo.java
@Data
@AllArgsConstructor
@NoArgsConstructor
//以上三个是 lombok 插件的注解,自动生成构造方法和各个属性的 get/set 方法
@TableName("tb_user_info")
public class UserInfo {
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
private String username;
private String password;
}
在 resource/application.yml 配置如下信息
spring:
datasource:
url: jdbc:mysql://localhost:3306/数据库名?useUnicode=true&characterEncoding=UTF-8&serverTimeZone=Asia/Shanghai
username: 访问数据库的用户名
password: 访问数据库的密码
driver-class-name: com.mysql.cj.jdbc.Driver
在项目文件夹下新建包 dao/mapper,然后创建 UserInfoMapper.java
@Mapper
public interface UserInfoMapper extends BaseMapper<UserInfo> {
}
继承 Mybatis-Plus 中提供的 BaseMapper 类会自动完成简单 CRUD 的接口创建。
在项目文件夹下新建包 service,然后创建自定义的 MyUserDetailsService.java
@Service("myUserDetailsService")
public class MyUserDetailsService implements UserDetailsService {
@Resource
private UserInfoMapper userInfoMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 调用 userInfoMapper 方法,根据用户名查询数据库
QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
wrapper.eq("username", username);
UserInfo userInfo = userInfoMapper.selectOne(wrapper);
// 判断
if(userInfo == null) {
throw new UsernameNotFoundException("用户不存在!");
}
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin,superAdmin,ROLE_admin,ROLE_superAdmin");
return new User(userInfo.getUsername(),
new BCryptPasswordEncoder().encode(userInfo.getPassword()), auths);
}
}
自定义的 MyUserDetailsService 实现 UserDetailsService 接口可以调用 DAO 层的方法查询数据库中的用户信息,然后交由 Spring Security 进行验证。
在项目文件夹下新建包 config,然后创建 WebSecurityConfig.java
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private UserDetailsService myUserDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置没有权限访问跳转自定义页面
http.exceptionHandling().accessDeniedPage("/403.html");
http.formLogin() //自定义登录页面
.loginPage("/login.html") //登录页面设置
.loginProcessingUrl("/user/login") //登录访问路径
.defaultSuccessUrl("/test/index").permitAll() //登录成功之后,跳转路径
.and().authorizeRequests()
.antMatchers("/","user/login").permitAll() //直接放行的页面/资源
//.antMatchers("/test/hello").hasAnyAuthority("admin,superAdmin")
//.antMatchers("/test/hello").hasAnyRole("admin,superAdmin")
.antMatchers("/test/hello").hasRole("visitor")
.anyRequest().authenticated() //任何请求都要授权
.and().csrf().disable(); //关闭跨域请求防护
}
}
代码中是通过配置的方式是实现权限和角色的控制
.antMatchers("URL").hasAuthority("权限名")
.antMatchers("URL").hasAnyAuthority("权限1,权限2...")
.antMatchers("URL").hasRole("角色名")
.antMatchers("URL").hasAnyRole("角色1,角色2...")
在自定义的 MyUserDetailsService 类中的 loadUserByUsername() 方法中编写权限和角色的集合,以 “ROLE_” 为前缀的是角色名。
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin,superAdmin,ROLE_admin,ROLE_superAdmin");
在 resource/static文件夹下创建 login.html 和 403.html
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页title>
head>
<body>
<form action="/user/login" method="post">
用户名:<input name="username" type="text">
br>
密 码:<input name="password" type="password">
br>
<input type="submit" name="login_btn" value="登录"/>
form>
body>
html>
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>无权限title>
head>
<body>
<h1>您没有相关权限,无法访问!h1>
body>
html>
在项目文件夹下新建包 controller,然后创建 TestController.java
@RestController
@RequestMapping("/test")
public class TestController {
@RequestMapping("/hello")
public String hello() {
return "Hello Spring Security!";
}
@RequestMapping("/index")
public String index() {
return "我是起始页!";
}
}
以上代码中是通过在 WebSecurityConfig 配置类中通过配置的方式进行权限和角色的控制,下面则使用注解进行权限和角色的控制。(个人认为注解可以简化代码,更方便理清代码的逻辑)
在启动类/配置类中开启注解
//分别开启 @Secured 和 @PreAuthorize、PostAuthorize 注解
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
@SpringBootApplication
public class SpringSecurityStudyApplication {
public static void main(String[] args) {
SpringApplication.run(SpringSecurityStudyApplication.class, args);
}
}
在 Controller 层的方法上使用注解,设置角色/权限
@RestController
@RequestMapping("/test")
public class TestController {
//@Secured 只作用于用户角色
@RequestMapping("/add")
@Secured({"ROLE_admin","ROLE_superAdmin"})
public String add() {
return "添加用户!";
}
//@PreAuthorize 作用于角色和权限,是在执行方法前验证权限/角色
@RequestMapping("/del")
//@PreAuthorize("hasAnyAuthority('admin','visitor')")
@PreAuthorize("hasAnyRole('ROLE_admin', 'visitor')") //角色名不加前缀也可以
public String del() {
return "删除用户!";
}
//@PostAuthorize 作用于角色和权限,是在执行方法后才验证权限/角色
@RequestMapping("/upd")
@PostAuthorize("hasAnyAuthority('admin','visitor')")
//@PostAuthorize("hasAnyRole('ROLE_admin', 'visitor')") //角色名不加前缀也可以
public String upd() {
//若用户没有相关权限/角色,也会执行下面的语句,但是在网页中会跳转到 403.html
System.out.println("执行了更新用户的方法!");
return "更新用户!";
}
}
在自定义的 MyUserDetailsService 中设置用户角色和权限
@Service("myUserDetailsService")
public class MyUserDetailsService implements UserDetailsService {
@Resource
private UserInfoMapper userInfoMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 调用 userInfoMapper 方法,根据用户名查询数据库
QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
wrapper.eq("username", username);
UserInfo userInfo = userInfoMapper.selectOne(wrapper);
// 判断
if(userInfo == null) {
throw new UsernameNotFoundException("用户不存在!");
}
//!!!在此设置用户角色和权限!!!
List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin,superAdmin,visitor,ROLE_admin,ROLE_superAdmin,ROLE_visitor");
return new User(userInfo.getUsername(),
new BCryptPasswordEncoder().encode(userInfo.getPassword()), auths);
}
}
在 WebSecurityConfig 中的 configure 方法中添加
//配置退出
http.logout().logoutUrl("/logout")
.logoutSuccessUrl("/login.html");
创建一个登录成功页,添加退出的超链接
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>成功页title>
head>
<body>
<h1>登录成功!h1>
<a href="/logout">退出a>
body>
html>
修改配置类中登录成功跳转到的页面URL
//登录成功之后,跳转路径
.defaultSuccessUrl("/loginSuccess.html").permitAll()
登录成功后在成功页面点击退出,再去访问其它URL
在数据库中创建表
drop table if exists `persistent_logins`;
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;
在 WebSecurityConfig 配置类中注入数据源,配置数据库操作对象
@Resource
private DataSource dataSource; //注入数据源
//配置数据库操作对象
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
return jdbcTokenRepository;
}
在 WebSecurityConfig 配置类的 configure() 方法中配置 remember me
.and().rememberMe()
.tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(60) //有效时长, 单位是秒
.userDetailsService(myUserDetailsService)
在登录页面中添加记住我复选框
<input name="remember-me" type="checkbox"/> 记住我
br>
学完微服务后再学习记录…