对所有连接进行拦截,并跳转到内置登录页面,登录名默认为 user, 登录密码,启动时在控制台打印出来
@Configuration public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { // http.httpBasic() http.formLogin() .and() // request 验证 .authorizeRequests() // 任何请求 .anyRequest() .authenticated(); } }
spring security 其实就是客户端发起请求是和 REST API 之间(请求到控制器,控制器到响应)的过滤器链。
这个过滤器链上有两个比较常用的 Filter, UsernamePasswordAuthenticationFilter (表单过滤器), BasicAuthenticationFilter (旧版本默认配置,弹窗输入账号密码的警告框),它们校验请求是否包含所需要的对应参数信息
过滤器链的最后一端(最靠近REST API 的一端) 是 FilterSecurityInterceptor,它决定你的请求能否访问你的服务,它的决定取决于配置代码(例如简单示例中的 BrowserSecurityConfig )。检验通过准许访问,校验不通过根据代码抛出异常。
在 FilterSecurityInterceptor 前还有一个 ExceptionTranslationFilter ,用于捕获 FilterSecurityInterceptor 抛出的异常。并根据前面的校验类型(UsernamePasswordAuthenticationFilter 或者 BasicAuthenticationFilter) 重定向到对应的地址。
常用UsernamePassword登陆方式
(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
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
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.用户帐户凭证是否已过期
org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider#createSuccessAuthentication
与1.4.1中一样,创建用户名密码身份验证令牌(UsernamePasswordAuthenticationToken),设置身份验证状态为 true
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);
2.successHandler.onAuthenticationSuccess(request, response, authResult); 调用自己的登陆成功处理器
AbstractAuthenticationProcessingFilter#doFilter -> UsernamePasswordAuthenticationFilter(Authentication 未认证) -> AuthenticationManager -> AuthenticationProvider -> UserDetailsService -> UserDetails -> Authentication(已认证)
http.formLogin().loginPage("/xxx")
实现 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(); }
实现 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 许可协议。转载请注明出处!