shiro jwt 構(gòu)建無狀態(tài)分布式鑒權(quán)體系

一:JWT

1胆萧、令牌構(gòu)造

JWT(json web token)是可在網(wǎng)絡(luò)上傳輸?shù)挠糜诼暶髂撤N主張的令牌(token)宣旱,以JSON 對象為載體的輕量級開放標準(RFC 7519)。

一個JWT令牌的定義包含頭信息、荷載信息哮幢、簽名信息三個部分:

Header//頭信息
{
    "alg": "HS256",//簽名或摘要算法
    "typ": "JWT"http://token類型
}
Playload//荷載信息
{
    "iss": "token-server",//簽發(fā)者
    "exp ": "Mon Nov 13 15:28:41 CST 2017",//過期時間
    "sub ": "wangjie",//用戶名
    "aud": "web-server-1"http://接收方,
    "nbf": "Mon Nov 13 15:40:12 CST 2017",//這個時間之前token不可用
    "jat": "Mon Nov 13 15:20:41 CST 2017",//簽發(fā)時間
    "jti": "0023",//令牌id標識
    "claim": {“auth”:”ROLE_ADMIN”}//訪問主張
}
Signature//簽名信息
簽名或摘要算法(
    base64urlencode(Header),
    Base64urlencode(Playload)坯台,
    secret-key
)

按照JWT規(guī)范,對這個令牌定義進行如下操作:

base64urlencode(Header)
+"."+
base64urlencode(Playload)
+"."+
signature(
    base64urlencode(Header)
    +"."+
    base64urlencode(Playload)
    ,secret-key
)

形成一個完整的JWT:
eyJhbGciOiJIUzUxMiIsInppcCI6IkRFRiJ9.eNqqVspMLFGyMjQ1NDA1tTA0NNRRKi5NUrJSKk_MS8_KTFXSUUqtKEAoMDKsBQAAAP__.dGLe7BVECKzQ_utZJqk4hbcBZthNhohuEjjue98vmpQSGn_9cCYHq7lPIfwKubW8M553F8Uhk933EJwgI5vbLQ

需要注意的是:

1:荷載信息(Playload)中的屬性可以根據(jù)情況進行設(shè)置瘫寝,不要求必須全部填寫。

2:由token的生成方式發(fā)現(xiàn)稠炬,Header和Playload僅僅是base64編碼焕阿,通過base64解碼之后可見,基本相當于是明文傳輸首启,所以應(yīng)避免敏感信息放入Playload暮屡。

2、令牌特點

緊湊性:體積較小毅桃、意味著傳輸速度快褒纲,可以作為POST參數(shù)或放置在HTTP頭准夷。

自包含性:有效的負載包含用戶鑒權(quán)所需所有信息,避免多次查詢數(shù)據(jù)庫莺掠。

安全性:支持對稱和非對稱方式(HMAC衫嵌、RSA)進行消息摘要簽名。

標準化:開放標準彻秆,多語言支持楔绞,跨平臺。

3唇兑、適用場景

1:無狀態(tài)酒朵、分布式鑒權(quán),比如rest api系統(tǒng)扎附、微服務(wù)系統(tǒng)蔫耽。

2:方便解決跨域授權(quán)的問題,比如SSO單點登陸留夜。

3:JWT只是消息協(xié)議匙铡,不牽涉到會話管理和存儲機制,所以單體WEB應(yīng)用還是推薦session-cookie機制香伴。

4慰枕、安全策略

1:重放攻擊(Replay Attacks):應(yīng)保證token只能使用一次,可以將有效期設(shè)置極短(這個時間不好控制)即纲;如果token只使用一次具帮,可以將token的ID放入緩存(redis、memcached)進行閱后即焚(這個可操作性強)低斋;如果一個token需要連續(xù)穿梭多個系統(tǒng)進行鑒權(quán)蜂厅,在最后一次使用后將token的ID放入銷毀緩存(redis、memcached)膊畴。

