Spring Security渐入佳境(一)[附] --SpringSecurity的基本原理及源码剖析

本文详细解析了SpringSecurity的鉴权认证流程,包括过滤器调用时序、认证流程及如何获取当前登录信息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Spring Security基本原理

<1>、过滤器调用时序解析

在调用API前通过过滤器链链式调用过滤器,所有过滤器通过后才调用API。
Spring Security过滤器调用时序图:
在这里插入图片描述
自定义鉴权配置:

@Configuration
@EnableWebSecurity
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //http.httpBasic()//spring默认校验方式
        http.formLogin()//表单登陆
            .and()
            .authorizeRequests()//对请求授权
                .antMatchers("/user**").permitAll()
            .anyRequest()
            .authenticated();//进行身份认证
    }
}
过滤器链和WebSecurityConfigurerAdapter :

SecurityContextPersistenceFilter: 过滤器链中的第一个过滤器,第一个进最后一个出的过滤器,SecurityContextPersistenceFilter是承接容器的session与spring security的重要filter,主要工作是从session中获取SecurityContext,然后放到上下文中(线程中),返回时,从线程中获取SecurityContext,若存在则放入session中。之后的filter大多依赖这个来获取登录态。其主要是通过HttpSessionSecurityContextRepository来存取的。
绿色部分的过滤器: 可自行配置时序,或是否需要运行,UsernamePasswordAuthenticationFilter用来处理http.formLogin()的,BasicAuthenticationFilter用来处理http.httpBasic()登录的 。
ExceptionTranslationFilter: 执行过滤器链的下一个过滤器(FilterSecurityInterceptor),捕获抛出的异常,判断异常原因,并重新抛出异常信息。
FilterSecurityInterceptor: 所有的请求到了这一个filter,如果这个filter之前没有执行过的话,那么首先执行的InterceptorStatusToken token = super.beforeInvocation(fi);这句是由AbstractSecurityInterceptor提供。它就是spring security处理鉴权的入口,主要处理登录,获取用户token信息,接着就是根据WebSecurityConfigurerAdapter的一些权限校验的配置进行对应的权限校验,通过则调用API,不通过则抛出异常。

//FilterSecurityInterceptor 部分源码
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
	//省略其他方法...
	public void invoke(FilterInvocation fi) throws IOException, ServletException {
        if (fi.getRequest() != null 
        && fi.getRequest().getAttribute("__spring_security_filterSecurityInterceptor_filterApplied") != null 
        && this.observeOncePerRequest) {
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        } else {
            if (fi.getRequest() != null) {
                fi.getRequest().setAttribute("__spring_security_filterSecurityInterceptor_filterApplied", Boolean.TRUE);
            }

            InterceptorStatusToken token = super.beforeInvocation(fi);

            try {
                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            } finally {
                super.finallyInvocation(token);
            }
            
            super.afterInvocation(token, (Object)null);
        }
    }
}
<2>、认证流程

在这里插入图片描述

发送一个请求的部分源码解读(按调用顺次解读):

(1)UsernamePasswordAuthenticationFilter :
该类主要是生成一个用户票据(包含用户信息,权限,是否认证成功),传递给AuthenticationManager进行认证校验。

public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    //省略部分代码...
	//通过formLogin发起登陆请求主要逻辑
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
    	//默认Post方式才接受
        if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
            String username = this.obtainUsername(request);
            String password = this.obtainPassword(request);
            if (username == null) {
                username = "";
            }
            if (password == null) {
                password = "";
            }
            username = username.trim();
            //UsernamePasswordAuthenticationToken是Authentication的子类
            //token中包含了用户名、密码、是否认证通过(此时为false),权限(此时为空)
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            this.setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }
//省略部分代码...
}

(2)ProviderManager:
该类主要是根据Authentication的类型交给能处理的Provider进行处理。

public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
    //省略部分代码...
    
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    	//authentication有很多种,OAuth,Social等等...
        Class<? extends Authentication> toTest = authentication.getClass();
        AuthenticationException lastException = null;
        Authentication result = null;
        boolean debug = logger.isDebugEnabled();
        Iterator var6 = this.getProviders().iterator();

        while(var6.hasNext()) {
            AuthenticationProvider provider = (AuthenticationProvider)var6.next();
            if (provider.supports(toTest)) {//根据authentication的类型匹配到合适的provider进行认证逻辑的校验
                if (debug) {
                    logger.debug("Authentication attempt using " + provider.getClass().getName());
                }

                try {
                	//校验核心逻辑
                    result = provider.authenticate(authentication);
                    if (result != null) {
                        this.copyDetails(authentication, result);
                        break;
                    }
                } catch (AccountStatusException var11) {
                    this.prepareException(var11, authentication);
                    throw var11;
                } catch (InternalAuthenticationServiceException var12) {
                    this.prepareException(var12, authentication);
                    throw var12;
                } catch (AuthenticationException var13) {
                    lastException = var13;
                }
            }
        }

        if (result == null && this.parent != null) {
            try {
                result = this.parent.authenticate(authentication);
            } catch (ProviderNotFoundException var9) {
                ;
            } catch (AuthenticationException var10) {
                lastException = var10;
            }
        }

        if (result != null) {
            if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
                ((CredentialsContainer)result).eraseCredentials();
            }

            this.eventPublisher.publishAuthenticationSuccess(result);
            return result;
        } else {
            if (lastException == null) {
                lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}"));
            }

            this.prepareException((AuthenticationException)lastException, authentication);
            throw lastException;
        }
    }
