编辑
2019-04-24
编程
00

目录

1. spring security 的基本原理
1.1 添加 spring security 依赖后的情况
1.2 简单配置示例
1.3 spring security 的基本原理
1.4 spring security 登陆源码解读
1.4.1 UsernamePasswordAuthenticationFilter#attemptAuthentication
1.4.2 AuthenticationManager
1.4.3 AuthenticationProvider
1.4.4 AbstractUserDetailsAuthenticationProvider
1.4.5 AbstractAuthenticationProcessingFilte
1.4.5 总结
2.一些用法
2.1 自定义登录页面
2.2 自定义登录成功处理和登录失败处理
2.3 自定义登录验证

1. spring security 的基本原理

1.1 添加 spring security 依赖后的情况

对所有连接进行拦截,并跳转到内置登录页面,登录名默认为 user, 登录密码,启动时在控制台打印出来

1.2 简单配置示例

@Configuration public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { // http.httpBasic() http.formLogin() .and() // request 验证 .authorizeRequests() // 任何请求 .anyRequest() .authenticated(); } }

1.3 spring security 的基本原理

spring security 其实就是客户端发起请求是和 REST API 之间(请求到控制器,控制器到响应)的过滤器链。

这个过滤器链上有两个比较常用的 Filter, UsernamePasswordAuthenticationFilter (表单过滤器), BasicAuthenticationFilter (旧版本默认配置,弹窗输入账号密码的警告框),它们校验请求是否包含所需要的对应参数信息

过滤器链的最后一端(最靠近REST API 的一端) 是 FilterSecurityInterceptor,它决定你的请求能否访问你的服务,它的决定取决于配置代码(例如简单示例中的 BrowserSecurityConfig )。检验通过准许访问,校验不通过根据代码抛出异常。

在 FilterSecurityInterceptor 前还有一个 ExceptionTranslationFilter ,用于捕获 FilterSecurityInterceptor 抛出的异常。并根据前面的校验类型(UsernamePasswordAuthenticationFilter 或者 BasicAuthenticationFilter) 重定向到对应的地址。

1.4 spring security 登陆源码解读

常用UsernamePassword登陆方式

1.4.1 UsernamePasswordAuthenticationFilter#attemptAuthentication

(org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#attemptAuthentication) 用户名密码认证过滤器

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" property setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); }

获取请求中的 username 和 password ,初始化用户名密码身份验证令牌(UsernamePasswordAuthenticationToken),设置身份验证状态为 false

1.4.2 AuthenticationManager

org.springframework.security.authentication.AuthenticationManager

AuthenticationManager 管理 AuthenticationProvider org.springframework.security.authentication.ProviderManager#authenticate ProviderManager 实现 AuthenticationManager

public Authentication authenticate(Authentication authentication) throws AuthenticationException { Class<? extends Authentication> toTest = authentication.getClass(); AuthenticationException lastException = null; Authentication result = null; boolean debug = logger.isDebugEnabled(); for (AuthenticationProvider provider : getProviders()) { if (!provider.supports(toTest)) { continue; } ...

通过 getProviders() 获取所有的 AuthenticationProvider 循环进行处理,因为 security 支持多种登陆方式,每个方式对应一个 AuthenticationProvider

1.判断获取的 provider 是否,支持当前插入的 authentication 当前传入为 UsernamePasswordAuthenticationToken,当为 DaoAuthenticationProvider 时验证通过

if (!provider.supports(toTest)) { continue; }

存疑:

当前 getProviders() 有且只有 AnonymousAuthenticationProvider 校验不通过,调用父 AuthenticationManager (仍为 ProviderManager),getProviders() 有且只有 DaoAuthenticationProvider 涉及容器初始化。

2.调用 provider 的 authenticate() 方法进行处理

try { result = provider.authenticate(authentication); if (result != null) { copyDetails(authentication, result); break; } }

此处调用了 DaoAuthenticationProvider#authenticate 没有实现调用抽象类AbstractUserDetailsAuthenticationProvider#authenticate

1.4.3 AuthenticationProvider

org.springframework.security.authentication.AuthenticationProvider

#authenticate

public Authentication authenticate(Authentication authentication) throws AuthenticationException { Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, messages.getMessage( "AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported")); // Determine username 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 = 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; } } ......

1.通过 retrieveUser 获取用户信息
2.使用preAuthenticationChecks.check(user); 做预检查
3.使用 additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication); 做附加检查(使用加密器检查密码是否匹配)
4.使用 postAuthenticationChecks.check(user);后检查,校验用户帐户凭证是否已过期
5.验证完成,创建一个 SuccessAuthentication

#retrieveUser

protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { prepareTimingAttackProtection(); try { UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username); if (loadedUser == null) { throw new InternalAuthenticationServiceException( "UserDetailsService returned null, which is an interface contract violation"); } return loadedUser; }

通过 this.getUserDetailsService().loadUserByUsername(username) 调用我们实现的UserDetailsService#loadUserByUsername 方法,使用 username 获取用户并返回

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

AbstractUserDetailsAuthenticationProvider.DefaultPreAuthenticationChecks org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.DefaultPreAuthenticationChecks#check

public void check(UserDetails user) { if (!user.isAccountNonLocked()) { logger.debug("User account is locked"); throw new LockedException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.locked", "User account is locked")); } if (!user.isEnabled()) { logger.debug("User account is disabled"); throw new DisabledException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled")); } if (!user.isAccountNonExpired()) { logger.debug("User account is expired"); throw new AccountExpiredException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.expired", "User account has expired")); } }

1.user.isAccountNonLocked() 判断用户是否被锁定
2.user.isEnabled() 判断用户是否无效
3.user.isAccountNonExpired() 判断用户是否过期

org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.DefaultPostAuthenticationChecks#check

public void check(UserDetails user) { if (!user.isCredentialsNonExpired()) { logger.debug("User account credentials have expired"); throw new CredentialsExpiredException(messages.getMessage( "AbstractUserDetailsAuthenticationProvider.credentialsExpired", "User credentials have expired")); } }

1.用户帐户凭证是否已过期

1.4.4 AbstractUserDetailsAuthenticationProvider

org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider#createSuccessAuthentication

与1.4.1中一样,创建用户名密码身份验证令牌(UsernamePasswordAuthenticationToken),设置身份验证状态为 true

1.4.5 AbstractAuthenticationProcessingFilte

org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter#successfulAuthentication (org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter#doFilter 中调用)

protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { if (logger.isDebugEnabled()) { logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult); } SecurityContextHolder.getContext().setAuthentication(authResult); rememberMeServices.loginSuccess(request, response, authResult); // Fire event if (this.eventPublisher != null) { eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent( authResult, this.getClass())); } successHandler.onAuthenticationSuccess(request, response, authResult); }

1.多请求共享

SecurityContextHolder.getContext().setAuthentication(authResult);
  • 把用户信息放到 SecurityContext, 传递到 SecurityContextHolder 中。SecurityContextPersistenceFilter 通过 SecurityContextHolder 在多个请求中共享用户信息。
  • SecurityContextHolder是对 TreadLocal 的封装
  • SecurityContextPersistenceFilter位于security过滤器链的最前端:当请求进来时,检查 session 是否存在对应的 SecurityContext ,如果存在,把 SecurityContext 放到当前线程;当请求出去时,检查当前线程是否存在 SecurityContext,如果存在,把 SecurityContext 放到 session 中
  • MVC 可以使用 @AuthenticationPrincipal 参数获取对应的 UserDetails 信息

2.successHandler.onAuthenticationSuccess(request, response, authResult); 调用自己的登陆成功处理器

1.4.5 总结

AbstractAuthenticationProcessingFilter#doFilter -> UsernamePasswordAuthenticationFilter(Authentication 未认证) -> AuthenticationManager -> AuthenticationProvider -> UserDetailsService -> UserDetails -> Authentication(已认证)

2.一些用法

2.1 自定义登录页面

http.formLogin().loginPage("/xxx")

2.2 自定义登录成功处理和登录失败处理

实现 AuthenticationSuccessHandler 处理登录成功

@Component() public class BrowserAuthenticationSuccessHandler implements AuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { System.out.println("================test success"); } }

实现 AuthenticationFailureHandler 处理登录失败

@Component public class BrowserAuthenticationFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { System.err.println("==================test fail"); } }

配置文件添加相关的配置

@Autowired private BrowserAuthenticationSuccessHandler browserAuthenticationSuccessHandler; @Autowired private BrowserAuthenticationFailureHandler browserAuthenticationFailureHandler; @Override protected void configure(HttpSecurity http) throws Exception { // http.httpBasic() http.formLogin() .successHandler(browserAuthenticationSuccessHandler) .failureHandler(browserAuthenticationFailureHandler) .and() // request 验证 .authorizeRequests() // 任何请求 .anyRequest() .authenticated() ; //关闭默认的csrf认证 http.csrf().disable(); }

2.3 自定义登录验证

实现 UserDetailsService 接口即可(保证有且只有一个 UserDetailsService 接口实现类)

@Component public class UserDetailServiceImpl implements UserDetailsService { private Logger log = LoggerFactory.getLogger(UserDetailServiceImpl.class); @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { log.info("username : " + username); return new User(username, "123456", AuthorityUtils.commaSeparatedStringToAuthorityList("*")); } }
@Configuration public class BeanConfig { @Bean public PasswordEncoder passwordEncoder(){ return new MyPasswordEncoder(); } }

本文作者:Yui_HTT

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!