亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

基于shiro的改造集成真正支持restful請求

nodejh / 3127人閱讀

摘要:基于的改造集成真正支持請求這個模塊分離至項目權限管理系統(tǒng)與前后端分離實踐權限管理系統(tǒng)與前后端分離實踐,感覺那樣太長了找不到重點,分離出來要好點?;诮巧氖跈嗄P椭?,角色所擁有的資源形式為。最后項目實現(xiàn)了基于的動態(tài)權限認證。

基于shiro的改造集成真正支持restful請求

這個模塊分離至項目[api權限管理系統(tǒng)與前后端分離實踐]api權限管理系統(tǒng)與前后端分離實踐,感覺那樣太長了找不到重點,分離出來要好點。

首先說明設計的這個安全體系是是RBAC(基于角色的權限訪問控制)授權模型,即用戶--角色--資源,用戶不直接和權限打交道,角色擁有資源,用戶擁有這個角色就有權使用角色所用戶的資源。所有這里沒有權限一說,簽發(fā)jwt里面也就只有用戶所擁有的角色而沒有權限。

為啥說是真正的restful風格集成,雖說shiro對rest不友好但他本身是有支持rest集成的filter--HttpMethodPermissionFilter,這個shiro rest的 風格攔截器,會自動根據(jù)請求方法構建權限字符串( GET=read,POST=create,PUT=update,DELETE=delete)構建權限字符串;eg: /users=rest[user] , 會 自動拼接出user:read,user:create,user:update,user:delete”權限字符串進行權限匹配(所有都得匹配,isPermittedAll)。

但是這樣感覺不利于基于jwt的角色的權限控制,在細粒度上驗權url(即支持get,post,delete鑒別)就更沒法了(個人見解)。打個比方:我們對一個用戶簽發(fā)的jwt寫入角色列(role_admin,role_customer)。對不同request請求:url="api/resource/",httpMethod="GET",url="api/resource",httpMethod="POST",在基于角色-資源的授權模型中,這兩個url相同的請求對HttpMethodPermissionFilter是一種請求,用戶對應的角色擁有的資源url="api/resource",只要請求的url是"api/resource",不論它的請求方式是什么,都會判定通過這個請求,這在restful風格的api中肯定是不可取的,對同一資源有些角色可能只要查詢的權限而沒有修改增加的權限。

可能會說在jwt中再增加權限列就好了嘛,但是在基于用戶-資源的授權模型中,雖然能判別是不同的請求,但是太麻煩了,對每個資源我們都要設計對應的權限列然后再塞入到jwt中,對每個用戶都要多帶帶授權資源這也是不可取的。

對shiro的改造這里自定義了一些規(guī)則:
shiro過濾器鏈的url=url+"=="+httpMethod
eg:對于url="api/resource/",httpMethod="GET"的資源,其拼接出來的過濾器鏈匹配url=api/resource==GET
這樣對相同的url而不同的訪問方式,會判定為不同的資源,即資源不再簡單是url,而是url和httpMethod的組合?;诮巧氖跈嗄P椭?,角色所擁有的資源形式為url+"=="+httpMethod
這里改變了過濾器的過濾匹配url規(guī)則,重寫PathMatchingFilterChainResolver的getChain方法,增加對上述規(guī)則的url的支持。

/* *
 * @Author tomsun28
 * @Description 
 * @Date 21:12 2018/4/20
 */
public class RestPathMatchingFilterChainResolver extends PathMatchingFilterChainResolver {

    private static final Logger LOGGER = LoggerFactory.getLogger(RestPathMatchingFilterChainResolver.class);

    public RestPathMatchingFilterChainResolver() {
        super();
    }

    public RestPathMatchingFilterChainResolver(FilterConfig filterConfig) {
        super(filterConfig);
    }

    /* *
     * @Description 重寫filterChain匹配
     * @Param [request, response, originalChain]
     * @Return javax.servlet.FilterChain
     */
    @Override
    public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
        FilterChainManager filterChainManager = this.getFilterChainManager();
        if (!filterChainManager.hasChains()) {
            return null;
        } else {
            String requestURI = this.getPathWithinApplication(request);
            Iterator var6 = filterChainManager.getChainNames().iterator();

            String pathPattern;
            boolean flag = true;
            String[] strings = null;
            do {
                if (!var6.hasNext()) {
                    return null;
                }

                pathPattern = (String)var6.next();

                strings = pathPattern.split("==");
                if (strings.length == 2) {
                    // 分割出url+httpMethod,判斷httpMethod和request請求的method是否一致,不一致直接false
                    if (WebUtils.toHttp(request).getMethod().toUpperCase().equals(strings[1].toUpperCase())) {
                        flag = false;
                    } else {
                        flag = true;
                    }
                } else {
                    flag = false;
                }
                pathPattern = strings[0];
            } while(!this.pathMatches(pathPattern, requestURI) || flag);

            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace("Matched path pattern [" + pathPattern + "] for requestURI [" + requestURI + "].  Utilizing corresponding filter chain...");
            }
            if (strings.length == 2) {
                pathPattern = pathPattern.concat("==").concat(WebUtils.toHttp(request).getMethod().toUpperCase());
            }

