Spring boot JWT在shrio toekn过滤器读取系统配置内容在过滤器中注入为java.lang.NullPointerException

92 阅读2分钟

需求

Spring boot 集成 shiro 、JWT

解决在token 过滤器注入reids实例和其它spring 容器管理的实例为java.lang.NullPointerException 提示空指针bug

基本思想思路

我们可以在ShiroConfig 配置类使用@Bean把token过滤器注入到Spring 容器。

我们通常在配置shiroFilterFactoryBean 的时候使用的是

filtersMap.put("token", new CustomAuthFilter());

这样是的方式是不能使用到Spring 容器内的实例

我们需要修改shiroFilterFactoryBean 配置

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager,FilterRegistrationBean tokenRegistrationBean){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
        //自定义拦截器限制并发人数,参考博客:
        LinkedHashMap<String, Filter> filtersMap = new LinkedHashMap<>();
        filtersMap.put("tokenFilter",tokenRegistrationBean.getFilter());
        shiroFilterFactoryBean.setFilters(filtersMap);
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        //配置拦截策略
       
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

编码实现

自定义token过滤器

​
​
/**
 * @ClassName: CustomAccessControlFilter
 * TODO:类文件简单描述
 * @Author: 小霍
 * @UpdateUser: 小霍
 * @Version: 0.0.1
 */
@Slf4j
public class CustomAuthFilter extends AccessControlFilter {
    @Resource
    private JwtUtil jwtUtil;
    @Resource
    private ObjectMapper objectMapper;
    @Resource
    private RedisService redisService;
    /**
     * 如果返回的是true 就流转到下一个链式调用
     * 返回false 就会流转到 onAccessDenied方法
     * @Author:      小霍
     * @UpdateUser:
     * @Version:     0.0.1
     * @param servletRequest
    • * @param servletResponse
    • * @param o
     * @return       boolean
     * @throws
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
        return false;
    }
    /**
     * 如果返true 就会流转到下一个链式调用
     * false 就是不会流转到下一个链式调用
     * @Author:      小霍
     * @UpdateUser:
     * @Version:     0.0.1
     * @param servletRequest
    • * @param servletResponse
     * @return       boolean
     * @throws
     */
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) {
         HttpServletRequest request= (HttpServletRequest) servletRequest;
        String accessToken=request.getHeader(Constant.ACCESS_TOKEN);
        log.info("url:{}",request.getRequestURI());
        try {
        if(!StringUtils.hasLength(accessToken)){
            throw new BusinessException(ResponseCode.TOKEN_ERROR.getCode(),ResponseCode.TOKEN_ERROR.getMessage());
        }
        if(!jwtUtil.validateToken(accessToken)){
            throw new BusinessException(ResponseCode.TOKEN_EXP.getCode(),ResponseCode.TOKEN_EXP.getMessage());
        }
        Long userId=jwtUtil.getUserId(accessToken);
        if(redisService.validateUser(userId)){
            throw new BusinessException(ResponseCode.FORCE_QUIT_USER);
        }
        JwtAuthToken jwtAuthToken=new JwtAuthToken(accessToken,userId);
        getSubject(servletRequest,servletResponse).login(jwtAuthToken);
        }catch (BusinessException e){
            customResponse(servletResponse,e.getCode(),e.getMessage());
            return false;
        } catch (AuthenticationException e) {
            if(e.getCause() instanceof BusinessException){
                BusinessException exception= (BusinessException) e.getCause();
                customResponse(servletResponse,exception.getCode(),exception.getMessage());
            }else {
                customResponse(servletResponse, ResponseCode.TOKEN_ERROR.getCode(), ResponseCode.TOKEN_ERROR.getMessage());
            }
            return false;
        }catch (Exception e){
            customResponse(servletResponse, ResponseCode.SYSTEM_ERROR.getCode(), ResponseCode.SYSTEM_ERROR.getMessage());
            return false;
        }
        return true;
    }
    /**
     * 自定义响应前端
     * @Author:      小霍
     * @UpdateUser:
     * @Version:     0.0.1
     * @param response
    • * @param code
    • * @param msg
     * @return       void
     * @throws
     */
    private void customResponse(ServletResponse response,int code ,String msg){
        DataResult result= DataResult.fail(code,msg);
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding("UTF-8");
        OutputStream outputStream=null;
        try {
            outputStream= response.getOutputStream();
            outputStream.write(objectMapper.writeValueAsString(result).getBytes("UTF-8"));
            outputStream.flush();
        } catch (IOException e) {
            log.error("customResponse...error:{}",e);
        }finally {
            if(outputStream!=null){
                try {
                    outputStream.close();
                } catch (IOException e) {
                    log.error("IOException:{}",e);
                }
            }
        }
​
    }
}
​