//省略部分代码...
}

(3)AbstractUserDetailsAuthenticationProvider:
该类包含了认证流程的主要逻辑,获取用户信息,将已认证的用户凭证返回。

public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {
   //...
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported"));
        String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
        boolean cacheWasUsed = true;
        UserDetails user = this.userCache.getUserFromCache(username);
        if (user == null) {
            cacheWasUsed = false;

            try {
            	//重新获取用户信息
                user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
            } catch (UsernameNotFoundException var6) {
                this.logger.debug("User '" + username + "' not found");
                if (this.hideUserNotFoundExceptions) {
                    throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
                }

                throw var6;
            }

            Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
        }

        try {
        	//预检查 若过期、锁定、冻结,则抛出相应异常
            this.preAuthenticationChecks.check(user);
            //检查密码的正确性,不正确则抛出BadCredentialsException
            this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
        } catch (AuthenticationException var7) {
            if (!cacheWasUsed) {
                throw var7;
            }

            cacheWasUsed = false;
            user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
            this.preAuthenticationChecks.check(user);
            this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
        }
		//提交前检查 检查密码是否过期,过期就抛出异常
        this.postAuthenticationChecks.check(user);
        if (!cacheWasUsed) {
            this.userCache.putUserInCache(user);
        }

        Object principalToReturn = user;
        if (this.forcePrincipalAsString) {
            principalToReturn = user.getUsername();
        }
		//最后一步:创建成功的认证信息并返回
        return this.createSuccessAuthentication(principalToReturn, authentication, user);
    }
    //最后一步:创建成功的认证信息(认证状态为true,包含用户信息,用户权限)
    protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
        UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
        result.setDetails(authentication.getDetails());
        return result;
    }
    //提交前检查(内部类) 检查密码是否过期,过期就抛出异常
    private class DefaultPostAuthenticationChecks implements UserDetailsChecker {
        private DefaultPostAuthenticationChecks() {
        }

        public void check(UserDetails user) {
            if (!user.isCredentialsNonExpired()) {
                AbstractUserDetailsAuthenticationProvider.this.logger.debug("User account credentials have expired");
                throw new CredentialsExpiredException(AbstractUserDetailsAuthenticationProvider.this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.credentialsExpired", "User credentials have expired"));
            }
        }
    }
    //预检查(内部类) 若过期、锁定、禁用,则抛出相应异常
    private class DefaultPreAuthenticationChecks implements UserDetailsChecker {
        private DefaultPreAuthenticationChecks() {
        }

        public void check(UserDetails user) {
            if (!user.isAccountNonLocked()) {//账户是否锁定
                AbstractUserDetailsAuthenticationProvider.this.logger.debug("User account is locked");
                throw new LockedException(AbstractUserDetailsAuthenticationProvider.this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked", "User account is locked"));
            } else if (!user.isEnabled()) {//账户是否禁用
                AbstractUserDetailsAuthenticationProvider.this.logger.debug("User account is disabled");
                throw new DisabledException(AbstractUserDetailsAuthenticationProvider.this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"));
            } else if (!user.isAccountNonExpired()) {//账户是否过期
                AbstractUserDetailsAuthenticationProvider.this.logger.debug("User account is expired");
                throw new AccountExpiredException(AbstractUserDetailsAuthenticationProvider.this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired"));
            }
        }
    }
   //...
}

(3)DaoAuthenticationProvider :
该类获取用户信息的方式是通过可自定义的UserDetailsService进行加载用户信息的。

public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
	protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        Object salt = null;
        if (this.saltSource != null) {
            salt = this.saltSource.getSalt(userDetails);
        }

        if (authentication.getCredentials() == null) {
            this.logger.debug("Authentication failed: no credentials provided");
            throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        } else {
            String presentedPassword = authentication.getCredentials().toString();
            //将提交上来的密码与数据库中的比对,传入盐值解密
            if (!this.passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) {
                this.logger.debug("Authentication failed: password does not match stored value");
                throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
            }
        }
    }
	protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        UserDetails loadedUser;
        try {
        	//调用自定义获取用户信息的方法
            loadedUser = this.getUserDetailsService().loadUserByUsername(username);
        } catch (UsernameNotFoundException var6) {
            if (authentication.getCredentials() != null) {
                String presentedPassword = authentication.getCredentials().toString();
                this.passwordEncoder.isPasswordValid(this.userNotFoundEncodedPassword, presentedPassword, (Object)null);
            }

            throw var6;
        } catch (Exception var7) {
            throw new InternalAuthenticationServiceException(var7.getMessage(), var7);
        }

        if (loadedUser == null) {
            throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
        } else {
            return loadedUser;
        }
    }
}

