SpringBoot + Shiro + Vue 前后端分离的权限管理

第一次写简书,打算用来做做开发的笔记本吧,以下是Shiro的使用案例,Shiro在SpringBoot框架下的使用,前端使用的是Vue脚手架,在老项目上测试的权限管理,所以只贴上了部分代码。

首先Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

三个核心组件:Subject, SecurityManagerRealms.

Subject:代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
Realms: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。

下面是在pom.xml中添加的依赖

        <!-- shiro -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.crazycake</groupId>
            <artifactId>shiro-redis</artifactId>
            <version>2.8.20</version>
        </dependency>

        <!--  redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>

使用redis缓存做session和权限cache的保存

一般下,Shiro的授权方式为代码方式和注解方式,

代码方式

Subject subject = SecurityUtils.getSubject();
// 判断用户身份是否为管理员
if (subject.hasRole("admin")){ 
    //有权限do sth.
}else{
    //无权限do sth.
}
 // 判断用户是否拥有"listen:high"权限
// 注意一般权限的表达式为三段式或两段式,以:号隔开 *:*:* 或者 *:* 这种格式
// 此处例子代码匹配所有三段式和二段式的权限
if(subject.isPermitted("listen:high")){
    //有权限do sth.
}else{
    //无权限do sth.
}

注解方式

@RequestMapping(value = "/getUser")
@RequiresRoles(value = {"admin"},logical = Logical.OR)
@RequiresPermissions("listen:high")
public Object getUser() {
  //do sth..
}

两种方式各有优劣,代码方式可以在方法内执行一些复杂的逻辑业务,但是请求已经进入了程序方法中,注解方式的好处是能将用户请求拦截在方法外,请求不进入方法,且使用较为方便,但是处理复杂的逻辑业务不是很灵活,需要自行捕捉抛出的异常类来实现业务逻辑。可按个人爱好选择,该dome使用的注解方式来完成。个人推荐注解方式。

以下是注解解析

@RequiresUser:当前Subject必须是应用的用户,才能访问或调用被该注解标注的类,实例,方法。
@RequiresRoles: 当前Subject必须拥有所有指定的角色时,才能访问被该注解标注的方法。如果当天Subject不同时拥有所有指定角色,则方法不会执行还会抛出AuthorizationException异常。
@RequiresPermissions:当前Subject需要拥有某些特定的权限时,才能执行被该注解标注的方法。如果当前Subject不具有这样的权限,则方法不会被执行。
@RequiresGuest:使用该注解标注的类,实例,方法在访问或调用时,当前Subject可以是“gust”身份,不需要经过认证或者在原先的session中存在记录。
@RequiresAuthentication:使用该注解标注的类,实例,方法在访问或调用时,当前Subject必须在当前session中已经过认证。

该项目主要运用@RequiresRoles@RequiresPermissions两个注解。
示例

//用户拥有user身份则进入
@RequiresRoles("user")

//用户拥有必须同时属于user和admin身份则进入
@RequiresRoles({"user","admin"})

//用户拥有user或者admin身份之一;修改 logical 为 OR 即可
@RequiresRoles(value={"user","admin"},logical=Logical.OR)

@RequiresPermissions 用法类似不在演示

项目结构为:

1544611423(1)_副本.png

Permission 权限实体
Permission.java

/**
 * @author by. 不笑猫丶
 * @date 2018年12月12日
 */
public class Permission {

    private String name;
    private String permission;
    private String code;

    public Permission(){}

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPermission() {
        return permission;
    }

    public void setPermission(String permission) {
        this.permission = permission;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }
}

Roles 权身份实体
Roles.java

/**
 * @author by. 不笑猫丶
 * @date 2018年12月12日
 */
public class Roles {

    private String role;
    private String code;
    private String permission;

    public Roles() {}

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getPermission() {
        return permission;
    }

    public void setPermission(String permission) {
        this.permission = permission;
    }
}

ShiroConfig Shiro配置类
ShiroConfig.java

/**
 * @author by. 不笑猫丶
 * @date 2018年12月12日
 * shiro配置类
 */
