表单登录流程
- 访问资源接口,该接口需要登录才能访问
- 请求会走Security中的过滤器链,在FilterSecurityInterceptor被拦截,抛出AccessDeniedException
- 该异常会在ExceptionTranslationFilter过滤器被捕获,调研LoginURLAuthenticationEntryPoint的commence方法给客户端返回302,重定向到登录页面
- 客户端发生/login请求
- /login请求被DefaultLoginPageGeneratingFilter拦截,返回登录页面
原理分析
- 自动创建SpringSecurityFilterChain的过滤器,注入到Spring容器中,代理了Spring Security中的过滤器链
- 创建UserDetailsService实例,负责提供用户数据,默认是给予内存的
- 生成默认登录页面
- 开启CSRF防御
- 开启其他防御
生成用户
通过UserDetails接口定义用户对象
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();// 账户权限
String getPassword();//密码
String getUsername();//用户名
boolean isAccountNonExpired();//是否未过期
boolean isAccountNonLocked();//是否未锁定
boolean isCredentialsNonExpired();//凭证是否未过期
boolean isEnabled();//是否可用
}
负责提供用户数据源的接口是UserDetailsService#loadUserByUsername
public interface UserDetailsService {
UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}
实际开发过程中,一般需要自开发UserDetailsService,默认情况下使用的是InMemoryUserDetailsManager管理用户信息。
只需要在application.properties中配置相关信息即可:
spring.security.user.name=user
spring.security.user.password=user
spring.security.user.roles=user,admin
配置
在Security中,如果需要自定义配置,都是继承自WebSecurityConfigurerAdapter来实现的,链式配置。
如:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest()
.authenticated()
.and().formLogin().loginPage("/login.html").loginProcessingUrl("/doLogin")
.defaultSuccessUrl("/index")
.failureUrl("/fail.html")
.usernameParameter("name")
.passwordParameter("passwd")
.permitAll()
.and()
.csrf().disable();
}
}
defaultSuccessUrl通过重定向实现跳转,客户端跳转
successForwardUrl则是服务器端跳转实现。
Security专门提供了AuthenticationSuccessHandler接口来处理登录事件:
public interface AuthenticationSuccessHandler {
default void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
this.onAuthenticationSuccess(request, response, authentication);
chain.doFilter(request, response);
}
void onAuthenticationSuccess(HttpServletRequest var1, HttpServletResponse var2, Authentication var3) throws IOException, ServletException;
}
实现类主要是SimpleUrlAuthenticationSuccessHandler:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.security.web.authentication;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.security.core.Authentication;
public class SimpleUrlAuthenticationSuccessHandler extends AbstractAuthenticationTargetUrlRequestHandler implements AuthenticationSuccessHandler {
public SimpleUrlAuthenticationSuccessHandler() {
}
public SimpleUrlAuthenticationSuccessHandler(String defaultTargetUrl) {
this.setDefaultTargetUrl(defaultTargetUrl);
}
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
this.handle(request, response, authentication);
this.clearAuthenticationAttributes(request);
}
protected final void clearAuthenticationAttributes(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session != null) {
session.removeAttribute("SPRING_SECURITY_LAST_EXCEPTION");
}
}
}
当需要在前后端分离场景下认证,只需要返回JSON数据即可,那么需要自定义AuthenticationSuccessHandler实现类来返回结果,然后需要在SecurityConfig中加上
.successHandler(new MyAuthenticationSUccessHandler())
那么对应的失败的处理是AuthenticationFailureHandler接口处理,如SimpleUrlAuthenticationFailureHandler。
用户数据获取
从SecurityContextHolder中获取
SecurityContextHolder
包含
SecurityContext
包含
Authentication
包含
Principal、Credentials、Authorities
SecurityContextPersistenceFilter在每次请求到时将HttpSession中获取SecurityContext放入SecurityContextHolder中,在请求结束时再取出并清除信息。
从当前请求对象获取
可以直接将Authentication对象注入到Controller的请求参数中,在此处获取登录用户信息。
用户定义
自定义用户就是使用不同的UserDetailsService的实现类提供用户数据,同时将该自定义类提供给AuthenticationManagerBuilder,系统再将该自定义类提供给AuthenticationProvider使用。
@Autowired
private MyUserDetailsServie myUserDetailsServie;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsServie)
}