2:跨站請求偽造(CSRF Cross-site request forgery):由于不依賴Cookie掘猿,所以一般情況下不需要考慮CSRF。

3:跨站腳本攻擊(XSS Cross Site Scripting):相比較CSRF JWT更容易收到XSS的威脅唇跨,可以考慮使用過濾器進行處理稠通,JAVA環(huán)境下的XSS HTMLFilter和PHP環(huán)境下的TWIG。

4:防止偽造令牌:如果使用公私鑰密碼體系买猖,請注意公鑰也應(yīng)該保密改橘,只對可信系統(tǒng)開放。

二:典型微服務(wù)鑒權(quán)架構(gòu)

微服務(wù)鑒權(quán)架構(gòu)

客戶端(移動端或者pc端)根據(jù)口令或者APP KEY到認證服務(wù)鑒權(quán)并申頒發(fā)令牌玉控,如果需要操作服務(wù)A飞主,必須先拿著令牌到服務(wù)A進行權(quán)限問詢。如果需要操作服務(wù)B,同樣先拿著令牌到服務(wù)B進行權(quán)限問詢碌识,一個令牌可以一次使用閱后即焚碾篡,也可以多次使用連續(xù)穿梭多個服務(wù)系統(tǒng),直至令牌過期失效或被銷毀筏餐。

JWT令牌使用了數(shù)字簽名可以有效的防止數(shù)據(jù)篡改和竊取开泽,同樣申請令牌時的數(shù)據(jù)也需要有這樣的安全保障,可以使用HMAC(哈希運算消息認證碼)進行簽名(摘要)和驗簽(參考:基于hmac的rest api鑒權(quán)處理)胖烛。

shiro是java業(yè)界普遍采用的安全框架眼姐,簡單、夠用佩番、擴展性強众旗。我們可以在shiro中添加對HMAC和JWT這兩種鑒權(quán)方式的支持。

三:shiro集成

1趟畏、令牌簽發(fā)服務(wù)

簽發(fā)服務(wù)的核心功能是驗證客戶端是否合法贡歧,如果合法則授予其包含特定訪問主張的JWT。

shiro Token定義:

/**
 * HMAC令牌
 * @author wangjie (http://www.reibang.com/u/ffa3cba4c604) 
 * @date 2016年6月24日 下午2:55:15
 */
public class HmacToken implements AuthenticationToken{
    private static final long serialVersionUID = -7838912794581842158L;
    
    private String clientKey;// 客戶標識(可以是用戶名赋秀、app id等等)
    private String digest;// 消息摘要
    private String timeStamp;// 時間戳
    private Map<String, String[]> parameters;// 訪問參數(shù)
    private String host;// 客戶端IP
    
    public HmacToken(String clientKey,String timeStamp,String digest
                                ,String host,Map<String, String[]> parameters){
        this.clientKey = clientKey;
        this.timeStamp = timeStamp;
        this.digest = digest;
        this.host = host;
        this.parameters = parameters;
    }
    
    @Override
    public Object getPrincipal() {
        return this.clientKey;
    }
    @Override
    public Object getCredentials() {
        return Boolean.TRUE;
    }
    
    // 省略getters and setters ... ...
}
    

shiro Realm即驗證邏輯定義:

/**
 * 基于HMAC( 散列消息認證碼)的控制域
 * @author wangjie (http://www.reibang.com/u/ffa3cba4c604) 
 * @date 2016年6月24日 下午2:55:15
 */
public class HmacRealm extends AuthorizingRealm{
    
    private final AccountProvider accountProvider;//賬號服務(wù)(持久化服務(wù))
    private final CryptogramService cryptogramService;//密碼服務(wù)

    public HmacRealm(AccountProvider accountProvider,CryptogramService cryptogramService){
        this.accountProvider = accountProvider;
        this.cryptogramService = cryptogramService;
    }
    
    public Class<?> getAuthenticationTokenClass() {
        return HmacToken.class;//此Realm只支持HmacToken
    }
    
