shiro为什么我配置了anno的路由还是会被拦截「建议收藏」

shiro为什么我配置了anno的路由还是会被拦截「建议收藏」企业级的应用权限控制一直是重中之重,Apache Shiro作为一款轻量级的权限控制框架,一直深受广大开发者的喜爱。公司最近新开始的一个项目,也

欢迎大家来到IT世界,在知识的湖畔探索吧!

企业级的应用权限控制一直是重中之重,Apache Shiro作为一款轻量级的权限控制框架,一直深受广大开发者的喜爱。公司最近新开始的一个项目,也正在使用shiro作为权限框架。

首先给给大家看看我们的项目的shiro配置

shiro

  • shiro公共配置
/*shiroFilter*/
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean ();
        //Shiro的核心安全接口,这个属性是必须的
        shiroFilterFactoryBean.setSecurityManager (securityManager);
        Map<String, Filter> filterMap = new LinkedHashMap<> ();
        filterMap.put ("jwtFilter", jwtFilter ());
        filterMap.put ("urlFilter", urlFilter ());
        //filterMap.put ("systemFilter", systemFilter ());
        shiroFilterFactoryBean.setFilters (filterMap);
        /*定义shiro过滤链  Map结构
         * Map中key(xml中是指value值)的第一个'/'代表的路径是相对于HttpServletRequest.getContextPath()的值来的
         * anon:它对应的过滤器里面是空的,什么都没做,这里.do和.jsp后面的*表示参数,比方说login.jsp?main这种
         * authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter
         */
         /* 过滤链定义,从上向下顺序执行,一般将 / ** 放在最为下边:这是一个坑呢,一不小心代码就不好使了;
          authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问 */
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<> ();
          filterChainDefinitionMap.put ("/swagger-ui.html", "anon");
          filterChainDefinitionMap.put ("/swagger", "anon");
          filterChainDefinitionMap.put ("/swagger-resources/**/**", "anon");
          filterChainDefinitionMap.put ("/v2/**", "anon");
          filterChainDefinitionMap.put ("/webjars/**", "anon");
          filterChainDefinitionMap.put ("/doc.html", "anon");
        filterChainDefinitionMap.put ("/", "anon");
        filterChainDefinitionMap.put ("/static/**", "anon");
        filterChainDefinitionMap.put ("/mass/login", "anon");
        filterChainDefinitionMap.put ("/system/login", "anon");
        filterChainDefinitionMap.put ("/login/auth", "anon");
        filterChainDefinitionMap.put ("/login/logout", "anon");
        filterChainDefinitionMap.put ("/user/logout", "anon");
        filterChainDefinitionMap.put ("/error", "anon");
        filterChainDefinitionMap.put ("/**", "jwtFilter,urlFilter");
        shiroFilterFactoryBean.setFilterChainDefinitionMap (filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

/*自定义filter*/
    @Bean
    public UrlFilter urlFilter() {
        return new UrlFilter ();
    }

    @Bean
    public JwtFilter jwtFilter() {
        return new JwtFilter ();
    }
 /*SecurityManager*/
@Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager ();
        //securityManager.setRealm (userRealm ());
        securityManager.setRealm (jwtRealm ());
        return securityManager;
    }
/*Realm*/

    @Bean
    public JwtRealm jwtRealm() {
        return new JwtRealm ();
    }
 @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor ();
    }

    /**
     * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
     * 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
     */
    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator ();
        advisorAutoProxyCreator.setProxyTargetClass (true);
        return advisorAutoProxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor ();
        authorizationAttributeSourceAdvisor.setSecurityManager (securityManager ());
        return authorizationAttributeSourceAdvisor;
    }

欢迎大家来到IT世界,在知识的湖畔探索吧!

JwtToken

shiro为什么我配置了anno的路由还是会被拦截「建议收藏」

JwtRealm

