SpringBoot项目实战-security配置过滤器

205 阅读2分钟

平时,我们使用security框架时,都会默认使用框架的登录方式,如果想使用自定义的登录方式,就要写一个securityConfig文件,重写security里的某些方法,以达到使用自定义登录方式,SecurityConfig:

/**
 * Security配置类
 * 登录授权过滤器
 * 重写UserDetailsService、configure的目的是为了当用户登录时是走我们自定义写的获取用户信息,以及走自定义的密码匹配规则
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {//保留疑问,问什么要继承WebSecurityConfigurerAdapter

    @Autowired
    private IAdminService adminService;
    @Autowired
    private RestfulAccessDeniedHandler restfulAccessDeniedHandler;
    @Autowired
    private RestAuthorizationEntryPoint restAuthorizationEntryPoint;

    /**
     * 第二步,实现security完整的配置
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //本项目使用了Jwt不需要csrf
        http.csrf()
                .disable()
                //基于token不需要session
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers("/login","logout")
                .permitAll()
                 //除上面请求,所有请求都要认证
                .anyRequest()
                .authenticated()
                .and()
                .headers()
                .cacheControl();
        //添加jwt登录授权拦截器
        http.addFilterBefore(jwtAuthencationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
        //添加自定义未授权和未登录结果返回
        http.exceptionHandling()
                .accessDeniedHandler(restfulAccessDeniedHandler)
                .authenticationEntryPoint(restAuthorizationEntryPoint);


    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception{
        auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
    }

    /**
     * 重写UserDetailsService,因为其存在于WebSecurityConfigurerAdapter
     */
    @Override
    @Bean
    public UserDetailsService userDetailsService(){
        return username->{
            Admin admin=adminService.getAdminByUsername(username);
            if(admin!=null){
                return admin;
            }
            return null;
        };
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean
    public JwtAuthencationTokenFilter jwtAuthencationTokenFilter(){
        return new JwtAuthencationTokenFilter();
    }
}

在这个文件有使用到自定义未登录时返回的状态,写在这两个包里面:

@Autowired
private RestfulAccessDeniedHandler restfulAccessDeniedHandler;
@Autowired
private RestAuthorizationEntryPoint restAuthorizationEntryPoint;

访问接口没有权限时,自定义返回结果,包RestfulAccessDeniedHandler的代码如下:

/**
 * 当访问接口没有权限时,自定义返回结果
 */
@Component
public class RestfulAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        PrintWriter out=response.getWriter();
        RespBean bean=RespBean.error("权限不足,请联系管理员!");
        bean.setCode(403L);
        out.write(new ObjectMapper().writeValueAsString(bean));
        out.flush();
        out.close();
    }
}

当用户token失效时访问接口,自定义返回结果,包RestAuthorizationEntryPoint的代码如下:

/**
 * 当登录或者token失效时访问接口时,自定义的返回结果
 */
@Component
public class RestAuthorizationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        response.setCharacterEncoding("utf-8");
        response.setContentType("application/json");
        PrintWriter out=response.getWriter();//获取数据结果
        RespBean bean=RespBean.error("尚未登录,请登录!");
        bean.setCode(401L);
        out.write(new ObjectMapper().writeValueAsString(bean));
        out.flush();
        out.close();

    }
}

上面的包里面还用到了JwtAuthencationTokenFilter,这个包里只有一个方法,就是重写OncePerRequestFilter里的doFilterInternal,主要为了拦截没有token或token过期的,如果有token,重置token:

/**
 * jwt登录授权过滤器
 */
public class JwtAuthencationTokenFilter extends OncePerRequestFilter {
    @Value("${jwt.tokenHeader")
    private String tokenHeader;
    @Value("${jwt.tokenHead}")
    private String tokenHead;
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Autowired
    private UserDetailsService userDetailsService;

    //这个方法主要是做前置拦截
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //获取请求里面的tokenHeader
        String authHeader=request.getHeader(tokenHeader);
        //存在token
        if(authHeader!=null&&authHeader.startsWith(tokenHead)){
            //获取到前端传过来的token
            String authToken=authHeader.substring(tokenHead.length());
           String username= jwtTokenUtil.getUserNameFormToken(authToken);
           //token存在用户名但未登录
           if(username!=null&& SecurityContextHolder.getContext().getAuthentication()==null){
               //登录
               UserDetails userDetails=userDetailsService.loadUserByUsername(username);
               //验证token是否有效,重新设置用户对象
               if(jwtTokenUtil.validateToken(tokenHead,userDetails)){
                   UsernamePasswordAuthenticationToken authenticationToken=new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());
                   //重新设置到用户对象
                   authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                   SecurityContextHolder.getContext().setAuthentication(authenticationToken);
               }
           }
        }
        filterChain.doFilter(request,response);
    }
}

以上就可以自定义对未登录或token失效的用户做拦截了!!!