    /**
     *  認證
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) 
                                                          throws AuthenticationException {
        HmacToken hmacToken = (HmacToken)token;
        List<String> keys = Lists.newArrayList();
        for (String key:hmacToken.getParameters().keySet()){
            if (!"digest".equals(key))
                keys.add(key);
        }
        Collections.sort(keys);//對請求參數(shù)進行排序參數(shù)->自然順序
        StringBuffer baseString = new StringBuffer();
        for (String key : keys) {
            baseString.append(hmacToken.getParameters().get(key)[0]);
        }
        //認證端生成摘要
        String serverDigest = cryptogramService.hmacDigest(baseString.toString());
        //客戶端請求的摘要和服務(wù)端生成的摘要不同
        if(!serverDigest.equals(hmacToken.getDigest())){
            throw new AuthenticationException("數(shù)字摘要驗證失斃洹!A粤绍弟!");
        }
        Long visitTimeStamp = Long.valueOf(hmacToken.getTimeStamp());
        Long nowTimeStamp = System.currentTimeMillis();
        Long jge = nowTimeStamp - visitTimeStamp;
        if (jge > 600000) {// 十分鐘之前的時間戳,這是有效期可以雙方約定由參數(shù)傳過來
            throw new AuthenticationException("數(shù)字摘要失效V荨U燎病!");
        }
        // 此處可以添加查詢數(shù)據(jù)庫檢查賬號是否存在身笤、是否被鎖定豹悬、是否被禁用等等邏輯
        return new SimpleAuthenticationInfo(hmacToken.getClientKey(),Boolean.TRUE,getName());
    }
    
    /** 
     * 授權(quán) 
     */  
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String clientKey = (String)principals.getPrimaryPrincipal();
        SimpleAuthorizationInfo info =  new SimpleAuthorizationInfo();
        // 根據(jù)客戶標識(可以是用戶名、app id等等) 查詢并設(shè)置角色
        Set<String> roles = accountProvider.loadRoles(clientKey);
        info.setRoles(roles);
      // 根據(jù)客戶標識(可以是用戶名液荸、app id等等) 查詢并設(shè)置權(quán)限
        Set<String> permissions = accountProvider.loadPermissions(clientKey);
        info.setStringPermissions(permissions);
        return info;  
    }
}

HMAC認證過濾器定義:

/**
 * 基于HMAC( 散列消息認證碼)的無狀態(tài)認證過濾器
 * @author wangjie (http://www.reibang.com/u/ffa3cba4c604) 
 * @date 2016年6月24日 下午2:55:15
 */
public class HmacFilter extends AccessControlFilter{
    
    private static final Logger log = LoggerFactory.getLogger(AccessControlFilter.class);
    
    public static final String DEFAULT_CLIENTKEY_PARAM = "clientKey";
    public static final String DEFAULT_TIMESTAMP_PARAM = "timeStamp";
    public static final String DEFAUL_DIGEST_PARAM = "digest";

    /**
     * 是否放行
     */
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, 
                                                        Object mappedValue) throws Exception {
        if (null != getSubject(request, response) 
                && getSubject(request, response).isAuthenticated()) {
            return true;//已經(jīng)認證過直接放行
        }
        return false;//轉(zhuǎn)到拒絕訪問處理邏輯
    }

    /**
     * 拒絕處理
     */
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response)  
                                                                          throws Exception {
        if(isHmacSubmission(request)){//如果是Hmac鑒權(quán)的請求
            //創(chuàng)建令牌
            AuthenticationToken token = createToken(request, response);
            try {
                Subject subject = getSubject(request, response);
                subject.login(token);//認證
                return true;//認證成功瞻佛,過濾器鏈繼續(xù)
            } catch (AuthenticationException e) {//認證失敗,發(fā)送401狀態(tài)并附帶異常信息
                log.error(e.getMessage(),e);
                WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED,e.getMessage());
            }
        }
        return false;//打住娇钱,訪問到此為止
    }

    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
        String clientKey = request.getParameter(DEFAULT_CLIENTKEY_PARAM);
        String timeStamp= request.getParameter(DEFAULT_TIMESTAMP_PARAM);
        String digest= request.getParameter(DEFAUL_DIGEST_PARAM);
        Map<String, String[]> parameters = request.getParameterMap();
        String host = request.getRemoteHost();
        return new HmacToken(clientKey, timeStamp, digest, host,parameters);
    }
    
    protected boolean isHmacSubmission(ServletRequest request) {
        String clientKey = request.getParameter(DEFAULT_CLIENTKEY_PARAM);
        String timeStamp= request.getParameter(DEFAULT_TIMESTAMP_PARAM);
        String digest= request.getParameter(DEFAUL_DIGEST_PARAM);
        return (request instanceof HttpServletRequest)
                            && StringUtils.isNotBlank(clientKey)
                            && StringUtils.isNotBlank(timeStamp)
                            && StringUtils.isNotBlank(digest);
    }
}

