Geeks_Z の Blog Geeks_Z の Blog
首页
  • 学习笔记

    • 《HTML》
    • 《CSS》
    • 《JavaWeb》
    • 《Vue》
  • 后端文章

    • Linux
    • Maven
    • 汇编语言
    • 软件工程
    • 计算机网络概述
    • Conda
    • Pip
    • Shell
    • SSH
    • Mac快捷键
    • Zotero
  • 学习笔记

    • 《数据结构与算法》
    • 《算法设计与分析》
    • 《Spring》
    • 《SpringMVC》
    • 《SpringBoot》
    • 《SpringCloud》
    • 《Nginx》
  • 深度学习文章
  • 学习笔记

    • 《PyTorch》
    • 《ReinforementLearning》
    • 《MetaLearning》
  • 学习笔记

    • 《高等数学》
    • 《线性代数》
    • 《概率论与数理统计》
  • 增量学习
  • 哈希学习
GitHub (opens new window)

Geeks_Z

AI小学生
首页
  • 学习笔记

    • 《HTML》
    • 《CSS》
    • 《JavaWeb》
    • 《Vue》
  • 后端文章

    • Linux
    • Maven
    • 汇编语言
    • 软件工程
    • 计算机网络概述
    • Conda
    • Pip
    • Shell
    • SSH
    • Mac快捷键
    • Zotero
  • 学习笔记

    • 《数据结构与算法》
    • 《算法设计与分析》
    • 《Spring》
    • 《SpringMVC》
    • 《SpringBoot》
    • 《SpringCloud》
    • 《Nginx》
  • 深度学习文章
  • 学习笔记

    • 《PyTorch》
    • 《ReinforementLearning》
    • 《MetaLearning》
  • 学习笔记

    • 《高等数学》
    • 《线性代数》
    • 《概率论与数理统计》
  • 增量学习
  • 哈希学习
GitHub (opens new window)
  • Linux

  • 数据结构与算法

  • 算法设计与分析

  • Java

  • Python

  • 设计模式

  • 计算机网络

  • Spring笔记

  • SpringMVC笔记

  • SpringBoot笔记

  • SpringSecurity

    • SpringSecurity
    • SpringSecurity简介
    • SpringSecurity Web 权限方案
      • configure(AuthenticationManagerBuilder auth)
      • configure(WebSecurity web)
      • configure(HttpSecurity http)
      • UserDetails
      • GrantedAuthority
      • UserDetailsService
      • SecurityContextHolder
      • 方式一:application 配置文件
      • 方式二:配置类
      • 方式三:实现 UserDetailsService 接口
        • 实现 UserDetailsService 接口
    • 新建 Markdown
    • 新建 Markdown
    • Handler
    • 注解使用
    • CSRF
    • 访问控制
  • Elasticsearch笔记

  • RabbitMQ笔记

  • Docker笔记

  • MySQL

  • Redis

  • Mybatis

  • MybatisPlus

  • Nginx

  • Kubernetes笔记

  • Git

  • Software

  • 微服务笔记

  • bug

  • BackEndNotes
  • SpringSecurity
Geeks_Z
2023-09-04
目录

SpringSecurity Web 权限方案

img

image-20230904084020922

配置类伪代码

@Configuration	
@EnableWebSecurity	
public class SecurityConfig extends WebSecurityConfigurerAdapter {	
    @Override	
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {	
        auth.userDetailsService(userDetailService).passwordEncoder(new BCryptPasswordEncoder());	
    }	
    @Override	
    public void configure(WebSecurity web) throws Exception {	
        web.ignoring().antMatchers("/resources/**/*.html", "/resources/**/*.js");	
    }	
    @Override	
    protected void configure(HttpSecurity http) throws Exception {	
       http	
       .formLogin()	
       .loginPage("/login_page")	
       .passwordParameter("username")	
       .passwordParameter("password")	
       .loginProcessingUrl("/sign_in")	
       .permitAll()	
       .and().authorizeRequests().antMatchers("/test").hasRole("test")	
       .anyRequest().authenticated().accessDecisionManager(accessDecisionManager())	
       .and().logout().logoutSuccessHandler(new MyLogoutSuccessHandler())	
       .and().csrf().disable();	
       http.addFilterAt(getAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class);	
       http.exceptionHandling().accessDeniedHandler(new MyAccessDeniedHandler());	
       http.addFilterAfter(new MyFittler(), LogoutFilter.class);	
    }	
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

配置简介

configure(AuthenticationManagerBuilder auth)

AuthenticationManager 的建造器,配置 AuthenticationManagerBuilder 会让Security 自动构建一个 AuthenticationManager(该类的功能参考流程图);如果想要使用该功能你需要配置一个 UserDetailService 和 PasswordEncoder。

