SpringSecurity基本原理
SpringSecurity本质上是一个过滤器链
代码底层流程:重点看三个过滤器
- FilterSecurityInterceptor——是一个方法级的权限过滤器链的最底部
//过滤方法
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
this.invoke(new FilterInvocation(request, response, chain));
}
//执行方法
public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
if (this.isApplied(filterInvocation) && this.observeOncePerRequest) {
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
} else {
if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
filterInvocation.getRequest().setAttribute("__spring_security_filterSecurityInterceptor_filterApplied", Boolean.TRUE);
}
InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
try {
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
} finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, (Object)null);
}
}
- ExceptionTranslationFilter——是一个异常过滤器,用来处理在认证授权过程中抛出的异常
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
chain.doFilter(request, response);
} catch (IOException var7) {
throw var7;
} catch (Exception var8) {
Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(var8);
RuntimeException securityException = (AuthenticationException)this.throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);
if (securityException == null) {
securityException = (AccessDeniedException)this.throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
}
if (securityException == null) {
this.rethrow(var8);
}
if (response.isCommitted()) {
throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", var8);
}
this.handleSpringSecurityException(request, response, chain, (RuntimeException)securityException);
}
}
- UsernamePasswordAuthenticationFilter:对/login的POST请求做拦截,校验表单中的用户名和密码。
// 用于接受传递过来的用户名和密码
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
String username = this.obtainUsername(request);
username = username != null ? username : "";
username = username.trim();
String password = this.obtainPassword(request);
password = password != null ? password : "";
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
过滤器加载过程
- 使用SpringSecurity配置过滤器
*DelegatingFilterProxy
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
Filter delegateToUse = this.delegate;
if (delegateToUse == null) {
synchronized(this.delegateMonitor) {
delegateToUse = this.delegate;
if (delegateToUse == null) {
WebApplicationContext wac = this.findWebApplicationContext();
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener or DispatcherServlet registered?");
}
delegateToUse = this.initDelegate(wac);
}
this.delegate = delegateToUse;
}
}
this.invokeDelegate(delegateToUse, request, response, filterChain);
}
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
String targetBeanName = this.getTargetBeanName();
Assert.state(targetBeanName != null, "No target bean name set");
Filter delegate = (Filter)wac.getBean(targetBeanName, Filter.class);
if (this.isTargetFilterLifecycle()) {
delegate.init(this.getFilterConfig());
}
return delegate;
}
//在FilterChainProxy过滤器中的doFilter方法
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
if (!clearContext) {
this.doFilterInternal(request, response, chain);
} else {
try {
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
this.doFilterInternal(request, response, chain);
} catch (RequestRejectedException var9) {
this.requestRejectedHandler.handle((HttpServletRequest)request, (HttpServletResponse)response, var9);
} finally {
SecurityContextHolder.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
}
}
//在doFilter中执行doFilterInternal方法
private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest)request);
HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse)response);
List<Filter> filters = this.getFilters((HttpServletRequest)firewallRequest);
if (filters != null && filters.size() != 0) {
if (logger.isDebugEnabled()) {
logger.debug(LogMessage.of(() -> {
return "Securing " + requestLine(firewallRequest);
}));
}
FilterChainProxy.VirtualFilterChain virtualFilterChain = new FilterChainProxy.VirtualFilterChain(firewallRequest, chain, filters);
virtualFilterChain.doFilter(firewallRequest, firewallResponse);
} else {
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.of(() -> {
return "No security for " + requestLine(firewallRequest);
}));
}
firewallRequest.reset();
chain.doFilter(firewallRequest, firewallResponse);
}
}
//getFilters中采用迭代器将过滤器加载到List集合中
private List<Filter> getFilters(HttpServletRequest request) {
int count = 0;
Iterator var3 = this.filterChains.iterator();
SecurityFilterChain chain;
do {
if (!var3.hasNext()) {
return null;
}
chain = (SecurityFilterChain)var3.next();
if (logger.isTraceEnabled()) {
++count;
logger.trace(LogMessage.format("Trying to match request against %s (%d/%d)", chain, count, this.filterChains.size()));
}
} while(!chain.matches(request));
return chain.getFilters();
}
两个重要的接口
UserDetailService接口()
当什么也没有配置的时候,账号和密码是由SpringSecurity定义生成的。而在实际开发过程中账号和密码都是从数据库中查询出来的。所以我们要通过自定义逻辑控制认证逻辑。如果需要自定义逻辑时,只需要实现UserDetailService接口即可。接口定义如下:
/**
实现UserDetailService接口,在接口中有loadUserByUsername方法并传入用户名
@return UserDetails
这个是系统默认的用户“主体”
*/
public interface UserDetailsService{
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
//对象中含有以下方法
//表示获取登录用户所有权限
Collection<? extends GrantedAuthority> getAuthorities();
//表示获取密码
String getPassword();
//表示用户名
String getUsername();
//表示判断用户是否过期
boolean isAccountNonExpired();
//表示判断账户是否被锁定
boolean isAccountNonLocked();
//表示当前用户是否可用
boolean isEnabled();
//最终返回一个User对象,此对象为SpringSecurity中的对象,需要new之后在返回。
PasswordEncoder接口
//表示把参数按照特定的解析规则进行解析
String encode(CharSequence rawPassword);
//表示验证从存储中获取的编码密码与编码后提交的原始密码是否匹配。如果匹配,则返回true,如果不匹配,则返回false,第一个参数表示需要被解析的密码,第二个参数表示存储的密码。
boolean matches(CharSequence rawPassword,String encodedPassword);
//表示如果解析的密码能够再次进行解析且达到更安全的结果则返回true,否则返回false。默认返回false。
default boolean upgradeEncoding(String encodedPassword){
return false;
}
//BCryptPasswordEncoder是SpringSecurity官方推荐的密码解析器,平时多使用这个解析器。
//BCryptPasswordEncoder是对bcrpty强散列方法的具体实现。是基于Hash算法实现的单向加密,可以通过strength控制加密强度,默认10
//使用演示
public void test(){
//创建密码解析器
BCryptPasswordEncoder bcryptPasswordEncoder = new BCryptPasswordEncoder();
//对密码进行加密
String Cesar = bcryptPasswordEncoder.encode("Cesar");
//打印数据
System.out.println("加密后的数据\t"+Cesar);
//判断原字符加密后和加密之前是否匹配
boolean result = bcryptPasswordEncoder.matches()
}
阅读更多
三连博主,每天分享一个编程小技巧!