SpringBoot2 整合 Shiro 基于URL的权限校验

概念模型

用户与角色之间是一对一关联,角色与资源之间是多对多关联(关联关系用中间表来维护)

Resource里面存储着系统的url路径

模型字段都很简单,SQL及Model类就不再贴出来了.


1.引入依赖

<dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.3.2</version>
</dependency>

添加这一个依赖就够用了,它会依赖core和web

 


我写的shiro类有这些

CheckLoginFilter  登录认证过滤器

CheckPermissionsFilter 权限校验过滤器

ShiroConfig 配置类(核心)

ShiroLifecycleBeanPostProcessorConfig shiro生命周期管理

ShiroPermissionResolver 生成Permission实例(关键)

ShiroWildcardPermission Permission的子孙类,判断权限通过与否(关键)

UserRealm 提供身份认证和授权(核心)

要使用自定义的鉴权方式,需要写  PermissionResolver  和  WildcardPermission 的子类

注:所有的Service类.就不贴出来了,功能很简单

1.首先看一下UserRealm

doGetAuthorizationInfo 和 doGetAuthenticationInfo方法必须要求实现

这里为了url权限校验的需要,覆盖了isPermitted方法(这不是必须的)

package com.web.shiro;

import com.web.constant.Constant;
import com.web.constant.ErrorCode;
import com.web.dto.AuthorizationRoleInfoDTO;
import com.web.entity.Role;
import com.web.entity.User;
import com.web.exception.PolarisException;
import com.web.service.RoleService;
import com.web.service.UserService;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.Permission;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.eis.MemorySessionDAO;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;

import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;

/**
 * Realm 充当了 Shiro 与应用安全数据间的“桥梁”或者“连接器”
 *
 * @author 
 * @date 2019/07/09 10:43
 */
public class UserRealm extends AuthorizingRealm {
    @Autowired
    @Lazy
    private UserService userService;
    @Autowired
    @Lazy
    private RoleService roleService;
    @Autowired
    @Lazy
    private MemorySessionDAO sessionDAO;

    /**
     * 授权
     *
     * @param principal principal
     * @return 用户权限信息集合
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
        User user = (User) principal.getPrimaryPrincipal();
        SimpleAuthorizationInfo info = (SimpleAuthorizationInfo) SecurityUtils.getSubject()
                .getSession().getAttribute(Constant.AUTHORIZATION_INFO);
        if (info != null) {
            return info;
        }

        info = new SimpleAuthorizationInfo();
        AuthorizationRoleInfoDTO roleInfo = roleService.getAuthorizationRoleInfoDTOByUsername(user.getUsername());
        if (roleInfo == null) {
            return info;
        }

        info.addRole(String.valueOf(roleInfo.getRoleId()));
        // 设置权限码
        info.setStringPermissions(new HashSet<>(roleInfo.getUrls()));
        SecurityUtils.getSubject().getSession().setAttribute(Constant.AUTHORIZATION_INFO, info);
        return info;
    }

    /**
     * 身份认证
     *
     * @param token token
     * @return 用户角色信息集合
     * @throws AuthenticationException 认证异常
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 当事人,类型取决于调用subject.login之前存储的数据类型
        String username = (String) token.getPrincipal();
        User user = userService.getByUsername(username);
        if (null == user) {
            throw new PolarisException(ErrorCode.USERNAME_OR_PASSWORD_ERROR);
        }

        if (User.STATUS_DISABLE == user.getStatus()) {
            throw new PolarisException(ErrorCode.USER_IS_DISABLED);
        }

        if (User.STATUS_DELETED == user.getStatus()) {
            throw new PolarisException(ErrorCode.USERNAME_OR_PASSWORD_ERROR);
        }

        String password = user.getPassword();
        user.setPassword(null);
        user.setRole(roleService.getById(user.getRoleId()));
        // 第一个参数随便放,可以是user,在系统中任意位置可以获取改对象;
        // 第二个参数必须是密码
        // 第三个参数当前Realm的名称,因为可能存在多个Realm
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName());
        stopPreviousSession(user.getId());
        return info;
    }

    /**
     * 超级管理员自动获取所有权限
     */
    @Override
    public boolean isPermitted(PrincipalCollection principals, String permission) {
        User user = ((User) principals.getPrimaryPrincipal());
        if (Role.ADMIN_FLAG_SUPER_ADMIN == user.getRole().getAdminFlag()) {
            return true;
        }

        return isPermitted(principals, getPermissionResolver().resolvePermission(permission));
    }

