SpringBoot + Shiro + Vue 前后端分離的權(quán)限管理

第一次寫簡(jiǎn)書(shū),打算用來(lái)做做開(kāi)發(fā)的筆記本吧擂涛,以下是Shiro的使用案例读串,Shiro在SpringBoot框架下的使用,前端使用的是Vue腳手架,在老項(xiàng)目上測(cè)試的權(quán)限管理爹土,所以只貼上了部分代碼甥雕。

首先Apache Shiro是一個(gè)強(qiáng)大且易用的Java安全框架,執(zhí)行身份驗(yàn)證、授權(quán)胀茵、密碼和會(huì)話管理社露。使用Shiro的易于理解的API,您可以快速、輕松地獲得任何應(yīng)用程序,從最小的移動(dòng)應(yīng)用程序到最大的網(wǎng)絡(luò)和企業(yè)應(yīng)用程序琼娘。

三個(gè)核心組件:Subject, SecurityManagerRealms.

Subject:代表了當(dāng)前用戶的安全操作峭弟,SecurityManager則管理所有用戶的安全操作。
SecurityManager:它是Shiro框架的核心脱拼,典型的Facade模式瞒瘸,Shiro通過(guò)SecurityManager來(lái)管理內(nèi)部組件實(shí)例,并通過(guò)它來(lái)提供安全管理的各種服務(wù)熄浓。
Realms: Realm充當(dāng)了Shiro與應(yīng)用安全數(shù)據(jù)間的“橋梁”或者“連接器”情臭。也就是說(shuō),當(dāng)對(duì)用戶執(zhí)行認(rèn)證(登錄)和授權(quán)(訪問(wèn)控制)驗(yàn)證時(shí)赌蔑,Shiro會(huì)從應(yīng)用配置的Realm中查找用戶及其權(quán)限信息俯在。

下面是在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和權(quán)限cache的保存

一般下,Shiro的授權(quán)方式為代碼方式和注解方式娃惯,

代碼方式

Subject subject = SecurityUtils.getSubject();
// 判斷用戶身份是否為管理員
if (subject.hasRole("admin")){ 
    //有權(quán)限do sth.
}else{
    //無(wú)權(quán)限do sth.
}
 // 判斷用戶是否擁有"listen:high"權(quán)限
// 注意一般權(quán)限的表達(dá)式為三段式或兩段式跷乐,以:號(hào)隔開(kāi) *:*:* 或者 *:* 這種格式
// 此處例子代碼匹配所有三段式和二段式的權(quán)限
if(subject.isPermitted("listen:high")){
    //有權(quán)限do sth.
}else{
    //無(wú)權(quán)限do sth.
}

注解方式

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

兩種方式各有優(yōu)劣,代碼方式可以在方法內(nèi)執(zhí)行一些復(fù)雜的邏輯業(yè)務(wù)趾浅,但是請(qǐng)求已經(jīng)進(jìn)入了程序方法中愕提,注解方式的好處是能將用戶請(qǐng)求攔截在方法外,請(qǐng)求不進(jìn)入方法皿哨,且使用較為方便浅侨,但是處理復(fù)雜的邏輯業(yè)務(wù)不是很靈活,需要自行捕捉拋出的異常類來(lái)實(shí)現(xiàn)業(yè)務(wù)邏輯。可按個(gè)人愛(ài)好選擇丐巫,該dome使用的注解方式來(lái)完成慢显。個(gè)人推薦注解方式。

以下是注解解析

@RequiresUser:當(dāng)前Subject必須是應(yīng)用的用戶,才能訪問(wèn)或調(diào)用被該注解標(biāo)注的類,實(shí)例,方法脖祈。
@RequiresRoles: 當(dāng)前Subject必須擁有所有指定的角色時(shí),才能訪問(wèn)被該注解標(biāo)注的方法刷晋。如果當(dāng)天Subject不同時(shí)擁有所有指定角色盖高,則方法不會(huì)執(zhí)行還會(huì)拋出AuthorizationException異常慎陵。
@RequiresPermissions:當(dāng)前Subject需要擁有某些特定的權(quán)限時(shí),才能執(zhí)行被該注解標(biāo)注的方法喻奥。如果當(dāng)前Subject不具有這樣的權(quán)限席纽,則方法不會(huì)被執(zhí)行。
@RequiresGuest:使用該注解標(biāo)注的類撞蚕,實(shí)例润梯,方法在訪問(wèn)或調(diào)用時(shí),當(dāng)前Subject可以是“gust”身份甥厦,不需要經(jīng)過(guò)認(rèn)證或者在原先的session中存在記錄纺铭。
@RequiresAuthentication:使用該注解標(biāo)注的類,實(shí)例刀疙,方法在訪問(wèn)或調(diào)用時(shí)舶赔,當(dāng)前Subject必須在當(dāng)前session中已經(jīng)過(guò)認(rèn)證。

