4. Spring Security 认证简介

本文详细解析了Spring Security的认证过程,包括登录验证、权限判断及请求间SecurityContext共享机制,同时介绍了ExceptionTranslationFilter在处理认证异常时的作用。

认证简介

目录

1.1     认证过程

1.2     Web应用的认证过程

1.2.1    ExceptionTranslationFilter

1.2.2    在request之间共享SecurityContext

 

1.1     认证过程

       1、用户使用用户名和密码进行登录。

       2、Spring Security将获取到的用户名和密码封装成一个实现了Authentication接口的UsernamePasswordAuthenticationToken。

       3、将上述产生的token对象传递给AuthenticationManager进行登录认证。

       4、AuthenticationManager认证成功后将会返回一个封装了用户权限等信息的Authentication对象。

       5、通过调用SecurityContextHolder.getContext().setAuthentication(...)将AuthenticationManager返回的Authentication对象赋予给当前的SecurityContext。

 

       上述介绍的就是Spring Security的认证过程。在认证成功后,用户就可以继续操作去访问其它受保护的资源了,但是在访问的时候将会使用保存在SecurityContext中的Authentication对象进行相关的权限鉴定。

 

1.2     Web应用的认证过程

       如果用户直接访问登录页面,那么认证过程跟上节描述的基本一致,只是在认证完成后将跳转到指定的成功页面,默认是应用的根路径。如果用户直接访问一个受保护的资源,那么认证过程将如下:

       1、引导用户进行登录,通常是重定向到一个基于form表单进行登录的页面,具体视配置而定。

       2、用户输入用户名和密码后请求认证,后台还是会像上节描述的那样获取用户名和密码封装成一个UsernamePasswordAuthenticationToken对象,然后把它传递给AuthenticationManager进行认证。

       3、如果认证失败将继续执行步骤1,如果认证成功则会保存返回的Authentication到SecurityContext,然后默认会将用户重定向到之前访问的页面。

       4、用户登录认证成功后再次访问之前受保护的资源时就会对用户进行权限鉴定,如不存在对应的访问权限,则会返回403错误码。

 

       在上述步骤中将有很多不同的类参与,但其中主要的参与者是ExceptionTranslationFilter。

 

1.2.1   ExceptionTranslationFilter

       ExceptionTranslationFilter是用来处理来自AbstractSecurityInterceptor抛出的AuthenticationException和AccessDeniedException的。AbstractSecurityInterceptor是Spring Security用于拦截请求进行权限鉴定的,其拥有两个具体的子类,拦截方法调用的MethodSecurityInterceptor和拦截URL请求的FilterSecurityInterceptor。当ExceptionTranslationFilter捕获到的是AuthenticationException时将调用AuthenticationEntryPoint引导用户进行登录;如果捕获的是AccessDeniedException,但是用户还没有通过认证,则调用AuthenticationEntryPoint引导用户进行登录认证,否则将返回一个表示不存在对应权限的403错误码。

 

1.2.2  在request之间共享SecurityContext

       可能你早就有这么一个疑问了,既然SecurityContext是存放在ThreadLocal中的,而且在每次权限鉴定的时候都是从ThreadLocal中获取SecurityContext中对应的Authentication所拥有的权限,并且不同的request是不同的线程,为什么每次都可以从ThreadLocal中获取到当前用户对应的SecurityContext呢?在Web应用中这是通过SecurityContextPersistentFilter实现的,默认情况下其会在每次请求开始的时候从session中获取SecurityContext,然后把它设置给SecurityContextHolder,在请求结束后又会将SecurityContextHolder所持有的SecurityContext保存在session中,并且清除SecurityContextHolder所持有的SecurityContext。这样当我们第一次访问系统的时候,SecurityContextHolder所持有的SecurityContext肯定是空的,待我们登录成功后,SecurityContextHolder所持有的SecurityContext就不是空的了,且包含有认证成功的Authentication对象,待请求结束后我们就会将SecurityContext存在session中,等到下次请求的时候就可以从session中获取到该SecurityContext并把它赋予给SecurityContextHolder了,由于SecurityContextHolder已经持有认证过的Authentication对象了,所以下次访问的时候也就不再需要进行登录认证了。

### Spring Security引入方法 在Spring Boot项目中引入Spring Security,可在`pom.xml`文件中添加如下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> ``` ### 环境搭建步骤 以在Spring Boot项目中结合Spring MVC和Thymeleaf实现访问图书管理后台页面为例进行环境搭建: 1. 创建Spring Boot项目。可使用Spring Initializr(https://start.spring.io/ )快速创建一个包含Spring Web、Spring Security、Thymeleaf等依赖的项目 [^4]。 2. 配置项目,在`application.properties`或`application.yml`中进行相关配置。 ### 开启安全效果测试方法 在完成环境搭建和配置后,启动Spring Boot应用程序。尝试访问应用的接口或页面,若出现登录页面,则表明Spring Security已生效。例如,在浏览器中访问`http://localhost:8080`,会自动跳转到Spring Security提供的默认登录页面。 ### 安全配置方法 可通过创建一个配置类继承`WebSecurityConfigurerAdapter`来进行安全配置。示例代码如下: ```java 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.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/public/**").permitAll() // 允许公共访问的路径 .anyRequest().authenticated() // 其他请求需要认证 .and() .formLogin() .loginPage("/login") // 自定义登录页面 .permitAll() .and() .logout() .permitAll(); } } ``` ### 内存用户认证方法 在安全配置类中可以配置内存用户,示例如下: ```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth .inMemoryAuthentication() .withUser("user") .password(passwordEncoder().encode("password")) .roles("USER") .and() .withUser("admin") .password(passwordEncoder().encode("admin")) .roles("ADMIN"); } @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/public/**").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll() .and() .logout() .permitAll(); } } ``` ### 自定义用户授权方法 可通过实现`UserDetailsService`接口来自定义用户授权。示例代码如下: ```java import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; @Service public class CustomUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 这里可以从数据库或其他数据源获取用户信息 if ("user".equals(username)) { List<GrantedAuthority> authorities = new ArrayList<>(); authorities.add(new SimpleGrantedAuthority("ROLE_USER")); return new User("user", "$2a$10$4XWjW2W777y8g7j8y2782G782g782G782g782G782g782G782g", authorities); } else if ("admin".equals(username)) { List<GrantedAuthority> authorities = new ArrayList<>(); authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN")); return new User("admin", "$2a$10$4XWjW2W777y8g7j8y2782G782g782G782g782G782g782G782g", authorities); } throw new UsernameNotFoundException("User not found"); } } ``` 然后在安全配置类中使用自定义的`UserDetailsService`: ```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } private final CustomUserDetailsService customUserDetailsService; public SecurityConfig(CustomUserDetailsService customUserDetailsService) { this.customUserDetailsService = customUserDetailsService; } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth .userDetailsService(customUserDetailsService) .passwordEncoder(passwordEncoder()); } @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/public/**").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll() .and() .logout() .permitAll(); } } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值