    @Override
    public boolean isPermitted(PrincipalCollection principals, Permission permission) {
        AuthorizationInfo info = getAuthorizationInfo(principals);
        Collection<Permission> perms = getPermissions(info);
        if (CollectionUtils.isEmpty(perms)) {
            return false;
        }

        for (Permission perm : perms) {
            if (perm.implies(permission)) {
                return true;
            }
        }

        return false;
    }

    /**
     * 踢掉上一个登录的同名用户
     *
     * @param id 主键
     */

    private void stopPreviousSession(Integer id) {
        Collection<Session> sessions = sessionDAO.getActiveSessions();
        Session currSession = SecurityUtils.getSubject().getSession();
        Serializable sId = currSession.getId();
        for (Session session : sessions) {
            SimplePrincipalCollection collection = (SimplePrincipalCollection) session
                    .getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
            if (collection == null) {
                continue;
            }

            User u = (User) collection.getPrimaryPrincipal();
            if (id.equals(u.getId())) {
                if (sId.equals(session.getId())) {
                    continue;
                }

                session.stop();
                break;
            }
        }
    }
}

 

2. ShiroWildcardPermission 类

      implies方法就是用来校验权限通过与否的.写的比较简单粗浅,考虑的情况很简单....

     假若用户拥有 /user/page 路径的访问权限,那么 /user/page/** 下的所有路径都会判定通过,

     而 /user 路径判定不通过,/role 判定也不通过

package com.web.shiro;

import org.apache.shiro.authz.Permission;
import org.apache.shiro.authz.permission.WildcardPermission;

import java.util.List;
import java.util.Set;

/**
 * @author 
 * @date 2019/07/25 15:10
 */
public class ShiroWildcardPermission extends WildcardPermission {
    ShiroWildcardPermission(String wildcardString) {
        super(wildcardString, DEFAULT_CASE_SENSITIVE);
    }

    @Override
    public boolean implies(Permission p) {
        if (!(p instanceof ShiroWildcardPermission)) {
           return false;
        }

        List<Set<String>> targetParts = ((ShiroWildcardPermission) p).getParts();
        String targetUrl = targetParts.get(0).iterator().next();
        String url = getParts().get(0).iterator().next();
        if (targetUrl.startsWith(url)) {
            if (targetUrl.equals(url)) {
                return true;
            }

            return targetUrl.startsWith(url.concat("/"));
        }

        return false;
    }
}

 

3. ShiroPermissionResolver 类

    很简单,就是生成 ShiroWildcardPermission 类对象

package com.web.shiro;

import org.apache.shiro.authz.Permission;
import org.apache.shiro.authz.permission.PermissionResolver;

/**
 * @author 
 * @date 2019/07/25 15:05
 */
public class ShiroPermissionResolver implements PermissionResolver {
    @Override
    public Permission resolvePermission(String permissionString) {
        return new ShiroWildcardPermission(permissionString);
    }
}

 

4.CheckLoginFilter 

onAccessDenied 方法   当用户没有登录的处理策略,这里是直接返回json

ShiroUtil类放到最后面贴出

package com.web.shiro;

import com.web.base.RestResponse;
import com.web.constant.ErrorCode;
import com.web.utils.ShiroUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

/**
 * 登录认证过滤器
 *
 * @author 
 */
public class CheckLoginFilter extends FormAuthenticationFilter {