該項(xiàng)目主要運(yùn)用@RequiresRoles@RequiresPermissions兩個(gè)注解谦秧。
示例

//用戶擁有user身份則進(jìn)入
@RequiresRoles("user")

//用戶擁有必須同時(shí)屬于user和admin身份則進(jìn)入
@RequiresRoles({"user","admin"})

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

@RequiresPermissions 用法類似不在演示

項(xiàng)目結(jié)構(gòu)為:

1544611423(1)_副本.png

Permission 權(quán)限實(shí)體
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 權(quán)身份實(shí)體
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>();
        //注意過(guò)濾器配置順序不能顛倒
        //配置退出過(guò)濾器竟纳,修改了默認(rèn)logou過(guò)濾器,清除相應(yīng)的緩存信息
        filterChainDefinitionMap.put("/easeApi/authc/user/change/Exit", "logout");
        // 配置需要攔截的鏈接
        filterChainDefinitionMap.put("/easeApi/authc/**", "ShiroAuthFilter");
        Map<String, Filter> filterMap = new LinkedHashMap<>();
        // 自定義的登錄過(guò)濾器
        filterMap.put("ShiroAuthFilter", new ShiroAuthFilter());
        shiroFilterFactoryBean.setFilters(filterMap);
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 憑證匹配器
     * (由于我們的密碼校驗(yàn)交給Shiro的SimpleAuthenticationInfo進(jìn)行處理了)
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:這里使用MD5算法;
        hashedCredentialsMatcher.setHashIterations(3);//散列的次數(shù)疚鲤,比如散列兩次锥累,相當(dāng)于 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());
        // 自定義緩存實(shí)現(xiàn) 使用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實(shí)現(xiàn)
     * @return
     */
    public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }

    /**
     * 通過(guò)redis RedisSessionDAO shiro sessionDao層的實(shí)現(xiàn) 
     * 使用shiro-redis插件
     */
    @Bean("redisSessionDAO")
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        return redisSessionDAO;
    }

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

MyShiroRealm類自定義的Realm石咬,用于身份認(rèn)證和權(quán)限獲取。

MyShiroRealm.java


/**
 * @author by. 不笑貓丶
 * @date 2018年12月12日
 * 自定義權(quán)限匹配
 */
public class MyShiroRealm extends AuthorizingRealm {

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

    @Autowired
    private UserService userService;

    @Autowired
    private UserDao userDao;

    //通過(guò)用戶名查找用戶擁有權(quán)限
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //獲取SimpleAuthenticationInfo中傳來(lái)的username
        String username = (String) principals.getPrimaryPrincipal();
        Map<String,Object> map = acquire.getHashMap("username", username);
        //得到用戶實(shí)體
        UserPrivacy userPrivacy = (UserPrivacy) userService.GetMsgformTable(3, map);
        //得到用戶身份的代碼
        String role = userPrivacy.getRoleCode();
        //通過(guò)身份代碼查找對(duì)應(yīng)的權(quán)限代碼的集合
        List<String> perCodeList = userDao.GetRoles(role);
        //權(quán)限代碼的集合查找對(duì)應(yīng)的權(quán)限表達(dá)式 如 "listen:high"
        List<String> permission = userDao.GetPermission(perCodeList);
        //添加用戶權(quán)限
        authorizationInfo.addStringPermissions(permission);
        //添加用戶角色
        authorizationInfo.addRole(role);
        return authorizationInfo;
    }

    //身份認(rèn)證
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        logger.info("成功進(jìn)入ShiroRealm認(rèn)證器");
        //從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) {
            // 用戶存在卖哎,檢查用戶狀態(tài) code = 0 為保護(hù)狀態(tài)
            Result result = userService.findNumcheck(username);
            int CODE = result.getStatus();
            if(CODE == 0){
                throw new LockedAccountException();
            } else {
                // 用戶存在鬼悠,且不為保護(hù)狀態(tài),對(duì)密碼進(jìn)行md5轉(zhuǎn)碼并與前端傳來(lái)的password比對(duì)
                String password = EncryUtils.getMD5(userPrivacy.getPassword().getBytes());
                //實(shí)際項(xiàng)目中亏娜,這里可以根據(jù)實(shí)際情況做緩存焕窝,如果不做,Shiro自己也是有時(shí)間間隔機(jī)制维贺,2分鐘內(nèi)不會(huì)重復(fù)執(zhí)行該方法
                authenticationInfo  = new SimpleAuthenticationInfo(username,password,getName());//getName() realm name
            }
        } else {
            return null;
        }
        return authenticationInfo;
    }


    /**
     * 通過(guò)用戶名清除緩存
     */
    public void clearCache(String username) {
        System.out.println("調(diào)用cache清理操作");
        PrincipalCollection principals = new SimplePrincipalCollection(
                new UserPrivacy(username), getName());
        clearCache(principals);
    }

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