@Configuration
public class ShiroConfig {

    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;
    @Value("${spring.redis.password}")
    private String password;

    @Bean
    public ShiroFilterFactoryBean shirFilter(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
        System.out.println("ShiroConfiguration.shirFilter()");
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        //注意过滤器配置顺序不能颠倒
        //配置退出过滤器,修改了默认logou过滤器,清除相应的缓存信息
        filterChainDefinitionMap.put("/easeApi/authc/user/change/Exit", "logout");
        // 配置需要拦截的链接
        filterChainDefinitionMap.put("/easeApi/authc/**", "ShiroAuthFilter");
        Map<String, Filter> filterMap = new LinkedHashMap<>();
        // 自定义的登录过滤器
        filterMap.put("ShiroAuthFilter", new ShiroAuthFilter());
        shiroFilterFactoryBean.setFilters(filterMap);
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 凭证匹配器
     * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了)
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashIterations(3);//散列的次数,比如散列两次,相当于 md5(md5(md5("")));
        return hashedCredentialsMatcher;
    }

    /**
     * 注入自定义的Realm类
     **/
    @Bean
    public MyShiroRealm myShiroRealm() {
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return myShiroRealm;
    }


    @Bean(name = "securityManager")
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        // 自定义session管理 使用redis
        securityManager.setSessionManager(sessionManager());
        // 自定义缓存实现 使用redis
        securityManager.setCacheManager(cacheManager());
        securityManager.setRealm(myShiroRealm());

        return securityManager;
    }


    /**
     * 自定义sessionManager,使用redisSessionDAO生成并保存session
     **/
    @Bean(name = "sessionManager")
    public SessionManager sessionManager() {
        MySessionManager mySessionManager = new MySessionManager();
        mySessionManager.setSessionDAO(redisSessionDAO());
        return mySessionManager;
    }


    /**
     * 配置shiro redisManager
     * @return
     */
    @ConfigurationProperties(prefix = "spring.redis")
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setDatabase(3);
        return redisManager;
    }

    /**
     * cacheManager 缓存 redis实现
     * @return
     */
    public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }

    /**
     * 通过redis RedisSessionDAO shiro sessionDao层的实现 
     * 使用shiro-redis插件
     */
    @Bean("redisSessionDAO")
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        return redisSessionDAO;
    }

    /**
     * 开启shiro aop注解支持.
     * 使用代理方式;所以需要开启代码支持;
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

MyShiroRealm类自定义的Realm,用于身份认证和权限获取。

MyShiroRealm.java


/**
 * @author by. 不笑猫丶
 * @date 2018年12月12日
 * 自定义权限匹配
 */
public class MyShiroRealm extends AuthorizingRealm {

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

    @Autowired
    private UserService userService;

    @Autowired
    private UserDao userDao;

    //通过用户名查找用户拥有权限
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //获取SimpleAuthenticationInfo中传来的username
        String username = (String) principals.getPrimaryPrincipal();
        Map<String,Object> map = acquire.getHashMap("username", username);
        //得到用户实体
        UserPrivacy userPrivacy = (UserPrivacy) userService.GetMsgformTable(3, map);
        //得到用户身份的代码
        String role = userPrivacy.getRoleCode();
        //通过身份代码查找对应的权限代码的集合
        List<String> perCodeList = userDao.GetRoles(role);
        //权限代码的集合查找对应的权限表达式 如 "listen:high"
        List<String> permission = userDao.GetPermission(perCodeList);
        //添加用户权限
        authorizationInfo.addStringPermissions(permission);
        //添加用户角色
        authorizationInfo.addRole(role);
        return authorizationInfo;
    }

    //身份认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        logger.info("成功进入ShiroRealm认证器");
        //从token中获取用户名.
        String username = (String) token.getPrincipal();
        SimpleAuthenticationInfo authenticationInfo;
        Map<String,Object> map = acquire.getHashMap("username", username);
        // 获取用户信息
        UserPrivacy userPrivacy = (UserPrivacy) userService.GetMsgformTable(3, map);
        if(userPrivacy != null) {
            // 用户存在,检查用户状态 code = 0 为保护状态
            Result result = userService.findNumcheck(username);
            int CODE = result.getStatus();
            if(CODE == 0){
                throw new LockedAccountException();
            } else {
                // 用户存在,且不为保护状态,对密码进行md5转码并与前端传来的password比对
                String password = EncryUtils.getMD5(userPrivacy.getPassword().getBytes());
                //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
                authenticationInfo  = new SimpleAuthenticationInfo(username,password,getName());//getName() realm name
            }
        } else {
            return null;
        }
        return authenticationInfo;
    }


    /**
     * 通过用户名清除缓存
     */
    public void clearCache(String username) {
        System.out.println("调用cache清理操作");
        PrincipalCollection principals = new SimplePrincipalCollection(
                new UserPrivacy(username), getName());
        clearCache(principals);
    }

    @Override
    public void clearCache(PrincipalCollection principals) {
        super.clearCache(principals);
    }

