一:JWT
1、令牌構(gòu)造
JWT(json web token)是可在網(wǎng)絡(luò)上傳輸?shù)挠糜诼暶髂撤N主張的令牌(token),以JSON 對象為載體的輕量級開放標(biāo)準(zhǔn)(RFC 7519)。
一個(gè)JWT令牌的定義包含頭信息、荷載信息、簽名信息三個(gè)部分:
Header//頭信息
{
"alg": "HS256",//簽名或摘要算法
"typ": "JWT"http://token類型
}
Playload//荷載信息
{
"iss": "token-server",//簽發(fā)者
"exp ": "Mon Nov 13 15:28:41 CST 2017",//過期時(shí)間
"sub ": "wangjie",//用戶名
"aud": "web-server-1"http://接收方,
"nbf": "Mon Nov 13 15:40:12 CST 2017",//這個(gè)時(shí)間之前token不可用
"jat": "Mon Nov 13 15:20:41 CST 2017",//簽發(fā)時(shí)間
"jti": "0023",//令牌id標(biāo)識
"claim": {“auth”:”ROLE_ADMIN”}//訪問主張
}
Signature//簽名信息
簽名或摘要算法(
base64urlencode(Header),
Base64urlencode(Playload),
secret-key
)
按照J(rèn)WT規(guī)范,對這個(gè)令牌定義進(jìn)行如下操作:
base64urlencode(Header)
+"."+
base64urlencode(Playload)
+"."+
signature(
base64urlencode(Header)
+"."+
base64urlencode(Playload)
,secret-key
)
形成一個(gè)完整的JWT:
eyJhbGciOiJIUzUxMiIsInppcCI6IkRFRiJ9.eNqqVspMLFGyMjQ1NDA1tTA0NNRRKi5NUrJSKk_MS8_KTFXSUUqtKEAoMDKsBQAAAP__.dGLe7BVECKzQ_utZJqk4hbcBZthNhohuEjjue98vmpQSGn_9cCYHq7lPIfwKubW8M553F8Uhk933EJwgI5vbLQ
需要注意的是:
1:荷載信息(Playload)中的屬性可以根據(jù)情況進(jìn)行設(shè)置,不要求必須全部填寫。
2:由token的生成方式發(fā)現(xiàn),Header和Playload僅僅是base64編碼,通過base64解碼之后可見,基本相當(dāng)于是明文傳輸,所以應(yīng)避免敏感信息放入Playload。
2、令牌特點(diǎn)
緊湊性:體積較小、意味著傳輸速度快,可以作為POST參數(shù)或放置在HTTP頭。
自包含性:有效的負(fù)載包含用戶鑒權(quán)所需所有信息,避免多次查詢數(shù)據(jù)庫。
安全性:支持對稱和非對稱方式(HMAC、RSA)進(jìn)行消息摘要簽名。
標(biāo)準(zhǔn)化:開放標(biāo)準(zhǔn),多語言支持,跨平臺。
3、適用場景
1:無狀態(tài)、分布式鑒權(quán),比如rest api系統(tǒng)、微服務(wù)系統(tǒng)。
2:方便解決跨域授權(quán)的問題,比如SSO單點(diǎn)登陸。
3:JWT只是消息協(xié)議,不牽涉到會話管理和存儲機(jī)制,所以單體WEB應(yīng)用還是推薦session-cookie機(jī)制。
4、安全策略
1:重放攻擊(Replay Attacks):應(yīng)保證token只能使用一次,可以將有效期設(shè)置極短(這個(gè)時(shí)間不好控制);如果token只使用一次,可以將token的ID放入緩存(redis、memcached)進(jìn)行閱后即焚(這個(gè)可操作性強(qiáng));如果一個(gè)token需要連續(xù)穿梭多個(gè)系統(tǒng)進(jìn)行鑒權(quán),在最后一次使用后將token的ID放入銷毀緩存(redis、memcached)。
2:跨站請求偽造(CSRF Cross-site request forgery):由于不依賴Cookie,所以一般情況下不需要考慮CSRF。
3:跨站腳本攻擊(XSS Cross Site Scripting):相比較CSRF JWT更容易收到XSS的威脅,可以考慮使用過濾器進(jìn)行處理,JAVA環(huán)境下的XSS HTMLFilter和PHP環(huán)境下的TWIG。
4:防止偽造令牌:如果使用公私鑰密碼體系,請注意公鑰也應(yīng)該保密,只對可信系統(tǒng)開放。
二:典型微服務(wù)鑒權(quán)架構(gòu)