AuthException類異常統(tǒng)一處理它掂,因?yàn)轫?xiàng)目是前后端分離的項(xiàng)目,異常捕捉后不能重定向溯泣,只返回json數(shù)組虐秋。此處只捕捉了UnauthorizedException異常,其他異忱伲可自行實(shí)現(xiàn)客给。

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)//處理訪問(wèn)方法時(shí)權(quán)限不足問(wèn)題
    public void AuthcErrorHandler(HttpServletResponse res, Exception e) throws IOException {
        logger.info("拋出UnauthorizedException權(quán)限異常");
        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", "權(quán)限不足");
        writer.write(JSON.toJSONString(map));
        writer.close();
    }
}

ShiroAuthFilter自定義的 Shiro 登錄認(rèn)證過(guò)濾器,繼承FormAuthenticationFilter類攔截權(quán)限不足的請(qǐng)求肢簿,并返回JSON數(shù)據(jù)

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過(guò)濾器,該項(xiàng)目中用于基于cookie的自動(dòng)登錄場(chǎng)景桩引,在session過(guò)期或者關(guān)閉瀏覽器時(shí)缎讼,session失效或不存在時(shí),在用戶cookie尚存的情況下坑匠,再次請(qǐng)求被保護(hù)的資源時(shí)血崭,重新獲取認(rèn)證,在Vue單頁(yè)面的情況下無(wú)需刷新頁(yè)面重新獲取JSESSIONID笛辟,該類在JSESSIONID不存在時(shí)會(huì)自動(dòng)生成cookie分配到前端頁(yè)面中功氨。

ShiroAutoAuthFilter.java

/**
 * @author by. 不笑貓丶
 * @date 2018年12月12日
 * /easeApi/authc/ 為受保護(hù)資源的路徑
 */
@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被調(diào)用");
        HttpServletRequest request = (HttpServletRequest) arg0;
        HttpServletResponse response = (HttpServletResponse) arg1;
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        //判斷用于自動(dòng)登錄的cookie是否存在
        Cookie UIDcookie = com.music.Tools.CookieTool.getCookieByName(request, "UID");
        //用戶給request添加header信息 添加Authorization頭,保證此次請(qǐng)求不被攔截手幢,實(shí)現(xiàn)不刷新頁(yè)面自動(dòng)認(rèn)證的關(guān)鍵
        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);
            //檢查帳號(hào)密碼是否有效
            boolean empty = userDao.QueryforPrivacy(map);
            if (!empty) {
                //攔截返回提示
                authcReq(response);
                return;
            }
            //獲取當(dāng)前JSESSIONID捷凄,判斷是否存在
            Cookie SUID = CookieTool.getCookieByName(request, SESSIONID);
            if(SUID == null) {
                logger.info("JSESSIONID為空直接執(zhí)行登錄操作");
                //JSESSIONID為空直接執(zhí)行登錄操作,并設(shè)置JSESSIONID至前端,實(shí)現(xiàn)不刷新自動(dòng)認(rèn)證
                ShiroTool.authLogin(httpReq, response, username, password);
                arg2.doFilter(httpReq, response);
                return;
            }
            //判斷 JSESSIONID 是否存在redis中 
            boolean bol = redisUtil.hasKey(3, "shiro:session:"+SUID.getValue());
            //redis檢測(cè)JSESSIONID結(jié)果為若為false則調(diào)用登錄操作
            logger.info("redis檢測(cè)JSESSIONID結(jié)果為  :"+bol);
            if(!bol){
                //不存在執(zhí)行登錄操作
                ShiroTool.authLogin(httpReq, response, username, password);
            } else {
                // 存在围来,判斷是否獲得認(rèn)證
                logger.info("JSESSIONID存在跺涤,驗(yàn)證是否已認(rèn)證");
                boolean auth = ShiroTool.isAuthenticated(SUID.getValue(), request, response);
                if (!auth){
                    // 否,獲取認(rèn)證
                    logger.info("JSESSIONID未認(rèn)證监透,執(zhí)行登錄操作");
                  
                    ShiroTool.authLogin(httpReq, response, username, password);
                }else {
                    // 是桶错,打印消息
                    logger.info("JSESSIONID已認(rèn)證");
                }
            }
        }
        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工具類,包括判斷是否認(rèn)證的方法和獲取緩存用戶信息胀蛮,和login+設(shè)置cookie的操作