HMAC鑒權(quán)最基礎(chǔ)的工作就此完成伤柄,需要注意的是鑒權(quán)是無狀態(tài)的不需要創(chuàng)建SESSION,所以需要對shiro的SubjectFactory做一下改造,并設(shè)置到SecurityManager :

/**
 * 擴展自DefaultWebSubjectFactory,對于無狀態(tài)的TOKEN 類型不創(chuàng)建session
 * @author wangjie (http://www.reibang.com/u/ffa3cba4c604)
 * @date 2016年6月24日 下午2:55:15
 */
public class AgileSubjectFactory extends DefaultWebSubjectFactory { 

    public Subject createSubject(SubjectContext context) { 
        AuthenticationToken token = context.getAuthenticationToken();
        if((token instanceof HmacToken)){
            // 當token為HmacToken時文搂, 不創(chuàng)建 session 
            context.setSessionCreationEnabled(false);
        }
        return super.createSubject(context); 
    }
}

JWT簽發(fā)邏輯定義:

@RestController
@RequestMapping("/auth")
public class AuthenticateAction {
    private final String SECRET_KEY = "*(-=4eklfasdfarerf41585fdasf";

    @RequestMapping(value="/apply-token",method=RequestMethod.POST)
    public Map<String,Object> applyToken(@RequestParam(name="clientKey") String clientKey) {
        // 簽發(fā)一個Json Web Token
        // 令牌ID=uuid响迂,用戶=clientKey,簽發(fā)者=clientKey
        // token有效期=1分鐘细疚,用戶角色=null,用戶權(quán)限=create,read,update,delete
        String jwt = issueJwt(UUID.randomUUID().toString(), clientKey, 
                                    "token-server",60000l, null, "create,read,update,delete");
        Map<String,Object> respond = Maps.newHashMap();
        respond.put("jwt", jwt);
        return respond;
    }
    
    /**
     * @param id 令牌ID
     * @param subject 用戶ID
     * @param issuer 簽發(fā)人
     * @param period 有效時間(毫秒)
     * @param roles 訪問主張-角色
     * @param permissions 訪問主張-權(quán)限
     * @param algorithm 加密算法
     * @return json web token 
     */
    private String issueJwt(String id,String subject,String issuer,Long period,String roles
                                            ,String permissions,SignatureAlgorithm algorithm) {
        long currentTimeMillis = System.currentTimeMillis();// 當前時間戳
        byte[] secretKeyBytes = DatatypeConverter.parseBase64Binary(SECRET_KEY);// 秘鑰
        JwtBuilder jwt  =  Jwts.builder();
        if(Strings.isNotBlank(id)) jwt.setId(id);
        jwt.setSubject(subject);// 用戶名主題
        if(Strings.isNotBlank(issuer)) jwt.setIssuer(issuer);//簽發(fā)者
        if(Strings.isNotBlank(issuer)) jwt.setIssuer(issuer);//簽發(fā)者  
        jwt.setIssuedAt(new Date(currentTimeMillis));//簽發(fā)時間
        if(null != period){
            Date expiration = new Date(currentTimeMillis+period);
            jwt.setExpiration(expiration);//有效時間
        }
        if(Strings.isNotBlank(roles)) jwt.claim("roles", roles);//角色
        if(Strings.isNotBlank(permissions)) jwt.claim("perms", permissions);//權(quán)限
        jwt.compressWith(CompressionCodecs.DEFLATE);//壓縮,可選GZIP
        jwt.signWith(algorithm, secretKeyBytes);//加密設(shè)置
        return jwt.compact();
    }
}

