十分钟带你入门SpringSecurity_spring



目录



🍉  学前测试

添加配置类

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()//表单登录
.and()
.authorizeRequests()//认证配置
.anyRequest()//任何请求
.authenticated();//都需要身份认证
}
}

十分钟带你入门SpringSecurity_自定义_02

SpringSecurity启动项目有一个默认的登录页面
十分钟带你入门SpringSecurity_java_03

​默认用户名:user​

🍒  权限管理中的相关概念

主体:​​principal​

使用系统的用户或设备从其他系统远程登录的用户等等。简单说就是谁使用系统谁就是主体

认证:​authentication​

权限管理的系统确认一个主体的身份,允许主体进入系统,简单说就是​​主体​​证明自己是谁!笼统的认为就是登录操作

授权:​authorization​

将操作系统的​​权利​​​、​​授权​​​、​​主体​​,这样主体就具备了操作系统中特定功能的能力,简单来说,授权就是给用户分配权限

① 添加一个控制器进行访问

@RestController
public class IndexController {

@GetMapping("/index")
public String index(){
return "success";
}
}

测试结果

十分钟带你入门SpringSecurity_自定义_04

② 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​​​:是一个方法级的权限过滤器,基本位于过滤链的最底部
十分钟带你入门SpringSecurity_自定义_05

​super.beforeInvocation(filterInvocation)​​​表示查看之前的​​filter​​是否通过

​filterInvocation.getChain().doFilter(filterInvocation.getRequest(),filterInvocation.getResponse)​​表示真正的调用后台的服务

​ExceptionTranslationFilter​​:是个异常过滤器,用来处理在认证授权过程中抛出的异常

十分钟带你入门SpringSecurity_java_06

​UsernamePasswordAuthenticationFilter​​​:对​​/login​​​的​​POST​​请求做拦截,校验表单中用户名、密码

十分钟带你入门SpringSecurity_web安全_07

③ 过滤器如何进行加载的?

  • 使用​​SpringSecurity​​配置过滤链器
  • ​DelegatingFilterProxy​

通过​​getBean()​​​获取过滤器的名称​​FilterChainProxy​

十分钟带你入门SpringSecurity_spring_08

将现有的过滤器通过​​this.getFilters()​​​添加到​​List<Filter>​​的过滤链中去

十分钟带你入门SpringSecurity_用户名_09十分钟带你入门SpringSecurity_spring_10
十分钟带你入门SpringSecurity_spring_11

④ UserDetailsService接口

当什么也没有配置的时候,账号和密码是由​​SpringSecurity​​定义生成的,而在实际的项目中账号和密码都是从数据库中查询出来的。所以要通过自定义逻辑控制认证逻辑。

​需要自定义逻辑时,只需要实现UeserDetailsService接口即可!​​​十分钟带你入门SpringSecurity_web安全_12

返回值:​​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的实现类

十分钟带你入门SpringSecurity_spring_13

以后我们只需要使用​​User​​这个实体类即可!

十分钟带你入门SpringSecurity_java_14

  • 方法参数:​​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;
}

十分钟带你入门SpringSecurity_spring_15

​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实现类

十分钟带你入门SpringSecurity_用户名_16

第二步:编写实现类,返回User对象,User对象有用户名密码和操作权限

十分钟带你入门SpringSecurity_用户名_17

② 自定义设置登录页面不需要认证可以访问

  • 在配置类实现相关的配置
@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​

  • 修改配置类

十分钟带你入门SpringSecurity_用户名_18

  • 修改​​MyUserDetailsService​​手动给登录用户添加所属权限
  • 权限为​​admins​

十分钟带你入门SpringSecurity_java_19

  • 权限为​​其他​​时:

十分钟带你入门SpringSecurity_java_20

(2) hasAnyAuthority方法

如果当前的主体有任何提供的角色(给定的作为一个逗号分隔的字符串列表)的话,返回​​true​

十分钟带你入门SpringSecurity_java_21

(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 + "')";
}

十分钟带你入门SpringSecurity_java_22

注意:由于底层对角色信息进行了甄别,判断角色是否以​​ROLE_​​​开头,如果不是则自动拼接,所以在测试的时候需要添加​​ROLE_​

十分钟带你入门SpringSecurity_web安全_23

(4) hasAnyRole

表示用户具备任何一个条件都可以访问。

给用户添加角色:

十分钟带你入门SpringSecurity_web安全_24

修改配置类

十分钟带你入门SpringSecurity_web安全_25

④ 自定义403没有权限访问页面

十分钟带你入门SpringSecurity_spring_26

在配置类中进行配置

十分钟带你入门SpringSecurity_web安全_27

⑤ 注解使用

(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设置用户角色

十分钟带你入门SpringSecurity_java_28

(2) @PreAuthorize

先开启注解功能:

​@EnableGlobalMethodSecurity(prePostEnabled = true)​

@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)

​@PreAuthorize​​​:注解适合进入方法前的权限验证,​​@PreAuthorize​​​可以将登录用户的​​roles/permissions​​参数传到方法中。

在controller的方法上添加注解
十分钟带你入门SpringSecurity_自定义_29

(3) @PostAuthorize

先开启注解功能

​@EnableGlobalMethodSecurity(prePostEnabled = true)​

​@PostAuthorize​​注解使用并不多,在方法执行后再进行权限验证,适合验证带有返回值的权限

在controller的方法上添加注解

十分钟带你入门SpringSecurity_web安全_30

如果使用一个没有权限访问此资源的用户去访问此资源的时候肯定是要跳转到之前我们自定义403页面的,但是实际上此方法已经执行过了!
十分钟带你入门SpringSecurity_自定义_31

(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>

修改配置类

十分钟带你入门SpringSecurity_web安全_32
十分钟带你入门SpringSecurity_spring_33

⑦ 自动登录(Remember me)

(1) 实现原理

十分钟带你入门SpringSecurity_web安全_34

第一步:创建相关表

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;
}

第三步:配置类中配置自动登录

十分钟带你入门SpringSecurity_web安全_35

第四步:在​​login.html​​​添加​​checkbox​

十分钟带你入门SpringSecurity_用户名_36

​注意: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_用户名_37

关于SpringSecurity的介绍就到这里了,相信诸佬也是学到了不少。

❗ ❗ 技术不仅要学而且还要学以致用才是学习的最终目的 ❗ ❗

十分钟带你入门SpringSecurity_自定义_38