欢迎大家来到IT世界,在知识的湖畔探索吧!public class JwtRealm extends AuthorizingRealm {

    @Autowired
    JWTTokenUtil jwtTokenUtil;

    @Autowired
    MassUserMapper massUserMapper;

    @Autowired
    SysUserMapper sysUserMapper;

    @Autowired
    RedisService redisService;

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return new SimpleAuthorizationInfo();
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        if(!(token instanceof JwtToken)){
            throw new AuthenticationException("没有对应的realm");
        }
        JwtToken jwtToken = (JwtToken) token;
        try {
            JwtUserVO jwtUser = jwtTokenUtil.jwtUser(jwtToken.getPrincipal().toString());
            if(ObjectUtils.isEmpty(jwtUser)){
                throw new AuthenticationException("认证过期,请重新登录!");
            }
            LoginUserVO loginUser = redisService.get(RedisKeyEnum.JWT_TOKEN_KEY.key() + token.getPrincipal(), LoginUserVO.class);
            if(ObjectUtils.isEmpty(loginUser)){
                loginUser = this.validateUser(jwtUser.getUserId(), jwtUser.getUserType());
                redisService.set (RedisKeyEnum.JWT_TOKEN_KEY.key() + token.getPrincipal(), loginUser, 2L, TimeUnit.HOURS);
            }
            return new SimpleAuthenticationInfo(loginUser, jwtToken.getPrincipal().toString(), getName());
        }catch (Exception e){
            throw new AuthenticationException("认证过期,请重新登录!");
        }
    }

    public LoginUserVO validateUser(String userId, Integer userType){
        if(UserTypeEnum.MASS_USER.getUserType() == userType){
            return this.validateMassUser(userId);
        }else if(UserTypeEnum.ADMIN_USER.getUserType() == userType){
            return this.validateSysUser(userId);
        }else{
            throw new AccountException("不支持的用户类型!");
        }
    }

    /**
     * 通过userId验证mass用户
     * @param userId
     * @return
     */
    public LoginUserVO validateMassUser(String userId){
        MassUserEntity massUserEntity = massUserMapper.selectById(userId);
        if(ObjectUtils.isEmpty(massUserEntity)){
            throw new AccountException("账号不存在!");
        }
        if(massUserEntity.getDeleted() == 1){
            throw new AccountException("用户账号或密码错误!");
        }
        return new LoginUserVO(massUserEntity.getId(), massUserEntity.getUsername(),
                massUserEntity.getPassword(), UserTypeEnum.MASS_USER.getUserType());
    }

    /**
     * 通过userId验证sys用户
     * @param userId
     * @return
     */
    public LoginUserVO validateSysUser(String userId){
        SysUserEntity sysUserEntity = sysUserMapper.selectById(userId);
        if(ObjectUtils.isEmpty(sysUserEntity)){
            throw new AccountException("账号不存在!");
        }
        if(sysUserEntity.getDeleted() == 1){
            throw new AccountException("用户账号或密码错误!");
        }
        return new LoginUserVO(sysUserEntity.getId(), sysUserEntity.getUsername(),
                sysUserEntity.getPassword(), UserTypeEnum.ADMIN_USER.getUserType());
    }
}

UrlFilter

public class UrlFilter extends AccessControlFilter {

    private static Logger logger = LoggerFactory.getLogger (UrlFilter.class);

    @Autowired
    private RedisService redisService;

    @Value("${system.access.admin-url}")
    private String adminUrl;

    @Value("${system.access.deal-url}")
    private String dealUrl;

    @Value("${system.access.mass-url}")
    private String massUrl;

    @Autowired
    JWTTokenUtil jwtTokenUtil;

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        try {
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            String token = httpServletRequest.getHeader ("token");
            LoginUserVO loginUser = redisService.get (RedisKeyEnum.JWT_TOKEN_KEY.key () + token, LoginUserVO.class);
            if (ObjectUtils.isEmpty (loginUser)) {
                return false;
            }
            // 2.根据用户类型,验证是否有权访问此路径
            if (loginUser.getUserType () == UserTypeEnum.ADMIN_USER.getUserType ()) {
                return true;
            }
            //委办局用户可以访问委办局的url
            if (loginUser.getUserType () == UserTypeEnum.DEAL_USER.getUserType ()) {
                return pathsMatch (dealUrl, request) || pathsMatch (adminUrl, request);
            }
            //民众用户可以访问
            if (loginUser.getUserType () == UserTypeEnum.MASS_USER.getUserType ()) {
                return pathsMatch (massUrl, request);
            }
            return true;
        } catch (Exception e) {
            logger.error ("访问拦截出错!", e);
        }
        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        PrintWriter out = null;
        try {
            response.setContentType ("application/json");
            response.setCharacterEncoding ("utf-8");
            out = response.getWriter ();
            String result = JSON.toJSONString (request.getAttribute ("resultJSON"));
            out.write (JSON.toJSONString (Response.error ("没有权限访问该路径")));
        } catch (IOException e) {
            throw e;
        } finally {
            if (null != out) {
                out.flush ();
                out.close ();
            }
        }
        return false;
    }
}