AuthException类异常统一处理,因为项目是前后端分离的项目,异常捕捉后不能重定向,只返回json数组。此处只捕捉了UnauthorizedException异常,其他异常可自行实现。

AuthException.java


/**
* @author by. 不笑猫丶
* @date 2018年12月12日
*/
@ControllerAdvice
public class AuthException {

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

    @ExceptionHandler(value = UnauthorizedException.class)//处理访问方法时权限不足问题
    public void AuthcErrorHandler(HttpServletResponse res, Exception e) throws IOException {
        logger.info("抛出UnauthorizedException权限异常");
        res.setHeader("Access-Control-Allow-Credentials", "true");
        res.setContentType("application/json; charset=utf-8");
        res.setStatus(HttpServletResponse.SC_OK);
        PrintWriter writer = res.getWriter();
        Map<String, Object> map= new HashMap<>();
        map.put("status", 3);
        map.put("msg", "权限不足");
        writer.write(JSON.toJSONString(map));
        writer.close();
    }
}

ShiroAuthFilter自定义的 Shiro 登录认证过滤器,继承FormAuthenticationFilter类拦截权限不足的请求,并返回JSON数据

ShiroAuthFilter.java


/**
 * @author by. 不笑猫丶
 * @date 2018年12月12日
 */
public class ShiroAuthFilter extends FormAuthenticationFilter {

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


    public ShiroAuthFilter() {
        super();
    }

    @Override
    public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        //Always return true if the request's method is OPTIONS
        if (request instanceof HttpServletRequest) {
            if (((HttpServletRequest) request).getMethod().toUpperCase().equals("OPTIONS")) {
                return true;
            }
        }
        return super.isAccessAllowed(request, response, mappedValue);
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        logger.info("SHIROFILTER authc拦截");
        HttpServletResponse res = (HttpServletResponse)response;
        res.setHeader("Access-Control-Allow-Origin", "true");
        res.setContentType("application/json; charset=utf-8");
        res.setStatus(HttpServletResponse.SC_OK);
        PrintWriter writer = res.getWriter();
        Map<String, Object> map= new HashMap<>();
        map.put("status", 3);
        map.put("msg", "未登录");
        writer.write(JSON.toJSONString(map));
        writer.close();
        //return false 拦截, true 放行
        return false;
    }
}

ShiroAutoAuthFilter类,自定义的WebFilter过滤器,该项目中用于基于cookie的自动登录场景,在session过期或者关闭浏览器时,session失效或不存在时,在用户cookie尚存的情况下,再次请求被保护的资源时,重新获取认证,在Vue单页面的情况下无需刷新页面重新获取JSESSIONID,该类在JSESSIONID不存在时会自动生成cookie分配到前端页面中。

ShiroAutoAuthFilter.java

/**
 * @author by. 不笑猫丶
 * @date 2018年12月12日
 * /easeApi/authc/ 为受保护资源的路径
 */