    /**
     * 表示访问拒绝时是否自己处理,如果返回true表示自己不处理且继续拦截器链执行,返回false表示自己已经处理了(比如重定向到另一个页面)
     *
     * @param request         对象
     * @param servletResponse 对象
     * @return true-继续往下执行,false-该filter过滤器已经处理,不继续执行其他过滤器
     * @throws Exception 异常
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse servletResponse) throws Exception {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        Object user = SecurityUtils.getSubject().getPrincipal();
        if (null != user) {
            return true;
        }

        Map<String, String> data = new HashMap<>(1);
        data.put("toLoginUrl", "/login");
        RestResponse<Map> rsp = new RestResponse<>(ErrorCode.SESSION_TIMEOUT_ERROR);
        rsp.setMsg("你还没有登录或者会话过期!!!!!!!!!");
        rsp.setData(data);
        ShiroUtil.errorResponse(response, rsp);
        return false;
    }
}

5.CheckPermissionsFilter

第一个方法判定权限的是否通过,getPathWithinApplication(request) 取得当前请求的url路径

方法会进入到UserRealm的 doGetAuthorizationInfo 方法进行授权,之后进入isPermitted方法进行初步处理,最终会进入ShiroWildcardPermission的 implies 进行鉴权.

第二个方法表示判定失败时的处理措施

package com.web.shiro;

import com.web.base.RestResponse;
import com.web.constant.ErrorCode;
import com.web.utils.ShiroUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;

/**
 * 检验权限过滤器
 *
 * @author 
 * @date 2019/07/24 14:34
 */
public class CheckPermissionsFilter extends PermissionsAuthorizationFilter {

    @Override
    public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        return SecurityUtils.getSubject().isPermitted(getPathWithinApplication(request));
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse r) {
        if (null == SecurityUtils.getSubject().getPrincipals()) {
            RestResponse rest = new RestResponse(ErrorCode.SESSION_TIMEOUT_ERROR);
            ShiroUtil.errorResponse((HttpServletResponse) r, rest);
            return false;
        }

        RestResponse rest = new RestResponse(ErrorCode.PERMIT_INSUFFICIENT);
        rest.setMsg("你没有访问权限,请联系管理员");
        ShiroUtil.errorResponse((HttpServletResponse) r, rest);
        return false;
    }
}

 

6.ShiroLifecycleBeanPostProcessorConfig 类

  里面只有一个bean

import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author 
 * @date 2019/07/12 15:33
 */
@Configuration
public class ShiroLifecycleBeanPostProcessorConfig {
    /**
     * Shiro生命周期处理器
     *
     * @return LifecycleBeanPostProcessor
     */
    @Bean(name = "lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

}

 

7.ShiroConfig 类

import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.eis.MemorySessionDAO;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Shiro 配置类
 *
 * @author 
 * @date 2019/07/09 10:13
 */
@Configuration
@AutoConfigureAfter(ShiroLifecycleBeanPostProcessorConfig.class)
public class ShiroConfig {
    @Value("${shiro.session.global-session-timeout}")
    String sessionTimeOut;

    @Bean("userRealm")
    public UserRealm userRealm() {
        UserRealm realm = new UserRealm();
        realm.setPermissionResolver(new ShiroPermissionResolver());
        return realm;
    }

    /**
     * 配置保存sessionId的cookie
     * 注意:这里的cookie 不是上面的记住我 cookie 记住我需要一个cookie session管理 也需要自己的cookie
     * 默认为: JSESSIONID 问题: 与SERVLET容器名冲突,重新定义为sid
     */
    @Bean("sessionIdCookie")
    public SimpleCookie sessionIdCookie() {
        //这个参数是cookie的名称
        SimpleCookie simpleCookie = new SimpleCookie("sid");
        //setcookie的httponly属性如果设为true的话,会增加对xss防护的安全系数。它有以下特点:

        //setcookie()的第七个参数
        //设为true后,只能通过http访问,javascript无法访问
        //防止xss读取cookie
        simpleCookie.setHttpOnly(true);
        simpleCookie.setPath("/");
        //maxAge=-1表示浏览器关闭时失效此Cookie
        simpleCookie.setMaxAge(1800);
        return simpleCookie;
    }

    @Bean(name = "sessionDAO")
    public MemorySessionDAO getMemorySessionDAO() {
        return new MemorySessionDAO();
    }

