springboot+springsecurity入门认识
Spring Security的源码更清楚的了解它的认证逻辑流程。
1.用户认证流程
2.认证结果如何在多个请求之间共享
3.获取认证用户信息
4.springboot+springsecurity入门
一、用户认证流程
提到Spring Security核心就是一系列的过滤器链,当一个请求来的时候,首先要通过过滤器链的校验,校验通过之后才会访问用户各种信息。
这里要说明的是在过滤器的最前端有一个SecurityContextPersistenceFilter,当请求进来和返回的时候都会经过这个过滤器,它主要存放用户的认证信息。
当用户发送登录请求的时候,首先进入到UsernamePasswordAuthenticationFilter中进行校验。
发送登录请求进入源码中,它会进入到UsernamePasswordAuthenticationFilter,在该类中,有一个attemptAuthentication方法在这个方法中,会获取用户的username以及password参数的信息,然后使用构造器new UsernamePasswordAuthenticationToken(username, password)封装为一个UsernamePasswordAuthenticationToken对象,在这个构造器内部会将对应的信息赋值给各自的本地变量,并且会调用父类AbstractAuthenticationToken构造器(这个父类的构造器后面会介绍到),传一个null值进去,为什么是null呢?因为刚开始并没有认证,因此用户没有任何权限,并且设置没有认证的信息(setAuthenticated(false)),最后会进入AuthenticationManager接口的实现类ProviderManager中。
在ProviderManager这个实现类中,它会调用AuthenticationProvider接口的实现类获取用户的信息,用户的信息权限的验证就在该类中校验。进入ProviderManager类中调用authenticate(Authentication authentication)方法,它通过AuthenticationProvider实现类获取用户的登录的方式后会有一个for循环遍历它是否支持这种登录方式,具体的登录方式有表单登录,qq登录,微信登录等。如果都不支持它会结束for循环,如果支持则会进入AuthenticationProvider接口的抽象实现类AbstractUserDetailsAuthenticationProvider中调用 authenticate(Authentication authentication)方法对用户的身份进入校验。 若需要自定义验证,只需要实现AuthenticationProvider接口,并重新authenticate发放,并通过AuthenticationManagerBuilder注册即可,自定义认证的方式有多种,本文暂不做自定义认证。
进入抽象类AbstractUserDetailsAuthenticationProvider的内部的authenticate方法之后,先会判断user是否为空,这个user是UserDetail的对象,如果为空,表示还没有认证,就需要调用retrieveUser方法去获取用户的信息,这个方法是抽象类AbstractUserDetailsAuthenticationProvider的扩展类DaoAuthenticationProvider的一个方法
在该扩展类的retrieveUser方法中调用UserDetailsService这个接口的实现类的loadUserByUsername方法去获取用户信息,而这里我自己编写了实现类MyUserDetail类,在这个实现类中,我们可以编写自己的逻辑,从数据库中获取用户密码等权限信息返回。
在拿到用户的信息后,返回到AbstractUserDetailsAuthenticationProvider类中调用createSuccessAuthentication(principalToReturn, authentication, user)方法,在该方法中会调用三个参数的UsernamePasswordAuthenticationToken构造器,不同于前面调用两个参数的,因为这里已经验证了用户的信息和权限,因此不再是给父类构造器中传null值了,而是用户的权限集合,并且设置认证通过(setAuthenticated(true))
在UsernamePasswordAuthenticationToken的父类中,它会检查用的权限,如果有一个为null,表示权限没有相应的权限,抛出异常。
然后在createSuccessAuthentication方法返回后回到ProvioderManager的authenticate方法中返回result,最后回到UsernamePasswordAuthenticationFilter的刚开始进入的attemptAuthentication方法中返回。
通过上面的源码我们已经深入源码了解到用户的具体认证流程。这里简单总结一下,首先当用户发送请求的时候,会进入到UsernamePasswordAuthenticationFilter中得到一个UsernamePasswordAuthenticationToken,它其实相当于一个令牌,不过还没有经过认证,然后调用AuthenticationManager的实现类ProviderManager中判断登录方式是否支持,如果支持,则会调用AuthenticationProvider接口的抽象实现类AbstractUserDetailsAuthenticationProvider中调用它的扩展类DaoAuthenticationProvider中获取我们自己实现的UserDetails扩展类获取用户密码进行用户身份验证,然后返回该对象,设置UsernamePasswordAuthenticationToken这个令牌认证通过,用户身份校验成功。
二、认证结果如何在多个请求之间共享
下面我们来看看用户在通过身份校验之后,是如何将认证结果在多个请求中共享的呢?肯定是放入session当中的。先来看看流程图。
身份认证成功后,最后在UsernamePasswordAuthenticationFilter返回后会进入一个AbstractAuthenticationProcessingFilter类中调用successfulAuthentication方法中,这个方法最后会返回我们自己定义的登录成功处理器handler,在返回之前,它会调用SecurityContext,最后将认证的结果放入SecurityContextHolder中,SecurityContext类很简单,重写了equals方法和hascode方法,保证了authentication的唯一性。SecurityContextHolder类实际上对ThreadLocal的一个封装,可以在不同方法之间进行通信,我们可以简单理解为线程级别的一个全局变量。因此可以在同一个线程中的不同方法中获取到认证信息。最后会被SecurityContextPersistenceFilter过滤器使用,这个过滤器的作用是什么呢?当一个请求来的时候,它会将session中的值传入到该线程中,当请求返回的时候,它会判断该请求线程是否有SecurityContext,如果有它会将其放入到session中,因此保证了请求结果可以在不同的请求之间共享。
三、获取认证用户信息
如果我们需要获取用的校验过的所有信息,该如何获取呢?上面我们知道了会将校验结果放入session中,因此,我们可以通过session获取。
SecurityContextHolder.getContext().getAuthentication();
四,springboot+springsecurity入门
通过上面的源码分析;我们需要自定义类实现UserDetailsService的loadUserByUsername方法
1,自定义UserDetailsService的loadUserByUsername方法;该方法一般是从数据库用户、角色、角色权限表中查询
2,编写认证结果处理方法
认证成功接口;
从接口看出,我们只需要重新onAuthenticationSuccess方法
认证失败接口与实现
3,配置WebSecurityConfig
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private FailureAuthenticationHandler failureAuthenticationHandler;
@Autowired
private SuccessAuthenticationHandler successAuthenticationHandler;
@Autowired
private SysUserDeatilService userService;
/**
* 注入身份管理器bean
*
* @return
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 把userService 放入AuthenticationManagerBuilder 里
* @param auth
* @throws Exception
*/
/* @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(
new PasswordEncoder() {
@Override
public String encode(CharSequence charSequence) {
return charSequence.toString();
}
@Override
public boolean matches(CharSequence charSequence, String s) {
return s.equals(charSequence.toString());
}
});
}*/
/*如果你想要将密码加密,可以修改 configure() 方法如下
* 本处加密后,AuthenticationProvider中matches
* */
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService)
.passwordEncoder(new BCryptPasswordEncoder());
}
/**
* 设置拦截忽略文件夹,可以对静态资源放行
* 接口一般不要在此处配置
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/js/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//http.httpBasic() //httpBasic 登录
http.formLogin()
.loginPage("/login")// 登陆的url;若前后段分离,去掉该配置
.loginProcessingUrl("/authentication/login") // 自定义登录路径
.failureHandler(failureAuthenticationHandler) // 自定义登录失败处理
.successHandler(successAuthenticationHandler) // 自定义登录成功处理
.and()
.logout()
.logoutUrl("/logout")
.and()
.authorizeRequests()// 对请求授权
// 这些页面不需要身份认证,其他请求需要认证
.antMatchers("/login", "/authentication/login").permitAll()
.anyRequest() // 任何请求
.authenticated() // 都需要身份认证
.and()
.csrf().disable();// 禁用跨站攻击
}
}
原文链接:https://www.cnblogs.com/bbtxxz/p/15901783.html
本文来自博客园,作者:小白有点黑,转载请注明原文链接:https://www.cnblogs.com/bbtxxz/p/15901783.html
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~