@WebFilter(filterName = "shiroAutoAuthFilter", urlPatterns = {"/easeApi/authc/*"})
public class ShiroAutoAuthFilter implements Filter {

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

    @Override
    public void destroy() {

    }

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private UserDao userDao;

    public static final String SESSIONID = "JSESSIONID";

    public static final int MAXAGE = 1800;

    public static final String AUTHORIZATION = "Authorization";

    @SuppressWarnings("deprecation")
    @Override
    public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2) throws IOException, ServletException {
        logger.info("shiroAutoAuthFilter被调用");
        HttpServletRequest request = (HttpServletRequest) arg0;
        HttpServletResponse response = (HttpServletResponse) arg1;
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        //判断用于自动登录的cookie是否存在
        Cookie UIDcookie = com.music.Tools.CookieTool.getCookieByName(request, "UID");
        //用户给request添加header信息 添加Authorization头,保证此次请求不被拦截,实现不刷新页面自动认证的关键
        MyHttpServletRequestWrapper httpReq = new MyHttpServletRequestWrapper(request);
        if (UIDcookie != null) {
            //获取securityManager管理器
            SecurityManager securityManager = (SecurityManager)SpringUtil.getBean("securityManager");
            SecurityUtils.setSecurityManager(securityManager);
            String enUser = URLDecoder.decode(UIDcookie.getValue());
            //得到username和password
            String[] userArray = com.music.Tools.PBEUtils.decrypt(enUser).split("_");
            String username = userArray[0];
            String password = userArray[1];
            Map<String, Object> map = acquire.getHashMap("username,password",username+","+password);
            //检查帐号密码是否有效
            boolean empty = userDao.QueryforPrivacy(map);
            if (!empty) {
                //拦截返回提示
                authcReq(response);
                return;
            }
            //获取当前JSESSIONID,判断是否存在
            Cookie SUID = CookieTool.getCookieByName(request, SESSIONID);
            if(SUID == null) {
                logger.info("JSESSIONID为空直接执行登录操作");
                //JSESSIONID为空直接执行登录操作,并设置JSESSIONID至前端,实现不刷新自动认证
                ShiroTool.authLogin(httpReq, response, username, password);
                arg2.doFilter(httpReq, response);
                return;
            }
            //判断 JSESSIONID 是否存在redis中 
            boolean bol = redisUtil.hasKey(3, "shiro:session:"+SUID.getValue());
            //redis检测JSESSIONID结果为若为false则调用登录操作
            logger.info("redis检测JSESSIONID结果为  :"+bol);
            if(!bol){
                //不存在执行登录操作
                ShiroTool.authLogin(httpReq, response, username, password);
            } else {
                // 存在,判断是否获得认证
                logger.info("JSESSIONID存在,验证是否已认证");
                boolean auth = ShiroTool.isAuthenticated(SUID.getValue(), request, response);
                if (!auth){
                    // 否,获取认证
                    logger.info("JSESSIONID未认证,执行登录操作");
                  
                    ShiroTool.authLogin(httpReq, response, username, password);
                }else {
                    // 是,打印消息
                    logger.info("JSESSIONID已认证");
                }
            }
        }
        arg2.doFilter(httpReq, response);
    }

    private void authcReq(HttpServletResponse response) throws IOException {
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setStatus(HttpServletResponse.SC_OK);
        response.setContentType("application/json; charset=utf-8");
        PrintWriter writer = response.getWriter();
        Map<String, Object> map= new HashMap<>();
        map.put("status", 4);
        map.put("msg", "未找到用户信息");
        writer.write(JSON.toJSONString(map));
        writer.close();
    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {

    }

ShiroTool类shiro工具类,包括判断是否认证的方法和获取缓存用户信息,和login+设置cookie的操作
ShiroTool.java

/**
 * @author by. 不笑猫丶
 * @date 2018年12月12日
 * Shiro 工具类
 */
public class ShiroTool {

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

    /**
     * 验证是否登陆
     */
    public static boolean isAuthenticated(String sessionID,HttpServletRequest request,HttpServletResponse response){
        boolean status = false;

        SessionKey key = new WebSessionKey(sessionID,request,response);
        try{
            Session se = SecurityUtils.getSecurityManager().getSession(key);
            Object obj = se.getAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY);
            if(obj != null){
                status = (Boolean) obj;
            }
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            Session se = null;
            Object obj = null;
        }

        return status;
    }
    /**
     * 获取用户登录信息
     */
    public static UserPrivacy getUserPrivacy(String sessionID, HttpServletRequest request, HttpServletResponse response){
        boolean status = false;
        SessionKey key = new WebSessionKey(sessionID,request,response);
        try{
            Session se = SecurityUtils.getSecurityManager().getSession(key);
            Object obj = se.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
            SimplePrincipalCollection coll = (SimplePrincipalCollection) obj;
            return (UserPrivacy)coll.getPrimaryPrincipal();
        }catch(Exception e){
            e.printStackTrace();
        }finally{
        }
        return null;
    }

    public static void authLogin(MyHttpServletRequestWrapper request, HttpServletResponse response, String username, String password){
        //JSESSIONID为  :true  执行登录操作
        logger.info("Shiro执行登录操作");
        Subject subject = SecurityUtils.getSubject();
        String sidVal = (String) subject.getSession().getId();
        Cookie SIDCookie = CookieTool.setCookie(ShiroAutoAuthFilter.SESSIONID   , sidVal, ShiroAutoAuthFilter.MAXAGE);
        CookieTool.addCookie(response, SIDCookie, true);
        request.putHeader(ShiroAutoAuthFilter.AUTHORIZATION, sidVal);
        logger.info("subject生成的sessionID   :"+sidVal);
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        subject.login(token);
    }

}