ShiroTool.java

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

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

    /**
     * 驗(yàn)證是否登陸
     */
    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  執(zhí)行登錄操作
        logger.info("Shiro執(zhí)行登錄操作");
        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);
        //如果請(qǐng)求頭中有 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管理器檢測(cè)到AUTHORIZATION token 使用此token作為Session");
            return id;
        } else {
            //否則按默認(rèn)規(guī)則從cookie取sessionId
            return super.getSessionId(request, response);
        }
    }
}

以上Shiro的配置和一些基本使用已經(jīng)全部搭建好院刁,可以開(kāi)始測(cè)試了。

測(cè)試之前粪狼,說(shuō)一點(diǎn)Dao層需注意的一點(diǎn)退腥,若要實(shí)現(xiàn)動(dòng)態(tài)的修改用戶權(quán)限范圍或者身份,需要更改身份時(shí)清除對(duì)應(yīng)的認(rèn)證緩存再榄,否則身份修改后狡刘,緩存還在的情況下,shiro會(huì)讀取緩存中之前的身份信息困鸥,所以可前端請(qǐng)求修改User身份時(shí)清除redis中的cache權(quán)限緩存信息嗅蔬,則下次用戶訪問(wèn)受保護(hù)的資源時(shí)會(huì)再次調(diào)用權(quán)限獲取方法 doGetAuthorizationInfo 從數(shù)據(jù)庫(kù)中獲取相應(yīng)權(quán)限和身份,此時(shí)獲取的數(shù)據(jù)為修改后的數(shù)據(jù)疾就,可以實(shí)現(xiàn)簡(jiǎn)單的用戶權(quán)限變更操作澜术。

