当前位置: 代码迷 >> 综合 >> spring_security 源码分析 一 认证过程
  详细解决方案

spring_security 源码分析 一 认证过程

热度:23   发布时间:2024-02-11 15:31:22.0

spring_security

介绍

spring_security 的分析主要包括认证和授权两个部分, 并且站在分析源码的角度探究整个执行过程以及原理。

  • 认证
  • 授权

组件版本

  • spring boot 2.2.4.RELEASE
  • springfox-swagger 2 2.4.0
  • spring-boot-starter-data-jpa 2.2.4.RELEASE
  • mysql 5.7

认证

认证

认证就是判断当前用户是否是一个合法用户, 即对比用户输入的用户名密码和数据库中的是否一致,但是这个查询过程是交由security来处理的,并且security会把当前的登陆用户保存到 中以便我们在全局都可以直接得到当前的登陆用户。

登陆分析

1、依赖

        <!--   以下是spring security依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>

2、配置

security 中的认证过程主要是帮助我们校验登录用户的合法性,其中框架本身已经帮助我们做出了大量的封装,登录界面以及校验逻辑都已经做完了,我们需要做的就是简单的配置使得security集成到当前的项目中~具体的集成方法不会描述,本文笔记重在描述整个认证的流程,**我们将从登录界面开始说起一点点深入。**前置的知识点,认证管理器、配置密码加密规则、认证。

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {/*** 配置用户的服务信息* // manager.createUser(User.withUsername("cyt").password("$2a$10$I9pgiXNbkct5cPlhHMvXfe4J7xk1akU6mIWArNTTAihvHUn1jSkpK").authorities("vip").build());* // manager.createUser(User.withUsername("ccc").password("$2a$10$I9pgiXNbkct5cPlhHMvXfe4J7xk1akU6mIWArNTTAihvHUn1jSkpK").authorities("v").build());* @return*/
/* @Override@Beanpublic UserDetailsService userDetailsService() {InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();return manager;}*//*** 配置密码的规则** @return*/@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()//开启登录配置.antMatchers("/test").hasAnyAuthority("vip1") //表示访问 /test 这个接口,需要具备 vip1 这个角色.antMatchers("/cyt").hasAnyAuthority("vip2").antMatchers("/cyt/test").hasAnyAuthority("vip3").anyRequest().permitAll() //表示剩余的其他接口,随便访问.and().formLogin() // 登陆表单.successForwardUrl("/success"); // 成功后的路由跳转}

.formLogin() // 登陆表单

点开此源码

  • getOrApply中传入的是一个配置, 然后本身是一个过滤器链 DefaultSecurityFilterChain
	public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {return getOrApply(new FormLoginConfigurer<>());}
  • 在这个配置中调用了父类了构造器并且传递一个认证过滤器
	public FormLoginConfigurer() {super(new UsernamePasswordAuthenticationFilter(), null);usernameParameter("username");passwordParameter("password");}
  • UsernamePasswordAuthenticationFilter 中继续调用父类,并传递 AntPathRequestMatcher
	public UsernamePasswordAuthenticationFilter() {super(new AntPathRequestMatcher("/login", "POST"));}
  • AbstractAuthenticationProcessingFilter 是一个过滤器, 看他的doFilter
	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)throws IOException, ServletException {// 得到请求HttpServletRequest request = (HttpServletRequest) req;HttpServletResponse response = (HttpServletResponse) res;// 不需要认证直接放行if (!requiresAuthentication(request, response)) {chain.doFilter(request, response);return;}if (logger.isDebugEnabled()) {logger.debug("Request is to process authentication");}// 认证的结果Authentication authResult;try {// 尝试认证authResult = attemptAuthentication(request, response);if (authResult == null) {// return immediately as subclass has indicated that it hasn't completed// authenticationreturn;}sessionStrategy.onAuthentication(authResult, request, response);}catch (InternalAuthenticationServiceException failed) {logger.error("An internal error occurred while trying to authenticate the user.",failed);// 认证失败unsuccessfulAuthentication(request, response, failed);return;}catch (AuthenticationException failed) {// Authentication failed// 认证失败unsuccessfulAuthentication(request, response, failed);return;}// Authentication successif (continueChainBeforeSuccessfulAuthentication) {chain.doFilter(request, response);}// 认证成功successfulAuthentication(request, response, chain, authResult);}

开始逐一分析上述的几个重要方法

attemptAuthentication

  • UsernamePasswordAuthenticationFilter 实际调用子类
    • 从请求中得到用户名和密码
    • 封装成一个用户名密码认证token
    • 放入 setDetails
    • 交由认证管理器认证
	public Authentication attemptAuthentication(HttpServletRequest request,HttpServletResponse response) throws AuthenticationException {if (postOnly && !request.getMethod().equals("POST")) {throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());}String username = obtainUsername(request);String password = obtainPassword(request);if (username == null) {username = "";}if (password == null) {password = "";}username = username.trim();UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);// Allow subclasses to set the "details" propertysetDetails(request, authRequest);return this.getAuthenticationManager().authenticate(authRequest);}

ProviderManager

认证管理器使用子类ProviderManager 中的认证方法, result = provider.authenticate(authentication); 在此方法中执行主要逻辑。其中ProviderManager 的认证方法会遍历所有的 AuthenticationProvider 子类然后逐一认证。这里我们关注 AbstractUserDetailsAuthenticationProvider

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SuA0N5o8-1597544677750)(E:\Code\my\spring_security\README.assets\1597543739925.png)]

		for (AuthenticationProvider provider : getProviders()) {if (!provider.supports(toTest)) {continue;}if (debug) {logger.debug("Authentication attempt using "+ provider.getClass().getName());}try {result = provider.authenticate(authentication);if (result != null) {copyDetails(authentication, result);break;}}catch (AccountStatusException | InternalAuthenticationServiceException e) {prepareException(e, authentication);// SEC-546: Avoid polling additional providers if auth failure is due to// invalid account statusthrow e;} catch (AuthenticationException e) {lastException = e;}}

AbstractUserDetailsAuthenticationProvider

AbstractUserDetailsAuthenticationProvider 本身是一个抽象类,方法会有子类实现, 在它的认证方法中先去缓存中获取用户,如果没有的话会 调用 retrieveUser(username,(UsernamePasswordAuthenticationToken) authentication);其中的authentication 就是之前认证管理器中传递的authRequest = new UsernamePasswordAuthenticationToken( username, password);

username 也就是UsernamePasswordAuthenticationToken 中的username.

		boolean cacheWasUsed = true;UserDetails user = this.userCache.getUserFromCache(username);if (user == null) {cacheWasUsed = false;try {user = retrieveUser(username,(UsernamePasswordAuthenticationToken) authentication);}catch (UsernameNotFoundException notFound) {logger.debug("User '" + username + "' not found");if (hideUserNotFoundExceptions) {throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials","Bad credentials"));}else {throw notFound;}}Assert.notNull(user,"retrieveUser returned null - a violation of the interface contract");}

retrieveUser

DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider

会默认调用DaoAuthenticationProvider 中的实现 在其方法中只是实现了一个逻辑: 根据用户名获得其用户信息返回一个 UserDetailss

UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);

loadUserByUsername

UserDetailsService 是一个接口 其中是有一个 loadUserByUsername方法, 查看到其子类的实现,有基于内存、基于jdbc以及缓存等,慢慢的就和上面的分析对上了。当然还有我们自己定义的实现。这里就找到了从数据库中查询用户信息交由security做认证的第一步。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HdzN6mUW-1597544677753)(E:\Code\my\spring_security\README.assets\1597544486542.png)]

重点过程

授权