(4)自定义UserDetailsService:
自定义获取用户信息。

@Component
public class MyUserDetailsService implements UserDetailsService {
    @Autowired
    PasswordEncoder passwordEncoder;
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
    	//...可进行一些数据库查询操作,作为返回值放入User中
        return new User(s, encoderPwd, true, true, true, true, AuthorityUtils.createAuthorityList("admin"));
    }
}

(5)User:
该类仅贴出它的属性值,对属性的获取,设置逻辑省略。

public class User implements UserDetails, CredentialsContainer {
    private static final long serialVersionUID = 420L;
    //密码
    private String password;
    //用户名
    private final String username;
    //权限
    private final Set<GrantedAuthority> authorities;
    //账户是否过期
    private final boolean accountNonExpired;
    //账户是否锁定
    private final boolean accountNonLocked;
    //密码是否过期
    private final boolean credentialsNonExpired;
    //账户是否禁用
    private final boolean enabled;
    //...
}

(6)AbstractAuthenticationProcessingFilter
认证结束会进入该filter。

public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean implements ApplicationEventPublisherAware, MessageSourceAware {
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
        if (!this.requiresAuthentication(request, response)) {
            chain.doFilter(request, response);
        } else {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Request is to process authentication");
            }

            Authentication authResult;
            try {
                authResult = this.attemptAuthentication(request, response);
                if (authResult == null) {
                    return;
                }

                this.sessionStrategy.onAuthentication(authResult, request, response);
            } catch (InternalAuthenticationServiceException var8) {
                this.logger.error("An internal error occurred while trying to authenticate the user.", var8);
                this.unsuccessfulAuthentication(request, response, var8);
                return;
            } catch (AuthenticationException var9) {
                this.unsuccessfulAuthentication(request, response, var9);
                return;
            }

            if (this.continueChainBeforeSuccessfulAuthentication) {
                chain.doFilter(request, response);
            }

            this.successfulAuthentication(request, response, chain, authResult);
        }
    }
    //成功认证
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult);
        }
		//将认证信息放入Security的上下文环境
        SecurityContextHolder.getContext().setAuthentication(authResult);
        this.rememberMeServices.loginSuccess(request, response, authResult);
        if (this.eventPublisher != null) {
            this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
        }
		//认证成功,调用successHandler(可继承并自定义)
        this.successHandler.onAuthenticationSuccess(request, response, authResult);
    }
	//不成功认证
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        SecurityContextHolder.clearContext();
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Authentication request failed: " + failed.toString(), failed);
            this.logger.debug("Updated SecurityContextHolder to contain null Authentication");
            this.logger.debug("Delegating to authentication failure handler " + this.failureHandler);
        }
        this.rememberMeServices.loginFail(request, response);
        //认证失败,调用failureHandler(可继承并自定义)
        this.failureHandler.onAuthenticationFailure(request, response, failed);
    }
}

<3>、获取当前登录信息

获取所有登陆信息:(包含IP,sessionId等一些其他信息)

@RestController
@RequestMapping("/user")
public class UserController {
	//方式1
    @GetMapping("/currentUserOne")
    public Object getCurrentUserOne(){
        return SecurityContextHolder.getContext().getAuthentication();
    }
    //方式2
    @GetMapping("/currentUserTwo")
    public Object getCurrentUserTwo(Authentication authentication){
        return authentication;
    }
}

返回结果:

{
	"authorities": [
		{
			"authority": "admin"
		}
	],
	"details": {
		"remoteAddress": "0:0:0:0:0:0:0:1",
		"sessionId": "1AA3FB90F026A178C5D73A3D4ED52B06"
	},
	"authenticated": true,
	"principal": {
		"password": null,
		"username": "user",
		"authorities": [
			{
				"authority": "admin"
			}
		],
		"accountNonExpired": true,
		"accountNonLocked": true,
		"credentialsNonExpired": true,
		"enabled": true
	},
	"credentials": null,
	"name": "user"
}

获得登陆的用户信息:

@RestController
@RequestMapping("/user")
public class UserController {
	//方式1
    @GetMapping("/currentUserOne")
    public Object getCurrentUserOne(){
        return SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    }
    //方式2
    @GetMapping("/currentUserTwo")
    public Object getCurrentUserTwo(@AuthenticationPrincipal UserDetails userDetails){
        return userDetails;
    }
}

返回结果:

{
	"password": null,
	"username": "user",
	"authorities": [
		{
			"authority": "admin"
		}
	],
	"accountNonExpired": true,
	"accountNonLocked": true,
	"credentialsNonExpired": true,
	"enabled": true
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Funnee

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值