MySessionManager类自定义的session获取类

MySessionManager.java


/**
 * @author by. 不笑猫丶
 * @date 2018年12月12日
 * 自定义session获取
 */
public class MySessionManager  extends DefaultWebSessionManager {

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

    private static final String AUTHORIZATION = "Authorization";

    private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";

    public MySessionManager() {
        super();
    }

    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
        //如果请求头中有 Authorization 则其值为sessionId
        if (!StringUtils.isEmpty(id)) {
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            logger.info("Session管理器检测到AUTHORIZATION token 使用此token作为Session");
            return id;
        } else {
            //否则按默认规则从cookie取sessionId
            return super.getSessionId(request, response);
        }
    }
}

以上Shiro的配置和一些基本使用已经全部搭建好,可以开始测试了。

测试之前,说一点Dao层需注意的一点,若要实现动态的修改用户权限范围或者身份,需要更改身份时清除对应的认证缓存,否则身份修改后,缓存还在的情况下,shiro会读取缓存中之前的身份信息,所以可前端请求修改User身份时清除redis中的cache权限缓存信息,则下次用户访问受保护的资源时会再次调用权限获取方法 doGetAuthorizationInfo 从数据库中获取相应权限和身份,此时获取的数据为修改后的数据,可以实现简单的用户权限变更操作。

部分Dao层代码

    /**
     * 根据 T 实体修改与column字段相匹配的数据信息
     * @param userPrivacy 实体类
     * @param column 字段名
     * @param value 字段对应值
     * @return TRUE or FALSE
     */
    @Override
    public boolean UpdateToPrivacy(UserPrivacy userPrivacy, String column, String value){
        Integer result = mapper.getUserPrivacyMapper().update(userPrivacy, new EntityWrapper<UserPrivacy>().eq(column, value));
        if (result > 0){
            if(userPrivacy.getRoleCode() != null){
                RealmSecurityManager securityManager = (RealmSecurityManager) SecurityUtils.getSecurityManager();
                //获取MyShiroRealm 
                MyShiroRealm userRealm = (MyShiroRealm)securityManager.getRealms().iterator().next();
                //执行clearCache方法通过username清除缓存
                userRealm.clearCache(value);
            }
            redisUtil.hset(0, "data_"+value,"update", 3, 604800);
            return true;
        }else {
            return false;
        }
    }

Controller层

