自定义secuirty拦截器
背景
很多时候security默认提供的拦截器往往不够用于我们的日常开发,所以我们经常需要自己重写某些拦截器,达到实现开发的需求
本文,以重写登录拦截器为例
相关源码
/* 在指定筛选器类的位置添加筛选器。例如,如果希望筛选器 CustomFilter 注册到与 相同的 UsernamePasswordAuthenticationFilter位置,则可以调用: addFilterAt(new CustomFilter(), UsernamePasswordAuthenticationFilter.class) 在同一位置注册多个筛选器意味着它们的排序不是确定的。更具体地说,在同一位置注册多个筛选器不会覆盖现有筛选器。相反,不要注册您不想使用的筛选器。 形参: 过滤器 – 要注册的过滤器 atFilter – 已在 Spring Security 注册(即已知)的另一个 Filter 位置。 返回值: 用于 HttpSecurity 进一步的定制 */ public HttpSecurity addFilterAt(Filter filter, Class<? extends Filter> atFilter) { return addFilterAtOffsetOf(filter, 0, atFilter); }
/* 处理身份验证表单提交。在 Spring Security 3.0 之前调用 AuthenticationProcessingFilter 。 登录表单必须向此筛选器提供两个参数:用户名和密码。要使用的默认参数名称包含在静态字段 SPRING_SECURITY_FORM_USERNAME_KEY 和 SPRING_SECURITY_FORM_PASSWORD_KEY中。还可以通过设置 usernameParameter and passwordParameter 属性来更改参数名称。 默认情况下,此过滤器响应 URL /login。 自: 3.0 作者: 本·亚历克斯、科林·桑帕莱亚努、卢克·泰勒 */ public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter { public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username"; public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password"; private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login", "POST"); private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY; private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY; private boolean postOnly = true; public UsernamePasswordAuthenticationFilter() { super(DEFAULT_ANT_PATH_REQUEST_MATCHER); } public UsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager) { super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager); } }
步骤
一、创建自定义的拦截器类,继承XXX拦截器,实现重写
例如:我们重写了登录的拦截器(表单提交),按照我们需要的方式【需要json格式】,来进行修改自定义的拦截器类
需要做的事情
- 判断是否为post的请求
- 判断是否为json格式的数据
- 将json格式的数据中 获取我们需要的username , password进行认证
/** * 自定义前后端分离认证 Filter */ public class LoginFilter extends UsernamePasswordAuthenticationFilter { @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { /** * 需要做的三件事 */ //1. 判断是否为post的请求 if (!request.getMethod().equals("POST")) { throw new AuthenticationServiceException("请求方法不支持" + request.getMethod()); } //2. 判断是否为json格式的数据 if (request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE)) { //2.1 提取用户名 ,密码 try { /** * 用流的形式去接收 * 因为源码中有 private String usernameParameter = "username"; private String passwordParameter = "password"; 所以我们可以直接继承下来 使用 */ Map<String, String> userInfo = new ObjectMapper().readValue(request.getInputStream(), Map.class); String username = userInfo.get(getUsernameParameter()); String password = userInfo.get(getPasswordParameter()); //已经获取到了username... //2.2 按照源码的实例, 我们就需要将获取的内容封装为一个token // UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); setDetails(request, authRequest); //最后调用本类中的authenticate return this.getAuthenticationManager().authenticate(authRequest); } catch (IOException e) { e.printStackTrace(); } } //3. 将json格式的数据中 获取我们需要的username , password进行认证 return super.attemptAuthentication(request, response); } }
二、在自定义的安全配置类中进行配置
/** * 自定义Filter交给容器进行管理 * 基本是按照源码中的内容去进行配置 */ @Bean public LoginFilter loginFilter() throws Exception { LoginFilter loFilter = new LoginFilter(); loFilter.setFilterProcessesUrl("/doLogin"); //其中的value属性与 前端form表单中输入用户名的name属性相同 loFilter.setUsernameParameter("username"); loFilter.setPasswordParameter("password"); //注入自己的AuthenticationManager loFilter.setAuthenticationManager(authenticationManagerBean()); //认证成功处理 。认证失败处理 loFilter.setAuthenticationSuccessHandler((request, response, authentication) -> { Map<String, Object> result = new HashMap<>(); result.put("msg", "登录成功!"); result.put("status", "200"); result.put("用户信息", (User) authentication.getPrincipal()); response.setContentType("application/json;charset=UTF-8"); String s = new ObjectMapper().writeValueAsString(result); response.getWriter().println(s); }); loFilter.setAuthenticationFailureHandler((request, response, exception) -> { Map<String, Object> result = new HashMap<>(); result.put("msg", "登录失败!!"); result.put("status", "400"); result.put("错误信息", exception.getMessage()); response.setContentType("application/json;charset=UTF-8"); String s = new ObjectMapper().writeValueAsString(result); response.getWriter().println(s); }); return loFilter; }
配置自己的身份认证管理员(AuthenticationManager)
这样做的目的是为了让我们登录时进行验证的数据是从数据库或者缓存中提取的,而不是仅仅放上默认给出的数据。所以这一步是必须要进行操作的
//注入自己的AuthenticationManager loFilter.setAuthenticationManager(authenticationManagerBean());
重写UserDetails
@Component public class MyUserDetailsService implements UserDetailsService { private final UserDao userDao; @Autowired public MyUserDetailsService(UserDao userDao) { this.userDao = userDao; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userDao.loadUserByUsername(username); if (ObjectUtils.isEmpty(user)) throw new UsernameNotFoundException("用户名不正确!"); //存在的话 , 赋予权限信息 List<Role> roles = userDao.getRolesByUid(user.getId()); user.setRoles(roles); return user; } }
实现注入
将自己重写的DetailsService进行依赖注入,然后交给AuthenticationManagerBuilder
/** * 创建自己userDetailsService,然后交给AuthenticationManagerBuilder来创建我们自己的AuthenticationManager */ private final MyUserDetailsService myUserDetailsService; @Autowired public testConfig(MyUserDetailsService myUserDetailsService) { this.myUserDetailsService = myUserDetailsService; }
创建自定义的AuthenticationManager,管理自己的DetailsService
* 创建自己的AuthenticationManager */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(myUserDetailsService); }
将自定义的认证放到容器中,覆盖默认的
/** * 将自定义的认证暴露在工厂中 (加入到容器中去管理) */ @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); }
三、在安全配置类中替换默认的拦截器
@Override protected void configure(HttpSecurity http) throws Exception { //前后端分离,数据从json中提取 http.authorizeRequests() .anyRequest().authenticated() .and() .formLogin().loginPage("/loginPages") .and() .csrf().disable() ; /** * 将我们自定义的filter过滤器替换其中的某个过滤器(filter) *loginFilter() ---change---> UsernamePasswordAuthenticationFilter */ http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class); }
将我们自定义的filter过滤器替换其中的某个过滤器(filter)
loginFilter() —change—> UsernamePasswordAuthenticationFilter