一些基础的知识可以参考以下连接:https://www.cnkirito.moe/spring-security-1/ 讲真,这老哥写的很好,不过还有很多估计没写完,遗憾
本文章主要是一个大概的总结。
首先SpringSecurity是一个安全框架,它所做的事儿简单概括就是使用Filter对整个项目进行一个保护,
保护的内容就包括 认证 和 授权验证 如果单针对Filter的话 他包括 登陆认证,和对URL的权限验证, 登陆认证是在SpringSecurity的过滤器链中的AbstractAuthenticationProcessingFilter的子类来做的 具体步骤
请求进来->若干前置过滤器->AbstractAuthenticationProcessingFilter.doFilter()-> AbstractAuthenticationProcessingFilter.attemptAuthentication()->如果获取到了Authentication则继续往下执行,否则重定向到登陆页面.... 从attemptAuthentication方法中的认证操作是调用了AuthenticationManager的authenticate方法来进行登陆认证,然后会把用户提交的用户信息封装成AuthenticationToken的实例丢进去,在其中进行验证,验证成功则返回填充了相关信息的Token对象,否则是null,则说明认证失败,会重定向到我们设置好的loginUrl。
这个认证过程中还涉及到 AuthenticationManager 的认证 是 AuthenticationManager的一个实现类ProviderManager,而ProviderManager会有一个List专门存放Provider,这些Provider都要实现AuthenticationProvider接口,只要有一个Provider支持放进来的Authentication则会由这个Provider进行处理,且这个方法可以抛出异常,抛出也说明登陆失败。
所以如果要自定义自己的一套登陆认证的话,
我们涉及到三个地方 Filter - Provider - Token (例子采用上面链接的老哥的例子)
我们需要自定义自己的过滤器 通过 继承 AbstractAuthenticationProcessingFilter 类 实现 attemptAuthentication方法
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....)
来源:oschina
链接:https://my.oschina.net/u/3387406/blog/2999395