spring-security使用-更友好的方式扩展登录AuthenticationProvider(三) 说明 接口定义 使用AuthenticationProvider自定义登录逻辑 原理
在 我们使用的是重写了Spring-security的filter的方式来进行自定义,但是这样的弊端,就是侵入太大。直接把spring-security的filter给替换掉了,
通过AuthenticationProvider的方式是在spring-security的filter内部留的扩展点进行扩展自定义登录逻辑
接口定义
AuthenticationProvider
public interface AuthenticationProvider { /** * 验证用户身份 * @param var1 * @return * @throws AuthenticationException */ Authentication authenticate(Authentication var1) throws AuthenticationException; /** * supports 则用来判断当前的 AuthenticationProvider 是否支持对应的 Authentication。 * @param var1 * @return */ boolean supports(Class<?> var1); }
Authentication
封装用户身份信息
public interface Authentication extends Principal, Serializable { //用来获取用户的权限。 Collection<? extends GrantedAuthority> getAuthorities(); //方法用来获取用户凭证,一般来说就是密码。 Object getCredentials(); //方法用来获取用户携带的详细信息,可能是当前请求之类的东西。 Object getDetails(); //方法用来获取当前用户,可能是一个用户名,也可能是一个用户对象。 Object getPrincipal(); //当前用户是否认证成功。 boolean isAuthenticated(); void setAuthenticated(boolean var1) throws IllegalArgumentException; }
类图
UsernamePasswordAuthenticationFilter 用的就是UserNamePasswordAuthenticationToken
使用AuthenticationProvider自定义登录逻辑
1.增加自定义provider继承DaoAuthenticationProvider
public class CodeAuthenticationProvider extends DaoAuthenticationProvider { @Override protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) { HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String code = req.getParameter("code"); String verify_code = (String) req.getSession().getAttribute("verify_code"); if (code == null || verify_code == null || !code.equals(verify_code)) { throw new AuthenticationServiceException("验证码错误"); } super.additionalAuthenticationChecks(userDetails, authentication); } }
2.在public class SecurityConfig extends WebSecurityConfigurerAdapter 类增加以下
/** * 对密码进行加密的实例 * @return */ @Bean PasswordEncoder passwordEncoder() { /** * 不加密所以使用NoOpPasswordEncoder * 更多可以参考PasswordEncoder 的默认实现官方推荐使用: BCryptPasswordEncoder,BCryptPasswordEncoder */ return NoOpPasswordEncoder.getInstance(); } /** * 自定义provider * @return */ public CodeAuthenticationProvider codeAuthenticationProvider() { CodeAuthenticationProvider myAuthenticationProvider = new CodeAuthenticationProvider(); //设置passorderEncoder myAuthenticationProvider.setPasswordEncoder(passwordEncoder()); //设置UserDetailsService 可以参考第一篇登录使用例子自定义userDetailServices myAuthenticationProvider.setUserDetailsService(userService); return myAuthenticationProvider; } /** * 重写父类自定义AuthenticationManager 将provider注入进去 * 当然我们也可以考虑不重写 在父类的manager里面注入provider * @return * @throws Exception */ @Override protected AuthenticationManager authenticationManager() throws Exception { ProviderManager manager = new ProviderManager(Arrays.asList(codeAuthenticationProvider())); return manager; }
原理
spring-security使用-登录(一) 我们替换了默认的UsernamePasswordAuthenticationFilter
1.根据看这个类UsernamePasswordAuthenticationFilter我们可以看出他的父类实现了Servilet Fitler,所以spring-security是基于Filter的
org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter#doFilter 父类实现
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { //省略部分代码...... //调用子类的实现 也就是 UsernamePasswordAuthenticationFilter authResult = this.attemptAuthentication(request, response); if (authResult == null) { return; } } }
2.看子类实现
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#attemptAuthentication
public org.springframework.security.core.Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { //注意这里不支持post请求,如果我们要让支持post请求,就要重写filter吧这里去掉 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 封装 UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); this.setDetails(request, authRequest); //委托给AuthenticationManager 执行 我们上面重写用的是ProviderManager return this.getAuthenticationManager().authenticate(authRequest); } }
3.providerManger实现
org.springframework.security.authentication.ProviderManager#authenticate
public org.springframework.security.core.Authentication authenticate(org.springframework.security.core.Authentication authentication) throws AuthenticationException { //获得Authentication的类型 Class<? extends org.springframework.security.core.Authentication> toTest = authentication.getClass(); AuthenticationException lastException = null; AuthenticationException parentException = null; org.springframework.security.core.Authentication result = null; org.springframework.security.core.Authentication parentResult = null; boolean debug = logger.isDebugEnabled(); //获得所有的Providers 就是我们定义的CodeAuthenticationProvider Iterator var8 = this.getProviders().iterator(); while(var8.hasNext()) { //迭代器迭代获取 AuthenticationProvider provider = (AuthenticationProvider)var8.next(); //判断是否能处理 if (provider.supports(toTest)) { if (debug) { logger.debug("Authentication attempt using " + provider.getClass().getName()); } try { //调用provider的authenticate 执行身份认证 result = provider.authenticate(authentication); if (result != null) { this.copyDetails(authentication, result); break; } } catch (InternalAuthenticationServiceException | AccountStatusException var13) { this.prepareException(var13, authentication); throw var13; } catch (AuthenticationException var14) { lastException = var14; } } } //省略部分代码....... }