    @Bean("sessionManager")
    public DefaultWebSessionManager defaultWebSessionManager(
                                                        @Qualifier("sessionIdCookie") SimpleCookie simpleCookie,
                                                        @Qualifier("sessionDAO") MemorySessionDAO sessionDAO) {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        // 全局会话超时时间,毫秒,目前是二十分钟
        sessionManager.setGlobalSessionTimeout(Integer.parseInt(sessionTimeOut));
        sessionManager.setSessionIdCookie(simpleCookie);
        sessionManager.setSessionDAO(sessionDAO);
        // 删除无效的session
        sessionManager.setDeleteInvalidSessions(true);
        // 是否开启定时调度器进行检测过期session 默认为true
        sessionManager.setSessionValidationSchedulerEnabled(true);
        //设置session失效的扫描时间, 清理用户直接关闭浏览器造成的孤立会话 默认为 1个小时
        //设置该属性 就不需要设置 ExecutorServiceSessionValidationScheduler 底层也是默认自动调用ExecutorServiceSessionValidationScheduler
        // 毫秒 十分钟
        sessionManager.setSessionValidationInterval(600000);
        //去掉URL中的JSESSIONID
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        return sessionManager;
    }

    @Bean("securityManager")
    public DefaultWebSecurityManager securityManager(UserRealm userRealm, DefaultWebSessionManager sessionManager) {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setSessionManager(sessionManager);
        manager.setRealm(userRealm);
        return manager;
    }


    /**
     * 开启shiro aop注解支持.
     * 使用代理方式;所以需要开启代码支持;
     * 可以在controller中的方法前加上注解
     * 如 @RequiresPermissions("userInfo:add")
     *
     * @param securityManager 管理器
     * @return AuthorizationAttributeSourceAdvisor
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager);
        bean.setLoginUrl("/toLogin");
        bean.setUnauthorizedUrl("/403");
        // 定义过滤器
        Map<String, Filter> filters = bean.getFilters();
        filters.put("authc", new CheckLoginFilter());
        filters.put("perms", new CheckPermissionsFilter());
        bean.setFilters(filters);
        // 配置访问权限
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();

        filterChainDefinitionMap.put("/", "anon");
        filterChainDefinitionMap.put("/*.ico", "anon");
        filterChainDefinitionMap.put("/**/*.js", "anon");
        filterChainDefinitionMap.put("/**/*.css", "anon");
        filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/user/login", "anon");
        filterChainDefinitionMap.put("/logout", "logout");
        filterChainDefinitionMap.put("/interface/**", "anon");
        filterChainDefinitionMap.put("/**", "authc,perms");
        bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return bean;
    }
}

 

其中 shiroFilter 就配置了访问策略

可以插入如下代码,

(因为可以我只是根据请求url判定,就没有写,当然即使加上了,在我这个代码里面也没有效果,

这和CheckPermissionsFilter 类的 isAccessAllowed,UserRealm类的 doGetAuthorizationInfo 授权方法,以及

ShiroWildcardPermission类的鉴权方法implies有关)
filterChainDefinitionMap.put("/user/**","perms[123]"); // /user 下的所有路径必须具备123权限才可以访问
filterChainDefinitionMap.put("/role/**","role[admin]"); // /role下的路径必须具备admin角色才可以访问

 

8.ShiroUtil 类

  就是输出json提示信息

import com.fasterxml.jackson.databind.ObjectMapper;
import com.polaris.web.base.RestResponse;
import com.polaris.web.constant.ErrorCode;
import com.polaris.web.exception.PolarisException;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author 
 * @date 2019/07/24 15:15
 */
public class ShiroUtil {
    public static void errorResponse(HttpServletResponse response, RestResponse rest) {
        response.setStatus(200);
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        try (PrintWriter writer = response.getWriter()) {
            writer.write(new ObjectMapper().writeValueAsString(rest));
            writer.flush();
        } catch (IOException e) {
            throw new PolarisException(ErrorCode.ServerError);
        }
    }
}

 

好,至此结束!

可以通过断点了解执行流程,这样可以方便理解

水平有限,错误之处敬请指出...谢谢!!!

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值