            return filterChainManager.proxy(originalChain, pathPattern);
        }
    }

}

重寫PathMatchingFilter的路徑匹配方法pathsMatch(),加入httpMethod支持。

/* *
 * @Author tomsun28
 * @Description 重寫過濾鏈路徑匹配規(guī)則,增加REST風格post,get.delete,put..支持
 * @Date 23:37 2018/4/19
 */
public abstract class BPathMatchingFilter extends PathMatchingFilter {

    public BPathMatchingFilter() {

    }
    /* *
     * @Description 重寫URL匹配  加入httpMethod支持
     * @Param [path, request]
     * @Return boolean
     */
    @Override
    protected boolean pathsMatch(String path, ServletRequest request) {
        String requestURI = this.getPathWithinApplication(request);
        // path: url==method eg: http://api/menu==GET   需要解析出path中的url和httpMethod
        String[] strings = path.split("==");
        if (strings.length <= 1) {
            // 分割出來只有URL
            return this.pathsMatch(strings[0], requestURI);
        } else {
            // 分割出url+httpMethod,判斷httpMethod和request請求的method是否一致,不一致直接false
            String httpMethod = WebUtils.toHttp(request).getMethod().toUpperCase();
            return httpMethod.equals(strings[1].toUpperCase()) && this.pathsMatch(strings[0], requestURI);
        }
    }
}

這樣增加httpMethod的改造就完成了,重寫ShiroFilterFactoryBean使其使用改造后的chainResolver:RestPathMatchingFilterChainResolver

/* *
 * @Author tomsun28
 * @Description rest支持的shiroFilterFactoryBean
 * @Date 21:35 2018/4/20
 */
public class RestShiroFilterFactoryBean extends ShiroFilterFactoryBean {

    private static final Logger LOGGER = LoggerFactory.getLogger(RestShiroFilterFactoryBean.class);

    public RestShiroFilterFactoryBean() {
        super();
    }

    @Override
    protected AbstractShiroFilter createInstance() throws Exception {
        LOGGER.debug("Creating Shiro Filter instance.");
        SecurityManager securityManager = this.getSecurityManager();
        String msg;
        if (securityManager == null) {
            msg = "SecurityManager property must be set.";
            throw new BeanInitializationException(msg);
        } else if (!(securityManager instanceof WebSecurityManager)) {
            msg = "The security manager does not implement the WebSecurityManager interface.";
            throw new BeanInitializationException(msg);
        } else {
            FilterChainManager manager = this.createFilterChainManager();
            RestPathMatchingFilterChainResolver chainResolver = new RestPathMatchingFilterChainResolver();
            chainResolver.setFilterChainManager(manager);
            return new RestShiroFilterFactoryBean.SpringShiroFilter((WebSecurityManager)securityManager, chainResolver);
        }
    }

    private static final class SpringShiroFilter extends AbstractShiroFilter {
        protected SpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) {
            if (webSecurityManager == null) {
                throw new IllegalArgumentException("WebSecurityManager property cannot be null.");
            } else {
                this.setSecurityManager(webSecurityManager);
                if (resolver != null) {
                    this.setFilterChainResolver(resolver);
                }

            }
        }
    }
}

上面是一些核心的代碼片段,更多請看項目代碼。

對用戶賬戶登錄注冊的過濾filter:PasswordFilter

/* *
 * @Author tomsun28
 * @Description 基于 用戶名密碼 的認證過濾器
 * @Date 20:18 2018/2/10
 */
public class PasswordFilter extends AccessControlFilter {

    private static final Logger LOGGER = LoggerFactory.getLogger(PasswordFilter.class);

    private StringRedisTemplate redisTemplate;

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