添加過濾器:filterChainManager.addFilter( "hmac", new HmacFilter());
配置過濾規(guī)則:filterChainManager.addToChain("/auth/**", "hmac");
如果有需要可以在規(guī)則中添加其他過濾器。
JWT申請測試:

    @Test
    public String applyToken(){
        Long current = System.currentTimeMillis() ;
        String url = "http://localhost:8080/tokenServer/auth/apply-token";
        MultiValueMap<String, Object> dataMap = new LinkedMultiValueMap<String, Object>();  
        String clientKey = "administrator";// 客戶端標識(用戶名)
        String mix = String.valueOf(new Random().nextFloat());// 隨機數(shù)疯兼,進行混淆
        String timeStamp = current.toString();// 時間戳
        dataMap.add("clientKey", clientKey);
        dataMap.add("mix", mix);
        dataMap.add("timeStamp", timeStamp);
        String baseString = clientKey+mix+timeStamp;
        String digest =  hmacDigest(baseString);// 生成HMAC摘要
        dataMap.add("digest", digest);
        Map result = rt.postForObject(url, dataMap, Map.class);
        return (String)result.get("jwt");
    }

返回JWT:

eyJhbGciOiJIUzUxMiIsInppcCI6IkRFRiJ9.eNo8y80KwjAQBOB32XMCTfNj7NvsNluI2jYkWxHEdzf14GUYPmbecJMMEwzXGAjnUdvBR-2cdzpSRE2jiUtwSLQEUNAO6mNMa95yk4qy1665ta6y33nTjeuTf4gCk_HGWBvtxSjgV_mDsx0K1_U8zpVRWPVM6ijp7IkfLAyfLwAAAP__.GK7EJibs7n50uGksvvLK6Y39Ur6ZYXoXI9LOlFwEpIijHGAZjIyDhiYD-1nv1YbPJ46BI-gDTntV3KC0d8NSrA

2:令牌驗簽

有了JWT簽發(fā)服務(wù)然遏,要使用JWT就需要業(yè)務(wù)系統(tǒng)有JWT驗鑒功能,同樣在shiro中集成吧彪。
由于JWT是自包含的待侵,令牌中已經(jīng)聲明了訪問主張(比如角色、權(quán)限等)姨裸,驗簽功能只需要驗證令牌合法就行了秧倾,不需要訪問數(shù)據(jù)庫。

shiro Token定義:

/**
 * JWT令牌
 * @author wangjie (http://www.reibang.com/u/ffa3cba4c604) 
 * @date 2016年6月24日 下午2:55:15
 */
public class JwtToken implements AuthenticationToken{

    private static final long serialVersionUID = -790191688300000066L;
    
    private String jwt;// json web token
    private String host;// 客戶端IP
    
    public JwtToken(String jwt,String host){
        this.jwt = jwt;
        this.host = host;
    }

    @Override
    public Object getPrincipal() {
        return this.jwt;
    }

    @Override
    public Object getCredentials() {
        return Boolean.TRUE;
    }
    
    // 忽略getters and setters
}
    

JWT Realm即認證邏輯定義:

/**
 * 基于JWT( JSON WEB TOKEN)的認證域
 * 
 * @author wangjie (http://www.reibang.com/u/ffa3cba4c604)
 * @date 2016年6月24日 下午2:55:15
 */
public class JwtRealm extends AuthorizingRealm {

    public Class<?> getAuthenticationTokenClass() {
        return JwtToken.class;//此Realm只支持JwtToken
    }

