十分钟带你入门SpringSecurity
:house_with_garden: 博客首页:派 大 星
:golf: 欢迎关注 :heart: 点赞 :school_satchel: 收藏 :pencil2: 留言
:roller_coaster: 本文由派大星原创编撰
:construction: 系列专栏:《安全框架》
🎈 技术要学以致用,而不仅停留在学习阶段
@TOC
🍉 学前测试
添加配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()//表单登录
.and()
.authorizeRequests()//认证配置
.anyRequest()//任何请求
.authenticated();//都需要身份认证
}
}
SpringSecurity启动项目有一个默认的登录页面
默认用户名:user
🍒 权限管理中的相关概念
主体:principal
使用系统的用户或设备从其他系统远程登录的用户等等。简单说就是谁使用系统谁就是主体
认证:authentication
权限管理的系统确认一个主体的身份,允许主体进入系统,简单说就是主体
证明自己是谁!笼统的认为就是登录操作
授权:authorization
将操作系统的权利
、授权
、主体
,这样主体就具备了操作系统中特定功能的能力,简单来说,授权就是给用户分配权限
① 添加一个控制器进行访问
@RestController
public class IndexController {
@GetMapping("/index")
public String index(){
return "success";
}
}
测试结果
② SpringSecurity基本原理
SpringSecurity本质是一个过滤器链,从启动是可以获取过滤器链
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter
org.springframework.security.web.context.SecurityContextPersistenceFilter
org.springframework.security.web.header.HeaderWriterFilter
org.springframework.security.web.csrf.CsrfFilter
org.springframework.security.web.authentication.logout.LogoutFilter
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter
org.springframework.security.web.savedrequest.RequestCacheAwareFilter
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
org.springframework.security.web.authentication.AnonymousAuthenticationFilter
org.springframework.security.web.session.SessionManagementFilter
org.springframework.security.web.access.ExceptionTranslationFilter
org.springframework.security.web.access.intercept.FilterSecurityInterceptor
代码底层流程:重点看三个过滤器:
FilterSecurityInterceptor
:是一个方法级的权限过滤器,基本位于过滤链的最底部
super.beforeInvocation(filterInvocation)
表示查看之前的filter
是否通过
filterInvocation.getChain().doFilter(filterInvocation.getRequest(),filterInvocation.getResponse)
表示真正的调用后台的服务
ExceptionTranslationFilter
:是个异常过滤器,用来处理在认证授权过程中抛出的异常
UsernamePasswordAuthenticationFilter
:对/login
的POST
请求做拦截,校验表单中用户名、密码
③ 过滤器如何进行加载的?
- 使用
SpringSecurity
配置过滤链器DelegatingFilterProxy
通过getBean()
获取过滤器的名称FilterChainProxy
将现有的过滤器通过this.getFilters()
添加到List<Filter>
的过滤链中去
④ UserDetailsService接口
当什么也没有配置的时候,账号和密码是由SpringSecurity
定义生成的,而在实际的项目中账号和密码都是从数据库中查询出来的。所以要通过自定义逻辑控制认证逻辑。
需要自定义逻辑时,只需要实现UeserDetailsService接口即可!
返回值:UserDetails
这个类是系统默认的用户主体
public interface UserDetails extends Serializable {
/**
* 返回授予用户的权限,无法返回null
*/
Collection<? extends GrantedAuthority> getAuthorities();
/**
* 返回用于验证用户的密码
*/
String getPassword();
/**
* 返回用于验证用户的用户名,没有返回null
*/
String getUsername();
/**
* 表示判断账户是否过期
*/
boolean isAccountNonExpired();
/**
* 表示判断账户是否被锁定
*/
boolean isAccountNonLocked();
/**
* 表示凭证{密码}是否过期
*/
boolean isCredentialsNonExpired();
/**
* 表示当前用户是否可用
*/
boolean isEnabled();
}
以下是UserDetails的实现类
以后我们只需要使用User
这个实体类即可!
- 方法参数:
username
表示用户名。此值是客户端表单传递过来的数据,默认情况下必须叫username
,否则无法接收!
⑤ PasswordEncoder接口
//表示把参数按照特定的解析规则进行解析
String encode(CharSequence rawPassword);
/**
表示验证从存储中获取的编码密码与编码后提交的原始密码是否匹配,如果密码匹配,则返回true,如果不匹配,则返回 false,第一个参数表示需要被解析的密码,第二个参数表示存储的密码
*/
boolean matches(CharSequence rawPassword,String encodedPassword);
//表示如果解析密码能够再次进行解析,且达到更安全的结果则返回 true,否则返回false,默认返回false
default boolean upgradeEncoding(String encodedPassword){
return false;
}
BCryptPasswordEncoder
是Spring Security
官方推荐的密码解析器,平时多使用这个解析器。
BCryptPasswordEncoder
是对bcrypt
强散列方法的具体实现。是基于Hash
算法实现的单向加密,可以通过strength
控制加密强度,默认为10
测试
@Test
public void testBCrypt(){
// 创建密码解析器
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
// 对密码进行加密
String password = passwordEncoder.encode("admin");
System.out.println("加密之后的密码:"+password);
//判断原始密码与加密后的密码是否一致
boolean isTrue = passwordEncoder.matches("admin", password);
System.out.println("密码是否相等:"+isTrue);
}
2.6 SpringBoot对 Security的自动配置
https://docs.spring.io/springsecurity/site/docs/5.3.4.RELEASE/reference/html5/#servlet-hello
🌽 SpringSecurity Web权限方案
① 设置登录的用户名和密码
- 方式一:配置文件
spring.security.user.name=admin
spring.security.user.password=admin
- 方式二:配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String password = passwordEncoder.encode("123");
auth.inMemoryAuthentication().withUser("lucy").password(password).roles("admin");
}
}
- 方式三:自定义编写实现类
第一步 创建配置类,设置使用哪个userDetailsService实现类
第二步:编写实现类,返回User对象,User对象有用户名密码和操作权限
② 自定义设置登录页面不需要认证可以访问
- 在配置类实现相关的配置
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()//自定义自己编写的登录页面
.loginPage("/login.html")//登录页面设置
//填写完用户名和密码之后需要提交表单操作到指定的Controller中
.loginProcessingUrl("/user/login")//登录访问路径
.defaultSuccessUrl("/index").permitAll()//默认登录成功,跳转到的路径
.and().authorizeRequests()//定义哪些url需要认证,哪些不需要认证
.antMatchers("/","/index","/user/login").permitAll()//设置哪些路径可以直接访问,不需要认证
.anyRequest().authenticated()//所有请求都需要认证
.and().csrf().disable();//关闭csrf防护
}
③ 基于角色或权限进行访问控制
(1) hasAuthority方法
如果当前的主体具有指定的权限,则返回
true
,否则返回false
- 修改配置类
-
修改
MyUserDetailsService
手动给登录用户添加所属权限
- 权限为
admins
- 权限为
- 权限为
其他
时:
(2) hasAnyAuthority方法
如果当前的主体有任何提供的角色(给定的作为一个逗号分隔的字符串列表)的话,返回
true
(3) hasRole方法
如果用户具备给定的角色就允许访问,否则出现403
如果当前主体具有指定的角色,则返回true
底层源码
private static String hasRole(String role) {
Assert.notNull(role, "role cannot be null");
Assert.isTrue(!role.startsWith("ROLE_"),
() -> "role should not start with 'ROLE_' since it is automatically inserted. Got '" + role + "'");
return "hasRole('ROLE_" + role + "')";
}
注意:由于底层对角色信息进行了甄别,判断角色是否以ROLE_
开头,如果不是则自动拼接,所以在测试的时候需要添加ROLE_
(4) hasAnyRole
表示用户具备任何一个条件都可以访问。
给用户添加角色:
修改配置类
④ 自定义403没有权限访问页面
在配置类中进行配置
⑤ 注解使用
(1) @Secured
判断是否具有角色,另外注意的是这里匹配的字符串需要添加前缀ROLE_
。
使用之前需要先开启注解功能!
@EnableGlobalMethodSecurity(securedEnabled = true)
@EnableGlobalMethodSecurity(securedEnabled = true)
@SpringBootApplication
@MapperScan("com.pdx.mapper")
public class SpringSecurity03Application {
public static void main(String[] args) {
SpringApplication.run(SpringSecurity03Application.class, args);
}
}
编写前端控制器使用注解,设置角色
@GetMapping("/update")
@Secured({"ROLE_sale","ROLE_manager"})
@ResponseBody
public String update(){
return "hello update";
}
userDetailsService设置用户角色
(2) @PreAuthorize
先开启注解功能:
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
@PreAuthorize
:注解适合进入方法前的权限验证,@PreAuthorize
可以将登录用户的roles/permissions
参数传到方法中。
在controller的方法上添加注解
(3) @PostAuthorize
先开启注解功能
@EnableGlobalMethodSecurity(prePostEnabled = true)
@PostAuthorize
注解使用并不多,在方法执行后再进行权限验证,适合验证带有返回值的权限
在controller的方法上添加注解
如果使用一个没有权限访问此资源的用户去访问此资源的时候肯定是要跳转到之前我们自定义403页面的,但是实际上此方法已经执行过了!
(4) @PostFilter【少用】
@PostFilter
:权限验证之后对数据进行过滤,留下用户名是admin1的数据
表达式中的filterObject
引用的是方法返回值List
中的某一个元素
@GetMapping("/getAll")
@PostAuthorize("hasAnyAuthority('admins')")
@PostFilter("filterObject.username == 'admin1'")
public List<Users> getAllUsers(){
List<Users> list = new ArrayList<>();
list.add(new Users(11,"admin1","666"));
list.add(new Users(21,"admin2","888"));
System.out.println(list );
return list;
}
(5) @PreFilter【少用】
@PreFilter
:进入控制器之前对数据进行过滤
@GetMapping("/putAll")
@PreAuthorize("hasAnyAuthority('admins')")
@PreFilter(value = "filterObject.id%2==0")
public List<Users> putAll(@RequestBody List<Users> list){
list.forEach(t ->{
System.out.println(t.getId()+"\t"+t.getUsername());
});
return list;
}
⑥ 用户注销
(1) 在登录页面添加一个退出连接
<body>
登录成功<br>
<a href="/logout">退出</a>
</body>
修改配置类
⑦ 自动登录(Remember me)
(1) 实现原理
第一步:创建相关表
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`)
);
第二步:配置类,注入数据源,配置操作数据库对象
//注入数据源
@Autowired
private DataSource dataSource;
//配置对象
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
//在启动时是否创建表
//jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
第三步:配置类中配置自动登录
第四步:在
login.html
添加checkbox
注意:SpringSecurity底层封装了只能识别 name= "remember-me"
⑧ CSRF
(1) CSRF理解
跨站请求伪造(英语:Cross-site request forgery
),也被成为one-click attack
或者session riding
,通常缩写为CSRF
或者XSRF
,是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法,跟跨网站脚本(XXS)
相比,XXS
利用的是用户对指定网站的信任,CSRF
利用的是网站对用户网页浏览器的信任。
从SpringSecurity4.0
开始,默认情况下会启用CSRF
保护,以防止CSRF
攻击应用程序,SpringSecurity CSRF
会针对PATH,POST、PUT和DELETE
方法进行防护。
(2) 示例
在登录页面添加一个隐藏域
<input type="hidden" th:if="${_csrf}!=null" th:value="${_csrf.token}" name="_csrf"/>
关闭安全配置类中的csrf
关于SpringSecurity的介绍就到这里了,相信诸佬也是学到了不少。
❗ ❗ 技术不仅要学而且还要学以致用才是学习的最终目的 ❗ ❗
- 点赞
- 收藏
- 关注作者
评论(0)