        Subject subject = getSubject(request,response);
        // 如果其已經(jīng)登錄,再此發(fā)送登錄請求
        if(null != subject && subject.isAuthenticated()){
            return true;
        }
        //  拒絕,統(tǒng)一交給 onAccessDenied 處理
        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {

        // 判斷若為獲取登錄注冊加密動態(tài)秘鑰請求
        if (isPasswordTokenGet(request)) {
            //動態(tài)生成秘鑰,redis存儲秘鑰供之后秘鑰驗證使用,設置有效期5秒用完即丟棄
            String tokenKey = CommonUtil.getRandomString(16);
            try {
                redisTemplate.opsForValue().set("PASSWORD_TOKEN_KEY_"+request.getRemoteAddr().toUpperCase(),tokenKey,5, TimeUnit.SECONDS);
                // 動態(tài)秘鑰response返回給前端
                Message message = new Message();
                message.ok(1000,"issued tokenKey success")
                        .addData("tokenKey",tokenKey);
                RequestResponseUtil.responseWrite(JSON.toJSONString(message),response);

            }catch (Exception e) {
                LOGGER.warn(e.getMessage(),e);
                // 動態(tài)秘鑰response返回給前端
                Message message = new Message();
                message.ok(1000,"issued tokenKey fail");
                RequestResponseUtil.responseWrite(JSON.toJSONString(message),response);
            }
            return false;
        }

        // 判斷是否是登錄請求
        if(isPasswordLoginPost(request)){
            AuthenticationToken authenticationToken = createPasswordToken(request);
            Subject subject = getSubject(request,response);
            try {
                subject.login(authenticationToken);
                //登錄認證成功,進入請求派發(fā)json web token url資源內
                return true;
            }catch (AuthenticationException e) {
                LOGGER.warn(authenticationToken.getPrincipal()+"::"+e.getMessage(),e);
                // 返回response告訴客戶端認證失敗
                Message message = new Message().error(1002,"login fail");
                RequestResponseUtil.responseWrite(JSON.toJSONString(message),response);
                return false;
            }catch (Exception e) {
                LOGGER.error(e.getMessage(),e);
                // 返回response告訴客戶端認證失敗
                Message message = new Message().error(1002,"login fail");
                RequestResponseUtil.responseWrite(JSON.toJSONString(message),response);
                return false;
            }
        }
        // 判斷是否為注冊請求,若是通過過濾鏈進入controller注冊
        if (isAccountRegisterPost(request)) {
            return true;
        }
        // 之后添加對賬戶的找回等

        // response 告知無效請求
        Message message = new Message().error(1111,"error request");
        RequestResponseUtil.responseWrite(JSON.toJSONString(message),response);
        return false;
    }

    private boolean isPasswordTokenGet(ServletRequest request) {
//        String tokenKey = request.getParameter("tokenKey");
        String tokenKey = RequestResponseUtil.getParameter(request,"tokenKey");

        return (request instanceof HttpServletRequest)
                && ((HttpServletRequest) request).getMethod().toUpperCase().equals("GET")
                && null != tokenKey && "get".equals(tokenKey);
    }

    private boolean isPasswordLoginPost(ServletRequest request) {
//        String password = request.getParameter("password");
//        String timestamp = request.getParameter("timestamp");
//        String methodName = request.getParameter("methodName");
//        String appId = request.getParameter("appId");
        Map map = RequestResponseUtil.getRequestParameters(request);
        String password = map.get("password");
        String timestamp = map.get("timestamp");
        String methodName = map.get("methodName");
        String appId = map.get("appId");
        return (request instanceof HttpServletRequest)
                && ((HttpServletRequest) request).getMethod().toUpperCase().equals("POST")
                && null != password
                && null != timestamp
                && null != methodName
                && null != appId
                && methodName.equals("login");
    }

    private boolean isAccountRegisterPost(ServletRequest request) {
//        String uid = request.getParameter("uid");
//        String methodName = request.getParameter("methodName");
//        String username = request.getParameter("username");
//        String password = request.getParameter("password");
        Map map = RequestResponseUtil.getRequestParameters(request);
        String uid = map.get("uid");
        String username = map.get("username");
        String methodName = map.get("methodName");
        String password = map.get("password");
        return (request instanceof HttpServletRequest)
                && ((HttpServletRequest) request).getMethod().toUpperCase().equals("POST")
                && null != username
                && null != password
                && null != methodName
                && null != uid
                && methodName.equals("register");
    }

    private AuthenticationToken createPasswordToken(ServletRequest request) {

//        String appId = request.getParameter("appId");
//        String password = request.getParameter("password");
//        String timestamp = request.getParameter("timestamp");
        Map map = RequestResponseUtil.getRequestParameters(request);
        String appId = map.get("appId");
        String timestamp = map.get("timestamp");
        String password = map.get("password");
        String host = request.getRemoteAddr();
        String tokenKey = redisTemplate.opsForValue().get("PASSWORD_TOKEN_KEY_"+host.toUpperCase());
        return new PasswordToken(appId,password,timestamp,host,tokenKey);
    }