自定义Ream

​
​
public class CustomRealm extends AuthorizingRealm {
    @Value("shiro.super.account")
    private List<String> superAccounts;
​
    @Override
    protected boolean isPermitted(Permission permission, AuthorizationInfo info) {
        /**
         * 系统内部超级管理不需要去校验权限
         */
        if(superAccounts.stream().anyMatch(account-> account.equalsIgnoreCase((String) SecurityUtils.getSubject().getPrincipal()))){
            return true;
        }
        return super.isPermitted(permission, info);
    }
​
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtAuthToken;
    }
​
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }
​
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(token.getPrincipal(),token.getCredentials(),CustomRealm.class.getName());
        return info;
    }
}
​

自定义认证器默认通过校验

​
​
public class CustomCredentialsMatcher extends HashedCredentialsMatcher {
    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        return true;
    }
}
​

禁用shiro session

​
​
public class CustomWebSubjectFactory extends DefaultWebSubjectFactory {
​
    @Override
    public Subject createSubject(SubjectContext context) {
        // 禁用session
        context.setSessionCreationEnabled(false);
        return super.createSubject(context);
    }
}
​

封装shiro AuthenticationToken

public class JwtAuthToken implements AuthenticationToken {
    private String token;
    private Long userId;
​
    public JwtAuthToken(String token,Long userId) {
        this.token = token;
        this.userId=userId;
    }
​
    @Override
    public Object getPrincipal() {
        return userId;
    }
​
    @Override
    public Object getCredentials() {
        return token;
    }
}
​

定义shiro 配置

@Configuration
public class ShiroConfig {
​
    @Bean
    public DefaultWebSubjectFactory subjectFactory() {
        return new CustomWebSubjectFactory();
    }
    @Bean
    public CustomAuthFilter customAuthFilter(){
        return new CustomAuthFilter();
    }
    @Bean
    public CustomCredentialsMatcher customCredentialsMatcher(){
        return new CustomCredentialsMatcher();
    }
    @Bean
    public CustomRealm customRealm(CustomCredentialsMatcher customCredentialsMatcher){
        CustomRealm realm=new CustomRealm();
        realm.setCredentialsMatcher(customCredentialsMatcher);
        return realm;
    }
​
    @Bean
    public DefaultWebSecurityManager  securityManager(CustomRealm customRealm){
        DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
        securityManager.setRealm(customRealm);
        //关闭shiro session 功能
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);
        return securityManager;
    }
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
​
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager,FilterRegistrationBean tokenRegistrationBean){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
        LinkedHashMap<String, Filter> filtersMap = new LinkedHashMap<>();
        filtersMap.put("tokenFilter",tokenRegistrationBean.getFilter());
        shiroFilterFactoryBean.setFilters(filtersMap);
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        //配置拦截策略
        filterChainDefinitionMap.put("/api/v1/auth/user/login", "anon");
     
        filterChainDefinitionMap.put("/**","tokenFilter");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }
​
    @Bean
    public FilterRegistrationBean tokenRegistrationBean(CustomAuthFilter customAuthFilter) {
        FilterRegistrationBean registration = new FilterRegistrationBean(customAuthFilter);
        registration.setEnabled(false);
        return registration;
    }