    /**
     * 認證
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) 
                                                        throws AuthenticationException {
        JwtToken jwtToken = (JwtToken) token;
        String jwt = (String) jwtToken.getPrincipal();
        JwtPlayload jwtPlayload;
        try {
            Claims claims = Jwts.parser()
                    .setSigningKey(DatatypeConverter.parseBase64Binary(SECRETKEY))
                    .parseClaimsJws(jwt)
                    .getBody();
            jwtPlayload = new JwtPlayload();
            jwtPlayload.setId(claims.getId());
            jwtPlayload.setUserId(claims.getSubject());// 用戶名
            jwtPlayload.setIssuer(claims.getIssuer());// 簽發(fā)者
            jwtPlayload.setIssuedAt(claims.getIssuedAt());// 簽發(fā)時間
            jwtPlayload.setAudience(claims.getAudience());// 接收方
            jwtPlayload.setRoles(claims.get("roles", String.class));// 訪問主張-角色
            jwtPlayload.setPerms(claims.get("perms", String.class));// 訪問主張-權(quán)限
        } catch (ExpiredJwtException e) {
            throw new AuthenticationException("JWT 令牌過期:" + e.getMessage());
        } catch (UnsupportedJwtException e) {
            throw new AuthenticationException("JWT 令牌無效:" + e.getMessage());
        } catch (MalformedJwtException e) {
            throw new AuthenticationException("JWT 令牌格式錯誤:" + e.getMessage());
        } catch (SignatureException e) {
            throw new AuthenticationException("JWT 令牌簽名無效:" + e.getMessage());
        } catch (IllegalArgumentException e) {
            throw new AuthenticationException("JWT 令牌參數(shù)異常:" + e.getMessage());
        } catch (Exception e) {
            throw new AuthenticationException("JWT 令牌錯誤:" + e.getMessage());
        }
        // 如果要使token只能使用一次傀缩,此處可以過濾并緩存jwtPlayload.getId()
        // 可以做簽發(fā)方驗證
        // 可以做接收方驗證
        return new SimpleAuthenticationInfo(jwtPlayload, Boolean.TRUE, getName());
    }

    /**
     * 授權(quán),JWT已包含訪問主張只需要解析其中的主張定義就行了
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        JwtPlayload jwtPlayload = (JwtPlayload) principals.getPrimaryPrincipal();
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        // 解析角色并設(shè)置
        Set<String> roles = Sets.newHashSet(StringUtils.split(jwtPlayload.getRoles(), ","));
        info.setRoles(roles);
        // 解析權(quán)限并設(shè)置
        Set<String> permissions = Sets.newHashSet(StringUtils.split(jwtPlayload.getPerms(), ","));
        info.setStringPermissions(permissions);
        return info;
    }
}

處理邏輯中拋出的異常信息很詳細那先,其實這樣并不安全只是對調(diào)試友好,線上環(huán)境不用把異常信息給那么細赡艰。

JWT鑒權(quán)過濾器定義:


/**
 * 基于JWT標準的無狀態(tài)認證過濾器
 * @author wangjie (http://www.reibang.com/u/ffa3cba4c604) 
 * @date 2016年6月24日 下午2:55:15
 * 
 */ 
public class JwtFilter extends AccessControlFilter {
    
    private static final Logger log = LoggerFactory.getLogger(AccessControlFilter.class);
    
    public static final String DEFAULT_JWT_PARAM = "jwt";

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        if (null != getSubject(request, response) 
                && getSubject(request, response).isAuthenticated()) {
            return true;
        }
        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        if(isJwtSubmission(request)){
            AuthenticationToken token = createToken(request, response);
            try {
                Subject subject = getSubject(request, response);
                subject.login(token);
                return true;
            } catch (AuthenticationException e) {
                log.error(e.getMessage(),e);
                WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED,e.getMessage());
            } 
        }
        return false;
    }

    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
        String jwt = request.getParameter(DEFAULT_JWT_PARAM);
        String host = request.getRemoteHost();
        log.info("authenticate jwt token:"+jwt);
        System.out.println("jwt:"+jwt);
        return new JwtToken(jwt, host);
    }
    
    protected boolean isJwtSubmission(ServletRequest request) {
        String jwt = request.getParameter(DEFAULT_JWT_PARAM);
        return (request instanceof HttpServletRequest)
                                && StringUtils.isNotBlank(jwt);
    }
    
}