    public void setRedisTemplate(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

}

支持restful風格的jwt鑒權filter:BJwtFilter

/* *
 * @Author tomsun28
 * @Description 支持restful url 的過濾鏈  JWT json web token 過濾器,無狀態(tài)驗證
 * @Date 0:04 2018/4/20
 */
public class BJwtFilter extends BPathMatchingFilter {

    private static final Logger LOGGER = LoggerFactory.getLogger(BJwtFilter.class);


    private StringRedisTemplate redisTemplate;
    private AccountService accountService;

    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object mappedValue) throws Exception {
        Subject subject = getSubject(servletRequest,servletResponse);
        // 判斷是否為JWT認證請求
        if ((null == subject || !subject.isAuthenticated()) && isJwtSubmission(servletRequest)) {
            AuthenticationToken token = createJwtToken(servletRequest);
            try {
                subject.login(token);
//                return this.checkRoles(subject,mappedValue) && this.checkPerms(subject,mappedValue);
                return this.checkRoles(subject,mappedValue);
            }catch (AuthenticationException e) {
                LOGGER.info(e.getMessage(),e);
                // 如果是JWT過期
                if (e.getMessage().equals("expiredJwt")) {
                    // 這里初始方案先拋出令牌過期,之后設計為在Redis中查詢當前appId對應令牌,其設置的過期時間是JWT的兩倍,此作為JWT的refresh時間
                    // 當JWT的有效時間過期后,查詢其refresh時間,refresh時間有效即重新派發(fā)新的JWT給客戶端,
                    // refresh也過期則告知客戶端JWT時間過期重新認證

                    // 當存儲在redis的JWT沒有過期,即refresh time 沒有過期
                    String appId = WebUtils.toHttp(servletRequest).getHeader("appId");
                    String jwt = WebUtils.toHttp(servletRequest).getHeader("authorization");
                    String refreshJwt = redisTemplate.opsForValue().get("JWT-SESSION-"+appId);
                    if (null != refreshJwt && refreshJwt.equals(jwt)) {
                        // 重新申請新的JWT
                        // 根據(jù)appId獲取其對應所擁有的角色(這里設計為角色對應資源,沒有權限對應資源)
                        String roles = accountService.loadAccountRole(appId);
                        long refreshPeriodTime = 36000L;  //seconds為單位,10 hours
                        String newJwt = JsonWebTokenUtil.issueJWT(UUID.randomUUID().toString(),appId,
                                "token-server",refreshPeriodTime >> 2,roles,null, SignatureAlgorithm.HS512);
                        // 將簽發(fā)的JWT存儲到Redis: {JWT-SESSION-{appID} , jwt}
                        redisTemplate.opsForValue().set("JWT-SESSION-"+appId,newJwt,refreshPeriodTime, TimeUnit.SECONDS);
                        Message message = new Message().ok(1005,"new jwt").addData("jwt",newJwt);
                        RequestResponseUtil.responseWrite(JSON.toJSONString(message),servletResponse);
                        return false;
                    }else {
                        // jwt時間失效過期,jwt refresh time失效 返回jwt過期客戶端重新登錄
                        Message message = new Message().error(1006,"expired jwt");
                        RequestResponseUtil.responseWrite(JSON.toJSONString(message),servletResponse);
                        return false;
                    }

                }
                // 其他的判斷為JWT錯誤無效
                Message message = new Message().error(1007,"error Jwt");
                RequestResponseUtil.responseWrite(JSON.toJSONString(message),servletResponse);
                return false;

            }catch (Exception e) {
                // 其他錯誤
                LOGGER.warn(servletRequest.getRemoteAddr()+"JWT認證"+e.getMessage(),e);
                // 告知客戶端JWT錯誤1005,需重新登錄申請jwt
                Message message = new Message().error(1007,"error jwt");
                RequestResponseUtil.responseWrite(JSON.toJSONString(message),servletResponse);
                return false;
            }
        }else {
            // 請求未攜帶jwt 判斷為無效請求
            Message message = new Message().error(1111,"error request");
            RequestResponseUtil.responseWrite(JSON.toJSONString(message),servletResponse);
            return false;
        }
    }

    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        Subject subject = getSubject(servletRequest,servletResponse);