JwtFilter

欢迎大家来到IT世界,在知识的湖畔探索吧!public class JwtFilter extends BasicHttpAuthenticationFilter {

    private static Logger logger = LoggerFactory.getLogger (JwtFilter.class);

    private static String tokenKey = "token";

    @Autowired
    JWTTokenUtil jwtTokenUtil;


    @Override
    protected boolean isLoginAttempt(ServletRequest servletRequest, ServletResponse servletResponse) {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String jwtToken = request.getHeader (tokenKey);
        if (StringUtils.isNotEmpty (jwtToken)) {
            return true;
        }
        return false;
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {

        if (isLoginAttempt (request, response)) {
            try {
                this.executeLogin (request, response);
                return true;
            } catch (Exception e) {
                logger.error ("用户登录失败!");
                ajaxNotLogin (response, e.getMessage ());
            }
        }
        ajaxNotLogin (response, "请登录系统.");
        return false;
    }

    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String jwtToken = httpServletRequest.getHeader (tokenKey);
        Subject subject = getSubject (request, response);
        subject.login (new JwtToken (jwtToken));
        if (subject.isAuthenticated ()) {
            return true;
        } else {
            return false;
        }
    }


    public void ajaxNotLogin(ServletResponse servletResponse, String msg) {

        HttpServletResponse response = WebUtils.toHttp (servletResponse);
        response.setStatus (HttpStatus.UNAUTHORIZED.value ());
        response.setCharacterEncoding ("UTF-8");
        response.setContentType ("application/json; charset=utf-8");
        Response<String> result = Response.error (msg);
        PrintWriter out = null;
        try {
            out = response.getWriter ();
            out.write (JSON.toJSONString (result));
        } catch (IOException e) {
            logger.error ("response输出错误", e);
        } finally {
            if (null != out) {
                out.flush ();
                out.close ();
            }
        }
    }
}

以上就是我们项目关于shiro的一些配置。

一号坑

创建filterChainDefinitionMap时一定要使用LinkedHashMap,否则在使用过程中会有意想不到的问题。因为Shiro的过滤器链是根据配置的filterChainDefinitionMap中配置的路由拦截规则,在调用时是根据map中元素的顺序依次来调用,而HashMap是无序的,因此,在使用时使用有序的LinkedHashMap

shiro为什么我配置了anno的路由还是会被拦截「建议收藏」

二号坑

先给大家看一个现象吧,我们的swagger竟然被拦截了。

shiro为什么我配置了anno的路由还是会被拦截「建议收藏」

我明明在上面配置了swagger的访问不需要鉴权

filterChainDefinitionMap.put (“/swagger-ui.html”, “anon”);

filterChainDefinitionMap.put (“/swagger”, “anon”);

filterChainDefinitionMap.put (“/swagger-resources/**/**”, “anon”);

filterChainDefinitionMap.put (“/v2/**”, “anon”);

filterChainDefinitionMap.put (“/webjars/**”, “anon”);

filterChainDefinitionMap.put (“/doc.html”, “anon”);

为什么还是被拦截了呢。

查看项目启动日志后发现了下面这段话

shiro为什么我配置了anno的路由还是会被拦截「建议收藏」

可以看到我们编写的Shiro的Filter被放进了Tomcat的过滤器链,也就是被ApplicationFilterChain管理起来了。

因此出现这个问题就不难理解了,我们在shiro中配置了不需要验证,但是在实际运行的时候,Tomcat中的过滤器还是拦截了我们的请求。

因此我们自定义的shiro过滤器一定不能在springboot启动的时候被注入独立注入进tomcat内。

现在问题的关键就是我们如何抑制自定义的shiroFiler向tomcat的拦截器链注入。

这里我们需要用到一个特殊的Bean——FilterRegistrationBean

具体的代码如下

shiro为什么我配置了anno的路由还是会被拦截「建议收藏」

协商如上的代码后,再启动项目可以看到已经可以正常使用。

shiro为什么我配置了anno的路由还是会被拦截「建议收藏」

shiro为什么我配置了anno的路由还是会被拦截「建议收藏」

总结

上面就是我们项目在使用shiro的时候碰到的一些问题,希望可以帮助你解决项目中出现的同样问题。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/11829.html

(0)

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们YX

mu99908888

在线咨询: 微信交谈

邮件:itzsgw@126.com

工作时间:时刻准备着!

关注微信