/**
* @author by. 不笑猫丶
* @date 2018年12月12日
*/          
@RestController
@RequestMapping("/easeApi")
public class controllerTest {

   /**
    * 用户登录
    * @param request
    * @param username
    * @return
    */    
    @RequestMapping("/auth/login")
    public Object login() {
        JSONObject jsonObject = new JSONObject();
        Subject subject = SecurityUtils.getSubject();
        subject.getSession().getId();
        String username = "123456";
        String password = "123456";
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try {
            subject.login(token);
            jsonObject.put("token", subject.getSession().getId());
            jsonObject.put("msg", "登录成功");
        } catch (IncorrectCredentialsException e) {
            jsonObject.put("msg", "密码错误");
        } catch (LockedAccountException e) {
            jsonObject.put("msg", "登录失败,该用户已被冻结");
        } catch (AuthenticationException e) {
            jsonObject.put("msg", "该用户不存在");
        } catch (Exception e) {
            e.printStackTrace();
        }

        return jsonObject;
    }


    @RequestMapping(value = "/authc/getUser")
    //此处为了方便用1,2,3来代表用户身份
    @RequiresRoles(value = {"2"},logical = Logical.OR)
    @RequiresPermissions("listen:high")
    public Object getUser(){
        return "Success";
    }

    @RequestMapping(value = "/authc/changeData")
    @RequiresRoles(value = "1")
    public Object changeData(){
        UserPrivacy userPrivacy = new UserPrivacy();
        userPrivacy.setRoleCode("2"); //修改用户身份
        String username = "123456";
        boolean bol = userDao.UpdateToPrivacy(userPrivacy, "username", username);
        return bol;
    }
}

使用postMan测试
首先测试请求url 127.0.0.1:8080/easeApi/authc/changeData,得到以下结果

{
    "msg": "未登录",
    "status": 3
}

因为在上面changeData()方法中设置了@RequiresRoles(value = "1"),而一开始用户并没有登录所以拒绝访问被保护的资源

然后我们再来测试登录操作,
请求url 127.0.0.1:8080/easeApi/auth/login,得到以下结果:

{
    "msg": "登录成功",
    "token": "b4c00d4f-8724-4ff3-88f6-ba216a06e269"
}

用户登录后测试getUser(),由于初始登录的用户只拥有 "1" 这个身份,用户以 "1" 的身份访问getUser()方法结果如下:

{
    "msg": "权限不足",
    "status": 3
}

结果显示权限不足,因为用户当前的拥有的身份为 "1" ,而请求此方法所需要的权限为 "2" 以上,所以请求被拦截。

此时在执行changeData()身份修改方法讲用户身份修改为"2"成功则返回true,得到结果如下

true

true既修改成功。此时用户拥有了 "2" ,这里再强调一下,在执行changeData()方法的时候Dao层中会执行这串代码,清除对应的username缓存信息。

   if(userPrivacy.getRoleCode() != null){
      RealmSecurityManager securityManager = (RealmSecurityManager) SecurityUtils.getSecurityManager();
      MyShiroRealm userRealm = (MyShiroRealm)securityManager.getRealms().iterator().next();
      userRealm.clearCache(username);
   }

因为缓存清除了,shiro会再次执行doGetAuthorizationInfo权限获取方法从数据库中获取用户权限信息,此时的数据为更新后的数据,从而实现用户身份和权限范围变更。
再次调用getUser(),得到以下结果

Success

大功告成!
文章没有贴出前端Vue的代码,可以使用axios发送AJAX请求即可完成同样的操作,注意做好Vue的跨域操作
在index.js中添加一下代码即可

proxyTable: {
      '/root': {
            // 测试环境
            target: 'http://127.0.0.1:8080/',  // 接口域名
            changeOrigin: true,  //是否跨域
            pathRewrite: {
                '^/root': ''   //需要rewrite重写的,
            }
        },
    },

以上就是SpringBoot+Shiro+Vue前后端分离开发的全部项目实现。本人小白难免出错,请谅解。

我是不笑猫丶
一只爱编程的猫。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容