  • UserDetailsService 用于在认证器中根据用户传过来的用户名查找一个用户
  • PasswordEncoder 用于密码的加密与比对,我们存储用户密码的时候用PasswordEncoder.encode() 加密存储,在认证器里会调用 PasswordEncoder.matches() 方法进行密码比对。如果重写了该方法,Security 会启用 DaoAuthenticationProvider 这个认证器,该认证就是先调用 UserDetailsService.loadUserByUsername 然后使用 PasswordEncoder.matches() 进行密码比对,如果认证成功成功则返回一个 Authentication 对象。

configure(WebSecurity web)

这个配置方法用于配置静态资源的处理方式,可使用 Ant 匹配规则。

configure(HttpSecurity http)

http	
.formLogin()	
.loginPage("/login_page")	
.passwordParameter("username")	
.passwordParameter("password")	
.loginProcessingUrl("/sign_in")	
.permitAll()
1
2
3
4
5
6
7

这是配置登录相关的操作。从方法名可知,配置了登录页请求路径,密码属性名,用户名属性名,和登录请求路径,permitAll()代表任意用户可访问。

http	
.authorizeRequests()	
.antMatchers("/test").hasRole("test")	
.anyRequest().authenticated()	
.accessDecisionManager(accessDecisionManager());
1
2
3
4
5

以上配置是权限相关的配置,配置了一个 /test url 该有什么权限才能访问, anyRequest() 表示所有请求,authenticated() 表示已登录用户才能访问, accessDecisionManager() 表示绑定在 url 上的鉴权管理器

为了对比,现在贴出另一个权限配置清单:

http.authorizeRequests()	
.antMatchers("/tets_a/**","/test_b/**").hasRole("test")	
.antMatchers("/a/**","/b/**").authenticated()	
.accessDecisionManager(accessDecisionManager())
1
2
3
4

我们可以看到权限配置的自由度很高,鉴权管理器可以绑定到任意 url 上;而且可以硬编码各种 url 权限:

http	
.logout()	
.logoutUrl("/logout")	
.logoutSuccessHandler(new MyLogoutSuccessHandler())
1
2
3
4

登出相关配置,这里配置了登出 url 和登出成功处理器:

http	
.exceptionHandling()	
.accessDeniedHandler(new MyAccessDeniedHandler());
1
2
3

上面代码是配置鉴权失败的处理器。

http.addFilterAfter(new MyFittler(), LogoutFilter.class);	
http.addFilterAt(getAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class);
1
2

上面代码展示如何在过滤器链中插入自己的过滤器,addFilterBefore 加在对应的过滤器之前,addFilterAfter 加在对应的过滤器之后,addFilterAt 加在过滤器同一位置,事实上框架原有的 Filter 在启动 HttpSecurity 配置的过程中,都由框架完成了其一定程度上固定的配置,是不允许更改替换的。根据测试结果来看,调用 addFilterAt 方法插入的 Filter ,会在这个位置上的原有 Filter 之前执行。

关于 HttpSecurity 使用的是链式编程,其中 http.xxxx.and.yyyyy 这种写法和 http.xxxx;http.yyyy 写法意义一样。

自定义 AuthenticationManager 和 AccessDecisionManager

重写 authenticationManagerBean() 方法,并构造一个 authenticationManager:

@Override	
public AuthenticationManager authenticationManagerBean() throws Exception {	
ProviderManager authenticationManager = new ProviderManager(Arrays.asLis(getMyAuthenticationProvider(),daoAuthenticationProvider()));	
return authenticationManager;	
}
1
2
3
4
5

我这里给 authenticationManager 配置了两个认证器,执行过程参考流程图。

定义构造AccessDecisionManager的方法并在配置类中调用,配置参考 configure(HttpSecurity http) 说明:

public AccessDecisionManager accessDecisionManager(){	
List<AccessDecisionVoter<? extends Object>> decisionVoters	
= Arrays.asList(	
new MyExpressionVoter(),	
new WebExpressionVoter(),	
new RoleVoter(),	
new AuthenticatedVoter());	
return new UnanimousBased(decisionVoters);	
}
1
2
3
4
5
6
7
8
9

投票管理器会收集投票器投票结果做统计,最终结果大于等于0代表通过;每个投票器会返回三个结果:-1(反对),0(通过),1(赞成)。

Security 权限系统

UserDetails

Security 中的用户接口,我们自定义用户类要实现该接口。

GrantedAuthority

Security 中的用户权限接口,自定义权限需要实现该接口:

public class MyGrantedAuthority implements GrantedAuthority {	
private String authority;	
}
1
2
3

authority 表示权限字段,需要注意的是在 config 中配置的权限会被加上 ROLE_ 前缀,比如我们的配置 authorizeRequests().antMatchers("/test").hasRole("test"),配置了一个 test 权限但我们存储的权限字段(authority)应该是 ROLE_test 。

UserDetailsService

Security 中的用户 Service,自定义用户服务类需要实现该接口:

@Service	
public class MyUserDetailService implements UserDetailsService {	
	@Override	
	public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {	
	return.....	
	}	
}
1
2
3
4
5
6
7

loadUserByUsername的作用在上文中已经说明,就是根据用户名查询用户对象。

SecurityContextHolder

用户在完成登录后 Security 会将用户信息存储到这个类中,之后其他流程需要得到用户信息时都是从这个类中获得,用户信息被封装成 SecurityContext ,而实际存储的类是 SecurityContextHolderStrategy ,默认的SecurityContextHolderStrategy 实现类是 ThreadLocalSecurityContextHolderStrategy 它使用了ThreadLocal来存储了用户信息。

手动填充 SecurityContextHolder 示例:

UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("test","test",list);	
SecurityContextHolder.getContext().setAuthentication(token);
1
2

对于使用 token 鉴权的系统,我们就可以验证token后手动填充SecurityContextHolder,填充时机只要在执行投票器之前即可,或者干脆可以在投票器中填充,然后在登出操作中清空SecurityContextHolder。

设置登录系统的账号、密码

方式一:application 配置文件

spring:
  security:
    user:
      name: hong
      password: 123
1
2
3
4
5

方式二:配置类


/**
 * 1.创建一个配置类继承自WebSecurityConfigurerAdapter
 * 2.重写configure(AuthenticationManagerBuilder auth)方法, 设置用户名和密码
 * 3.添加PasswordEncoder组件, 可能是用来解码的吧
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // BCryptPasswordEncoder类可以给字符串进行加密
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        String password = passwordEncoder.encode("123");
        
        auth.inMemoryAuthentication().withUser("Tom").password(password).roles("admin");
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

方式三:实现 UserDetailsService 接口

/**
 * 1.创建一个配置类继承自WebSecurityConfigurerAdapter
 * 2.重写configure(AuthenticationManagerBuilder auth)方法, 给auth传入一个UserDetailsService接口的实现类
 * 3.编写一个UserDetailsService的实现类, 重写loadUserByUsername方法, 进行用户名判断, 并返回UserDetails对象交给SpringSecurity进行密码的判断
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    /**
     * 密码解析器:
     * 作用:
     *  1.SpringSecurity会使用此组件对密码进行加密,
     *  2.SpringSecurity会使用此组件对userDetailsService返回UserDetails对象中保存的密码与前端发送的密码进行比较
     *
     * PasswordEncoder接口定义的方法的含义:
     *  encode: 对密码进行加密
     *  matches: 前端提交的原始密码与UserDetails对象中保存的密码进行比较, 如果一致返回true, 反之false
     *  upgradeEncoding: 是否需要再次加密
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

实现 UserDetailsService 接口

@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
   /**
     * 作用: 进行用户名的判断
     * 运行流程: 这个方法会传入一个用户名, 我们可以根据用户名进行查询, 并将查询结果交给SpringSecurity,
     *          SpringSecurity会使用PasswordEncoder密码解析器帮我们进行密码的判断操作, 我们需要负责用户名的判断。
     * 总结: SpringSecurity会根据我们返回的UserDetails对象进行密码的判断, 但是并不会判断用户名,
     *       因此我们需要编写用户名的判断操作
     * @param username 登录时传入的用户名
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if(!"Jerry".equals(username)){
            throw new UsernameNotFoundException("用户不存在");
        }
        String password = new BCryptPasswordEncoder().encode("123");
        // 在这里可以给登录用户设置权限和角色, 使用逗号分割, xxx为权限, ROLE_xxx为角色
        List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
        return new User("Jerry", password, auths);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
上次更新: 2024/03/29, 08:56:31
SpringSecurity简介
新建 Markdown

← SpringSecurity简介 新建 Markdown→

最近更新
01
并行训练
03-29
02
tensor维度转换
03-26
03
ResNet源码解读
03-23
更多文章>
Theme by Vdoing | Copyright © 2022-2024 Geeks_Z | MIT License
京公网安备 11010802040735号 | 京ICP备2022029989号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式