客戶端(移動端或者pc端)根據(jù)口令或者APP KEY到認(rèn)證服務(wù)鑒權(quán)并申頒發(fā)令牌,如果需要操作服務(wù)A,必須先拿著令牌到服務(wù)A進(jìn)行權(quán)限問詢。如果需要操作服務(wù)B,同樣先拿著令牌到服務(wù)B進(jìn)行權(quán)限問詢,一個(gè)令牌可以一次使用閱后即焚,也可以多次使用連續(xù)穿梭多個(gè)服務(wù)系統(tǒng),直至令牌過期失效或被銷毀。
JWT令牌使用了數(shù)字簽名可以有效的防止數(shù)據(jù)篡改和竊取,同樣申請令牌時(shí)的數(shù)據(jù)也需要有這樣的安全保障,可以使用HMAC(哈希運(yùn)算消息認(rèn)證碼)進(jìn)行簽名(摘要)和驗(yàn)簽(參考:基于hmac的rest api鑒權(quán)處理)。
shiro是java業(yè)界普遍采用的安全框架,簡單、夠用、擴(kuò)展性強(qiáng)。我們可以在shiro中添加對HMAC和JWT這兩種鑒權(quán)方式的支持。
三:shiro集成
1、令牌簽發(fā)服務(wù)
簽發(fā)服務(wù)的核心功能是驗(yàn)證客戶端是否合法,如果合法則授予其包含特定訪問主張的JWT。
shiro Token定義:
/**
* HMAC令牌
* @author wangjie (http://www.itdecent.cn/u/ffa3cba4c604)
* @date 2016年6月24日 下午2:55:15
*/
public class HmacToken implements AuthenticationToken{
private static final long serialVersionUID = -7838912794581842158L;
private String clientKey;// 客戶標(biāo)識(可以是用戶名、app id等等)
private String digest;// 消息摘要
private String timeStamp;// 時(shí)間戳
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即驗(yàn)證邏輯定義:
/**
* 基于HMAC( 散列消息認(rèn)證碼)的控制域
* @author wangjie (http://www.itdecent.cn/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
}
/**
* 認(rèn)證
*/
@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ù)進(jìn)行排序參數(shù)->自然順序
StringBuffer baseString = new StringBuffer();
for (String key : keys) {
baseString.append(hmacToken.getParameters().get(key)[0]);
}
//認(rèn)證端生成摘要
String serverDigest = cryptogramService.hmacDigest(baseString.toString());
//客戶端請求的摘要和服務(wù)端生成的摘要不同
if(!serverDigest.equals(hmacToken.getDigest())){
throw new AuthenticationException("數(shù)字摘要驗(yàn)證失?。。。?);
}
Long visitTimeStamp = Long.valueOf(hmacToken.getTimeStamp());
Long nowTimeStamp = System.currentTimeMillis();
Long jge = nowTimeStamp - visitTimeStamp;
if (jge > 600000) {// 十分鐘之前的時(shí)間戳,這是有效期可以雙方約定由參數(shù)傳過來
throw new AuthenticationException("數(shù)字摘要失效!??!");
}
// 此處可以添加查詢數(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ù)客戶標(biāo)識(可以是用戶名、app id等等) 查詢并設(shè)置角色
Set<String> roles = accountProvider.loadRoles(clientKey);
info.setRoles(roles);
// 根據(jù)客戶標(biāo)識(可以是用戶名、app id等等) 查詢并設(shè)置權(quán)限
Set<String> permissions = accountProvider.loadPermissions(clientKey);
info.setStringPermissions(permissions);
return info;
}
}
HMAC認(rèn)證過濾器定義:
/**
* 基于HMAC( 散列消息認(rèn)證碼)的無狀態(tài)認(rèn)證過濾器
* @author wangjie (http://www.itdecent.cn/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)認(rèn)證過直接放行
}
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);//認(rèn)證
return true;//認(rèn)證成功,過濾器鏈繼續(xù)
} catch (AuthenticationException e) {//認(rèn)證失敗,發(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 :
/**
* 擴(kuò)展自DefaultWebSubjectFactory,對于無狀態(tài)的TOKEN 類型不創(chuàng)建session
* @author wangjie (http://www.itdecent.cn/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)){
// 當(dāng)token為HmacToken時(shí), 不創(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ā)一個(gè)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 有效時(shí)間(毫秒)
* @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();// 當(dāng)前時(shí)間戳
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ā)時(shí)間
if(null != period){
Date expiration = new Date(currentTimeMillis+period);
jwt.setExpiration(expiration);//有效時(shí)間
}
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";// 客戶端標(biāo)識(用戶名)
String mix = String.valueOf(new Random().nextFloat());// 隨機(jī)數(shù),進(jìn)行混淆
String timeStamp = current.toString();// 時(shí)間戳
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:令牌驗(yàn)簽
有了JWT簽發(fā)服務(wù),要使用JWT就需要業(yè)務(wù)系統(tǒng)有JWT驗(yàn)鑒功能,同樣在shiro中集成。
由于JWT是自包含的,令牌中已經(jīng)聲明了訪問主張(比如角色、權(quán)限等),驗(yàn)簽功能只需要驗(yàn)證令牌合法就行了,不需要訪問數(shù)據(jù)庫。
shiro Token定義:
/**
* JWT令牌
* @author wangjie (http://www.itdecent.cn/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即認(rèn)證邏輯定義:
/**
* 基于JWT( JSON WEB TOKEN)的認(rèn)證域
*
* @author wangjie (http://www.itdecent.cn/u/ffa3cba4c604)
* @date 2016年6月24日 下午2:55:15
*/
public class JwtRealm extends AuthorizingRealm {
public Class<?> getAuthenticationTokenClass() {
return JwtToken.class;//此Realm只支持JwtToken
}
/**
* 認(rèn)證
*/
@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ā)時(shí)間
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ā)方驗(yàn)證
// 可以做接收方驗(yàn)證
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;
}
}
處理邏輯中拋出的異常信息很詳細(xì),其實(shí)這樣并不安全只是對調(diào)試友好,線上環(huán)境不用把異常信息給那么細(xì)。
JWT鑒權(quán)過濾器定義:
/**
* 基于JWT標(biāo)準(zhǔn)的無狀態(tài)認(rèn)證過濾器
* @author wangjie (http://www.itdecent.cn/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.itdecent.cn/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),請參見:
項(xiàng)目文檔、源碼
項(xiàng)目中經(jīng)常用到的功能比如:驗(yàn)證碼、密碼錯誤次數(shù)限制、賬號唯一用戶登陸、動態(tài)URL過濾規(guī)則、無狀態(tài)鑒權(quán)等等jsets-shiro-spring-boot-starter對這些常用的功能進(jìn)行了封裝和自動導(dǎo)入,少量的配置就可以應(yīng)用在項(xiàng)目中。
1、jsets-shiro-spring-boot-starter項(xiàng)目詳情請參見:jsets-shiro-spring-boot-starter
2、應(yīng)用示例源碼請參見:jsets-shiro-demo
3、jsets-shiro-spring-boot-starter使用說明請參見:使用說明
碼字不易,轉(zhuǎn)載請保留原文連接http://www.itdecent.cn/p/0a5d3d07a151