        // 未認證的情況
        if (null == subject || !subject.isAuthenticated()) {
            // 告知客戶端JWT認證失敗需跳轉到登錄頁面
            Message message = new Message().error(1006,"error jwt");
            RequestResponseUtil.responseWrite(JSON.toJSONString(message),servletResponse);
        }else {
            //  已經(jīng)認證但未授權的情況
            // 告知客戶端JWT沒有權限訪問此資源
            Message message = new Message().error(1008,"no permission");
            RequestResponseUtil.responseWrite(JSON.toJSONString(message),servletResponse);
        }
        // 過濾鏈終止
        return false;
    }

    private boolean isJwtSubmission(ServletRequest request) {

        String jwt = RequestResponseUtil.getHeader(request,"authorization");
        String appId = RequestResponseUtil.getHeader(request,"appId");
        return (request instanceof HttpServletRequest)
                && !StringUtils.isEmpty(jwt)
                && !StringUtils.isEmpty(appId);
    }

    private AuthenticationToken createJwtToken(ServletRequest request) {

        Map maps = RequestResponseUtil.getRequestHeaders(request);
        String appId = maps.get("appId");
        String ipHost = request.getRemoteAddr();
        String jwt = maps.get("authorization");
        String deviceInfo = maps.get("deviceInfo");

        return new JwtToken(ipHost,deviceInfo,jwt,appId);
    }

    // 驗證當前用戶是否屬于mappedValue任意一個角色
    private boolean checkRoles(Subject subject, Object mappedValue){
        String[] rolesArray = (String[]) mappedValue;
        return rolesArray == null || rolesArray.length == 0 || Stream.of(rolesArray).anyMatch(role -> subject.hasRole(role.trim()));
    }

    // 驗證當前用戶是否擁有mappedValue任意一個權限
    private boolean checkPerms(Subject subject, Object mappedValue){
        String[] perms = (String[]) mappedValue;
        boolean isPermitted = true;
        if (perms != null && perms.length > 0) {
            if (perms.length == 1) {
                if (!subject.isPermitted(perms[0])) {
                    isPermitted = false;
                }
            } else {
                if (!subject.isPermittedAll(perms)) {
                    isPermitted = false;
                }
            }
        }
        return isPermitted;
    }

    public void setRedisTemplate(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }


    public void setAccountService(AccountService accountService) {
        this.accountService = accountService;
    }
}

realm數(shù)據(jù)源,數(shù)據(jù)提供service,匹配matchs,自定義token,spring集成shiro配置等其他詳見項目代碼。
最后項目實現(xiàn)了基于jwt的動態(tài)restful api權限認證。


效果展示




github:
bootshiro
usthe

碼云:
bootshiro
usthe



分享一波阿里云代金券快速上云

轉載請注明 from tomsun28

文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉載請注明本文地址:http://www.ezyhdfw.cn/yun/69183.html

相關文章

  • Spring Security

    摘要:框架具有輕便,開源的優(yōu)點,所以本譯見構建用戶管理微服務五使用令牌和來實現(xiàn)身份驗證往期譯見系列文章在賬號分享中持續(xù)連載,敬請查看在往期譯見系列的文章中,我們已經(jīng)建立了業(yè)務邏輯數(shù)據(jù)訪問層和前端控制器但是忽略了對身份進行驗證。 重拾后端之Spring Boot(四):使用JWT和Spring Security保護REST API 重拾后端之Spring Boot(一):REST API的搭建...

    keelii 評論0 收藏0
  • api權限管理系統(tǒng)與前后端分離實踐

    摘要:自己在前后端分離上的實踐要想實現(xiàn)完整的前后端分離,安全這塊是繞不開的,這個系統(tǒng)主要功能就是動態(tài)管理,這次實踐包含兩個模塊基于搭建的權限管理系統(tǒng)后臺編寫的前端管理。 自己在前后端分離上的實踐 要想實現(xiàn)完整的前后端分離,安全這塊是繞不開的,這個系統(tǒng)主要功能就是動態(tài)restful api管理,這次實踐包含兩個模塊,基于springBoot + shiro搭建的權限管理系統(tǒng)后臺bootshir...

    bawn 評論0 收藏0
  • api權限管理系統(tǒng)與前后端分離實踐

    摘要:自己在前后端分離上的實踐要想實現(xiàn)完整的前后端分離,安全這塊是繞不開的,這個系統(tǒng)主要功能就是動態(tài)管理,這次實踐包含兩個模塊基于搭建的權限管理系統(tǒng)后臺編寫的前端管理。 自己在前后端分離上的實踐 要想實現(xiàn)完整的前后端分離,安全這塊是繞不開的,這個系統(tǒng)主要功能就是動態(tài)restful api管理,這次實踐包含兩個模塊,基于springBoot + shiro搭建的權限管理系統(tǒng)后臺bootshir...

    tianlai 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<