資源訪問權(quán)限過濾器定義:

/**
 * 基于JWT( JSON WEB TOKEN)的無狀態(tài)資源過濾器
 * @author wangjie (http://www.reibang.com/u/ffa3cba4c604) 
 * @date 2016年6月24日 下午2:55:15
 */
public class JwtPermFilter extends HmacFilter{
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, 
                                                    Object mappedValue) throws Exception {
        Subject subject = getSubject(request, response);
        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;
    }
    
}

添加過濾器:filterChainManager.addFilter( "jwt", new JwtFilter());
filterChainManager.addFilter( "jwtPerms", new JwtPermFilter());
配置過濾規(guī)則:filterChainManager.addToChain("/api/", "jwt");
filterChainManager.addToChain("/api/delete/
", "jwtPerms["api:delete"]");
如果有需要可以在規(guī)則中添加其他過濾器售淡。
同令牌申請服務(wù)一樣,需要設(shè)置shiro不創(chuàng)建SESSION慷垮。

jsets-shiro-spring-boot-starter中封裝了JWT的鑒權(quán)揖闸,請參見:

項目文檔、源碼

項目中經(jīng)常用到的功能比如:驗證碼料身、密碼錯誤次數(shù)限制汤纸、賬號唯一用戶登陸、動態(tài)URL過濾規(guī)則芹血、無狀態(tài)鑒權(quán)等等jsets-shiro-spring-boot-starter對這些常用的功能進行了封裝和自動導入贮泞,少量的配置就可以應(yīng)用在項目中。

1祟牲、jsets-shiro-spring-boot-starter項目詳情請參見:jsets-shiro-spring-boot-starter
2隙畜、應(yīng)用示例源碼請參見:jsets-shiro-demo
3、jsets-shiro-spring-boot-starter使用說明請參見:使用說明

碼字不易说贝,轉(zhuǎn)載請保留原文連接http://www.reibang.com/p/0a5d3d07a151

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末议惰,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子乡恕,更是在濱河造成了極大的恐慌言询,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件傲宜,死亡現(xiàn)場離奇詭異运杭,居然都是意外死亡,警方通過查閱死者的電腦和手機函卒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門辆憔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事虱咧⌒荛唬” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵腕巡,是天一觀的道長玄坦。 經(jīng)常有香客問我,道長绘沉,這世上最難降的妖魔是什么煎楣? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮车伞,結(jié)果婚禮上择懂,老公的妹妹穿的比我還像新娘。我一直安慰自己帖世,他們只是感情好休蟹,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著日矫,像睡著了一般赂弓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上哪轿,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天盈魁,我揣著相機與錄音,去河邊找鬼窃诉。 笑死杨耙,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的飘痛。 我是一名探鬼主播珊膜,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼宣脉!你這毒婦竟也來了车柠?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤塑猖,失蹤者是張志新(化名)和其女友劉穎竹祷,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體羊苟,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡塑陵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蜡励。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片令花。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡阻桅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出兼都,到底是詐尸還是另有隱情鳍刷,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布俯抖,位于F島的核電站,受9級特大地震影響瓦胎,放射性物質(zhì)發(fā)生泄漏芬萍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一搔啊、第九天 我趴在偏房一處隱蔽的房頂上張望柬祠。 院中可真熱鬧,春花似錦负芋、人聲如沸漫蛔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽莽龟。三九已至,卻和暖如春锨天,著一層夾襖步出監(jiān)牢的瞬間毯盈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工病袄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留搂赋,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓益缠,卻偏偏與公主長得像脑奠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子幅慌,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

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