部分Dao層代碼

    /**
     * 根據(jù) T 實(shí)體修改與column字段相匹配的數(shù)據(jù)信息
     * @param userPrivacy 實(shí)體類
     * @param column 字段名
     * @param value 字段對(duì)應(yīng)值
     * @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();
                //執(zhí)行clearCache方法通過(guò)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", "密碼錯(cuò)誤");
        } catch (LockedAccountException e) {
            jsonObject.put("msg", "登錄失敗,該用戶已被凍結(jié)");
        } catch (AuthenticationException e) {
            jsonObject.put("msg", "該用戶不存在");
        } catch (Exception e) {
            e.printStackTrace();
        }

        return jsonObject;
    }


    @RequestMapping(value = "/authc/getUser")
    //此處為了方便用1,2,3來(lái)代表用戶身份
    @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測(cè)試
首先測(cè)試請(qǐng)求url 127.0.0.1:8080/easeApi/authc/changeData,得到以下結(jié)果

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

因?yàn)樵谏厦鎐hangeData()方法中設(shè)置了@RequiresRoles(value = "1")猬腰,而一開(kāi)始用戶并沒(méi)有登錄所以拒絕訪問(wèn)被保護(hù)的資源

然后我們?cè)賮?lái)測(cè)試登錄操作,
請(qǐng)求url 127.0.0.1:8080/easeApi/auth/login,得到以下結(jié)果:

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

用戶登錄后測(cè)試getUser(),由于初始登錄的用戶只擁有 "1" 這個(gè)身份瘪板,用戶以 "1" 的身份訪問(wèn)getUser()方法結(jié)果如下:

{
    "msg": "權(quán)限不足",
    "status": 3
}

結(jié)果顯示權(quán)限不足,因?yàn)橛脩舢?dāng)前的擁有的身份為 "1" 漆诽,而請(qǐng)求此方法所需要的權(quán)限為 "2" 以上侮攀,所以請(qǐng)求被攔截锣枝。

此時(shí)在執(zhí)行changeData()身份修改方法講用戶身份修改為"2"成功則返回true,得到結(jié)果如下

true

true既修改成功兰英。此時(shí)用戶擁有了 "2" 撇叁,這里再?gòu)?qiáng)調(diào)一下,在執(zhí)行changeData()方法的時(shí)候Dao層中會(huì)執(zhí)行這串代碼畦贸,清除對(duì)應(yīng)的username緩存信息陨闹。

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

因?yàn)榫彺媲宄耍瑂hiro會(huì)再次執(zhí)行doGetAuthorizationInfo權(quán)限獲取方法從數(shù)據(jù)庫(kù)中獲取用戶權(quán)限信息薄坏,此時(shí)的數(shù)據(jù)為更新后的數(shù)據(jù)趋厉,從而實(shí)現(xiàn)用戶身份和權(quán)限范圍變更。
再次調(diào)用getUser()胶坠,得到以下結(jié)果

Success

大功告成君账!
文章沒(méi)有貼出前端Vue的代碼,可以使用axios發(fā)送AJAX請(qǐng)求即可完成同樣的操作沈善,注意做好Vue的跨域操作
在index.js中添加一下代碼即可

proxyTable: {
      '/root': {
            // 測(cè)試環(huán)境
            target: 'http://127.0.0.1:8080/',  // 接口域名
            changeOrigin: true,  //是否跨域
            pathRewrite: {
                '^/root': ''   //需要rewrite重寫的,
            }
        },
    },

以上就是SpringBoot+Shiro+Vue前后端分離開(kāi)發(fā)的全部項(xiàng)目實(shí)現(xiàn)乡数。本人小白難免出錯(cuò),請(qǐng)諒解闻牡。

我是不笑貓丶
一只愛(ài)編程的貓净赴。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市罩润,隨后出現(xiàn)的幾起案子玖翅,更是在濱河造成了極大的恐慌,老刑警劉巖割以,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件金度,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡拳球,警方通過(guò)查閱死者的電腦和手機(jī)审姓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門珍特,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)祝峻,“玉大人,你說(shuō)我怎么就攤上這事扎筒±痴遥” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵嗜桌,是天一觀的道長(zhǎng)奥溺。 經(jīng)常有香客問(wèn)我,道長(zhǎng)骨宠,這世上最難降的妖魔是什么浮定? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任相满,我火速辦了婚禮,結(jié)果婚禮上桦卒,老公的妹妹穿的比我還像新娘立美。我一直安慰自己,他們只是感情好方灾,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布建蹄。 她就那樣靜靜地躺著,像睡著了一般裕偿。 火紅的嫁衣襯著肌膚如雪洞慎。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,929評(píng)論 1 290
  • 那天嘿棘,我揣著相機(jī)與錄音劲腿,去河邊找鬼。 笑死蔫巩,一個(gè)胖子當(dāng)著我的面吹牛谆棱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播圆仔,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼垃瞧,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了坪郭?” 一聲冷哼從身側(cè)響起个从,我...
    開(kāi)封第一講書(shū)人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎歪沃,沒(méi)想到半個(gè)月后嗦锐,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡沪曙,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年奕污,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片液走。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡碳默,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出缘眶,到底是詐尸還是另有隱情嘱根,我是刑警寧澤,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布巷懈,位于F島的核電站该抒,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏顶燕。R本人自食惡果不足惜凑保,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一冈爹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧欧引,春花似錦犯助、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至癌蓖,卻和暖如春瞬哼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背租副。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工坐慰, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人用僧。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓结胀,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親责循。 傳聞我的和親對(duì)象是個(gè)殘疾皇子糟港,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容

  • 前言 上一章主要對(duì)Shiro功能,運(yùn)行原理院仿,架構(gòu)設(shè)計(jì)進(jìn)行了介紹秸抚,這一章我們主要學(xué)習(xí)Shiro的身份驗(yàn)證。本章的代碼...
    卑微幻想家閱讀 2,057評(píng)論 0 12
  • 文/小宋老師 -01- 幾年前吭敢,我一邊讀研究生,一邊利用業(yè)余時(shí)間在新東方教英語(yǔ)暮芭。 為了賺更多的學(xué)費(fèi)鹿驼,順便減輕家里的...
    小宋老師的幸福課閱讀 2,343評(píng)論 38 72
  • 人類有三個(gè)大腦蠢沿?是的伸头,爬行動(dòng)物腦匾效、古哺乳動(dòng)物腦、理性腦三位一體恤磷,共同構(gòu)成了完整的人類大腦面哼。小小的顱腔內(nèi)存留的是人類...
    明瑩閱讀 15,486評(píng)論 0 9
  • 小青耳邊又出現(xiàn)了老公的嘮叨野宜,比如早晨起床后要把被子掀起來(lái),否則床容易受潮魔策。有一兩次孩子尿床了匈子,小青走得匆忙,沒(méi)有把...
    江小昨閱讀 397評(píng)論 0 1
  • 看到題目后我就焦慮了闯袒!別說(shuō)過(guò)去一年虎敦,就我生命里許許多多的一年都是虛幻而過(guò)。 什么算是成就政敢?我認(rèn)為吃飽喝好其徙,身體健康...
    石果閱讀 162評(píng)論 0 0