一些基础的知识能够参考如下链接:https://www.cnkirito.moe/spring-security-1/ 讲真,这老哥写的很好,不过还有不少估计没写完,遗憾spring
本文章主要是一个大概的总结。安全
首先SpringSecurity是一个安全框架,它所作的事儿简单归纳就是使用Filter对整个项目进行一个保护,框架
保护的内容就包括 认证 和 受权验证 若是单针对Filter的话 他包括 登录认证,和对URL的权限验证, 登录认证是在SpringSecurity的过滤器链中的AbstractAuthenticationProcessingFilter的子类来作的 具体步骤ide
请求进来->若干前置过滤器->AbstractAuthenticationProcessingFilter.doFilter()-> AbstractAuthenticationProcessingFilter.attemptAuthentication()->若是获取到了Authentication则继续往下执行,不然重定向到登录页面.... 从attemptAuthentication方法中的认证操做是调用了AuthenticationManager的authenticate方法来进行登录认证,而后会把用户提交的用户信息封装成AuthenticationToken的实例丢进去,在其中进行验证,验证成功则返回填充了相关信息的Token对象,不然是null,则说明认证失败,会重定向到咱们设置好的loginUrl。ui
这个认证过程当中还涉及到 AuthenticationManager 的认证 是 AuthenticationManager的一个实现类ProviderManager,而ProviderManager会有一个List专门存放Provider,这些Provider都要实现AuthenticationProvider接口,只要有一个Provider支持放进来的Authentication则会由这个Provider进行处理,且这个方法能够抛出异常,抛出也说明登录失败。this
因此若是要自定义本身的一套登录认证的话,url
咱们涉及到三个地方 Filter - Provider - Token (例子采用上面连接的老哥的例子)code
咱们须要自定义本身的过滤器 经过 继承 AbstractAuthenticationProcessingFilter 类 实现 attemptAuthentication方法orm
public class IpAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter { public IpAuthenticationProcessingFilter(String uri) { super(new AntPathRequestMatcher(uri)); } @Override public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException { //获取host信息 String host = httpServletRequest.getRemoteHost(); //交给内部的AuthenticationManager去认证,实现解耦 return getAuthenticationManager().authenticate(new IpAuthenticationToken(host)); } }
咱们须要自定义本身的认证提供器 经过继承 AuthenticationProvider 类对象
public class IpAuthenticationProvider implements AuthenticationProvider { final static Map<String, SimpleGrantedAuthority> ipAuthorityMap = new ConcurrentHashMap<>(); //维护一个ip白名单列表,每一个ip对应必定的权限 static { ipAuthorityMap.put("127.0.0.1", new SimpleGrantedAuthority("ADMIN")); ipAuthorityMap.put("10.0.0.8", new SimpleGrantedAuthority("FRIEND")); } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { if (authentication instanceof IpAuthenticationToken){ String ip = ((IpAuthenticationToken)authentication).getIp(); if (ipAuthorityMap.containsKey(ip)){ HashSet<SimpleGrantedAuthority> grants = new HashSet(); grants.add(ipAuthorityMap.get(ip)); return new IpAuthenticationToken(ip,grants); } } return null; } @Override public boolean supports(Class<?> aClass) { return IpAuthenticationToken.class.isAssignableFrom(aClass); } }
咱们须要自定义本身的 认证对象 通常就是咱们的领域对象 可是这个对象必须实现 Authentication 接口 咱们能够直接继AbstractAuthenticationToken类便可
public class IpAuthenticationToken extends AbstractAuthenticationToken { public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } private String ip; public IpAuthenticationToken(String ip) { super(null); this.ip = ip; super.setAuthenticated(false); } public IpAuthenticationToken(String ip, Collection<? extends GrantedAuthority> authorities) { super(authorities); this.ip = ip; super.setAuthenticated(true); } @Override public Object getCredentials() { return null; } @Override public Object getPrincipal() { return null; } }
而后咱们须要将provider添加到ProviderManager的List中去
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(new IpAuthenticationProvider()); auth.userDetailsService(userDetailsService).passwordEncoder(NoOpPasswordEncoder.getInstance()); }
将咱们自定义的Filter注册到过滤器链中去
@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/iplogin","/login").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/iplogin"); //这个操做就是把咱们自定义的过滤器插入到UsernamePasswordAuthenticationFilter的前面,表示优先使用咱们的验证,若是验证成功则会将Authentication对象放入 //SecurityContext中,后面的过滤器在进行验证时会检测SecurityContext中是否有相应的对象,没有则会 http.addFilterBefore(ipAuthenticationProcessingFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class); }
搞定 这个时候咱们的登录验证方式已经改为了iplogin(虽然原始的login还在,可是因为咱们设置了loginPage的url为/iplogin因此那个页面在登录失败的时候永远也访问不到,除非你直接访问/login....)