SpringSecurity
总课程计划
-
第一课:主要学习理论知识,框架搭建
-
第二课:认证的实际运用
-
第三课:授权
-
第四课:第四阶段,微服务下的运用
今日课程计划
-
搭建一个SpringSecurity项目
-
介绍用户名密码登录过滤器
-
登录流程
-
阅读源码
-
使用自己的数据库登录
-
密码加密
-
修改密码
-
注销登录
今日学习目标
-
认识用户名密码过滤器
-
能够自己配置使用自定义数据库
认证---页面
一、介绍
-
是安全框架
-
认证
- 能不能登录
-
授权
- 登录成功之后,能访问什么
-
防止网络攻击
认证和授权是独立的
二、安全框架的几种方案
1、自己实现
自定义注解 搭配web的拦截器,来控制url地址能否访问。
自定义注解 搭配AOP,来控制某个方法能否访问。
优点:实现快速方便,使用简单,规则都是自己定义的,在公司内部使用方便修改。
缺点:重新学习本公司特有的权限体系,无法防御网络攻击(Sentinel去弥补)
2、Shiro
优点:整合方便,使用简单
缺点:在微服务体系下,不够安全
3、SpringSecurity
优点:能够适应复杂的业务场景,比较适合在微服务体系内使用
缺点:代码复杂,学习难度高,配置多,不容易整合
三、搭建项目
1、新建项目


2、添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
3、启动测试
Security框架在引入之后,自动配置成功,并且自动生效
只要加入了依赖,就会帮助你,拦截所有的请求
被拦截的请求,都会统一的跳转到登录页面


使用自动生成的用户名密码登录
默认的用户名:user
默认的密码:查看控制台
现在所有的请求,都会被拦截,重定向到/login页面。
登录成功之后,又会重定向到之前(最后一次)访问的页面。
- ApiFox访问

四、流程
在SpringSecurity中存在一个过滤器链
这个过过滤器链在一个list中存储,他们之间是有顺序的。
默认情况下,启动的时候,Security会生成默认的用户名和密码,把密码打印到控制台上。
如果后续用于自定义了用户名和密码(使用了自己的用户名和密码)这个流程会消失。
Security中,每个功能,都是使用过滤器来实现的。
最上层第一个被访问的过滤器,是ExceptionTrancationFilter
登录的时候,要经过的过滤器:
-
UsernamePasswordAuthenicationFilter
-
接收页面传入的参数
-
拼装成实体类
-
传递给下一层Provider
-
校验密码是否正确
-
重新拼装实体类,返回给Filter
-
返回的用户信息实体类,存储到上下文对象
-
8、整体流程

五、自定义登录页面
1、创建HTML页面
在resources文件夹里的static中创建页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<form action="/javasmlogin" method="post">
<p>
<input type="text" placeholder="用户名" name="uname">
</p>
<p>
<input type="password" placeholder="用户名" name="pwd">
</p>
<p>
<input type="submit" value="登录">
</p>
</form>
</body>
</html>
2、添加配置类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
//配置SecuritFilterChain 自定义登录页面,认证规则
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
//http对象,支持链式调用
//关闭csrf 跨域请求伪造的控制
http.csrf(csrf -> csrf.disable());
http.authorizeHttpRequests(
auth ->
auth.requestMatchers("/loginpage.html", "/login/**")
.permitAll()
.anyRequest().authenticated() //其他页面,要登录之后才能访问
);//放过登录接口,以及静态页面
// ↓配置表单提交
http.formLogin(form -> {
form.loginPage("/loginpage.html") //自定义登录页面的路径
.loginProcessingUrl("/javasmlogin") //表单提交的路径
.usernameParameter("uname") //自定义用户名的参数名(默认是username)
.passwordParameter("pwd")
.defaultSuccessUrl("/index.html", true) //登录成功之后,跳转的页面,true表示强制跳转
.failureUrl("/loginpage.html?error=true") //登录失败之后,返回的页面,可以自定义
.permitAll(); //以上提到的路径,都放行
});
//注销登录
http.logout(logout->logout
.logoutUrl("/logout")
.logoutSuccessUrl("/loginpage.html")//注销成功之后,跳转的路径
.permitAll()
);
return http.build();
}
}
3、配置静态用户名和密码
过渡测试,帮助大家理解UserDetails的作用
这个代码在后续接入数据库之后,会删除
@Bean
public UserDetailsService userDetailsService(){
//密码已经改成了静态的 amdin 和 123
//{noop} 表示密码不加密,在添加了加密算法之后,这个标识会失效
UserDetails user = User.withUsername("admin").password("{noop}123").roles("ADMIN").build();
return new InMemoryUserDetailsManager(user);
}
六、使用自己的数据库的用户名和密码
1、配置整合MyBatiPlus
参考之前的项目
2、生成代码
在Security框架逻辑中,添加自己的逻辑,查询自己的数据库。
密码在默认情况下是不加密的,需要加上{noop}前缀
添加一条测试数据

3、UserDetails
@NoArgsConstructor
@Getter
public class LoginUserDetails implements UserDetails {
private AdminUser adminUser;
public LoginUserDetails(AdminUser adminUser) {
this.adminUser = adminUser;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
//存储授权列表
return List.of();
}
@Override
public String getPassword() {
//密码
return adminUser.getPassword();
}
@Override
public String getUsername() {
//用户名
return adminUser.getUsername();
}
//【一定记得把下方四个方法的返回值改成true】
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
4、UserDetailsService
@Service
public class UsernameLoginUserDetailsServiceImpl implements UserDetailsService {
@Resource
AdminUserService adminUserService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
AdminUser adminUser = adminUserService.getByUsername(username);
if (adminUser != null){
return new LoginUserDetails(adminUser);
}
return null;
}
}
5、注释掉静态用户名和密码
6、测试
七、密码加密
在配置来看密码加密之后,{noop}就失效了。
所有的密码,都必须是加密之后的密码
1、修改配置类
@Bean
public PasswordEncoder passwordEncoder(){
//指定加密算法
return new BCryptPasswordEncoder();
}
2、修改密码
现有的密码已经无法使用,需要一个接口,来修改密码
- Controller
@RestController
@RequestMapping("/login")
public class LoginController {
@Resource
LoginService loginService;
@PutMapping("/update/password")
public R updatePassword(Integer uid,String password){
loginService.updatePassword(uid,password);
return R.ok();
}
}
- Service
@Service
public class LoginServiceImpl implements LoginService {
@Resource
PasswordEncoder passwordEncoder;
@Override
public void updatePassword(Integer uid, String password) {
//把不加密的密码,改成加密的密码
String newPassword = passwordEncoder.encode(password);
AdminUser adminUser = new AdminUser();
adminUser.setUid(uid);
adminUser.setPassword(newPassword);
adminUser.updateById();
}
}
今日总结
-
阅读源码
-
使用自己的数据库
-
密码加密
-
修改密码