一:JWT

1、令牌构造

JWT(json web token)是可在网络上传输的用于声明某种主张的令牌(token),以JSON 对象为载体的轻量级开放标准(RFC 7519)。

一个JWT令牌的定义包含头信息、荷载信息、签名信息三个部分:

Header//头信息
{
"alg": "HS256",//签名或摘要算法
"typ": "JWT"//token类型
}
Playload//荷载信息
{
"iss": "token-server",//签发者
"exp ": "Mon Nov 13 15:28:41 CST 2017",//过期时间
"sub ": "wangjie",//用户名
"aud": "web-server-1"//接收方,
"nbf": "Mon Nov 13 15:40:12 CST 2017",//这个时间之前token不可用
"jat": "Mon Nov 13 15:20:41 CST 2017",//签发时间
"jti": "0023",//令牌id标识
"claim": {“auth”:”ROLE_ADMIN”}//访问主张
}
Signature//签名信息
签名或摘要算法(
base64urlencode(Header),
Base64urlencode(Playload),
secret-key

按照JWT规范,对这个令牌定义进行如下操作:

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

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

需要注意的是:

1:荷载信息(Playload)中的属性可以根据情况进行设置,不要求必须全部填写。

2:由token的生成方式发现,Header和Playload仅仅是base64编码,通过base64解码之后可见,基本相当于是明文传输,所以应避免敏感信息放入Playload。

2、令牌特点

紧凑性:体积较小、意味着传输速度快,可以作为POST参数或放置在HTTP头。

自包含性:有效的负载包含用户鉴权所需所有信息,避免多次查询数据库。

安全性:支持对称和非对称方式(HMAC、RSA)进行消息摘要签名。

标准化:开放标准,多语言支持,跨平台。

3、适用场景

1:无状态、分布式鉴权,比如rest api系统、微服务系统。

2:方便解决跨域授权的问题,比如SSO单点登陆。

3:JWT只是消息协议,不牵涉到会话管理和存储机制,所以单体WEB应用还是推荐session-cookie机制。

4、安全策略

1:重放攻击(Replay Attacks):应保证token只能使用一次,可以将有效期设置极短(这个时间不好控制);如果token只使用一次,可以将token的ID放入缓存(redis、memcached)进行阅后即焚(这个可操作性强);如果一个token需要连续穿梭多个系统进行鉴权,在最后一次使用后将token的ID放入销毁缓存(redis、memcached)。

2:跨站请求伪造(CSRF Cross-site request forgery):由于不依赖Cookie,所以一般情况下不需要考虑CSRF。

3:跨站脚本攻击(XSS Cross Site Scripting):相比较CSRF JWT更容易收到XSS的威胁,可以考虑使用过滤器进行处理,JAVA环境下的XSS HTMLFilter和PHP环境下的TWIG。

4:防止伪造令牌:如果使用公私钥密码体系,请注意公钥也应该保密,只对可信系统开放。

二:典型微服务鉴权架构

 
微服务鉴权架构

客户端(移动端或者pc端)根据口令或者APP KEY到认证服务鉴权并申颁发令牌,如果需要操作服务A,必须先拿着令牌到服务A进行权限问询。如果需要操作服务B,同样先拿着令牌到服务B进行权限问询,一个令牌可以一次使用阅后即焚,也可以多次使用连续穿梭多个服务系统,直至令牌过期失效或被销毁。

JWT令牌使用了数字签名可以有效的防止数据篡改和窃取,同样申请令牌时的数据也需要有这样的安全保障,可以使用HMAC(哈希运算消息认证码)进行签名(摘要)和验签(参考:基于hmac的rest api鉴权处理)。

shiro是java业界普遍采用的安全框架,简单、够用、扩展性强。我们可以在shiro中添加对HMAC和JWT这两种鉴权方式的支持。

三:shiro集成

1、令牌签发服务

签发服务的核心功能是验证客户端是否合法,如果合法则授予其包含特定访问主张的JWT。

shiro Token定义:

/**
* HMAC令牌
* @author wangjie (http://www.jianshu.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;// 访问参数
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.jianshu.com/u/ffa3cba4c604)
* @date 2016年6月24日 下午2:55:15
*/
public class HmacRealm extends AuthorizingRealm{ private final AccountProvider accountProvider;//账号服务(持久化服务)
private final CryptogramService cryptogramService;//密码服务 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);//对请求参数进行排序参数->自然顺序
StringBuffer baseString = new StringBuffer();
for (String key : keys) {
baseString.append(hmacToken.getParameters().get(key)[0]);
}
//认证端生成摘要
String serverDigest = cryptogramService.hmacDigest(baseString.toString());
//客户端请求的摘要和服务端生成的摘要不同
if(!serverDigest.equals(hmacToken.getDigest())){
throw new AuthenticationException("数字摘要验证失败!!!");
}
Long visitTimeStamp = Long.valueOf(hmacToken.getTimeStamp());
Long nowTimeStamp = System.currentTimeMillis();
Long jge = nowTimeStamp - visitTimeStamp;
if (jge > 600000) {// 十分钟之前的时间戳,这是有效期可以双方约定由参数传过来
throw new AuthenticationException("数字摘要失效!!!");
}
// 此处可以添加查询数据库检查账号是否存在、是否被锁定、是否被禁用等等逻辑
return new SimpleAuthenticationInfo(hmacToken.getClientKey(),Boolean.TRUE,getName());
} /**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String clientKey = (String)principals.getPrimaryPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 根据客户标识(可以是用户名、app id等等) 查询并设置角色
Set<String> roles = accountProvider.loadRoles(clientKey);
info.setRoles(roles);
// 根据客户标识(可以是用户名、app id等等) 查询并设置权限
Set<String> permissions = accountProvider.loadPermissions(clientKey);
info.setStringPermissions(permissions);
return info;
}
}

HMAC认证过滤器定义:

/**
* 基于HMAC( 散列消息认证码)的无状态认证过滤器
* @author wangjie (http://www.jianshu.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;//已经认证过直接放行
}
return false;//转到拒绝访问处理逻辑
} /**
* 拒绝处理
*/
protected boolean onAccessDenied(ServletRequest request, ServletResponse response)
throws Exception {
if(isHmacSubmission(request)){//如果是Hmac鉴权的请求
//创建令牌
AuthenticationToken token = createToken(request, response);
try {
Subject subject = getSubject(request, response);
subject.login(token);//认证
return true;//认证成功,过滤器链继续
} catch (AuthenticationException e) {//认证失败,发送401状态并附带异常信息
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鉴权最基础的工作就此完成,需要注意的是鉴权是无状态的不需要创建SESSION,所以需要对shiro的SubjectFactory做一下改造,并设置到SecurityManager :

/**
* 扩展自DefaultWebSubjectFactory,对于无状态的TOKEN 类型不创建session
* @author wangjie (http://www.jianshu.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时, 不创建 session
context.setSessionCreationEnabled(false);
}
return super.createSubject(context);
}
}

JWT签发逻辑定义:

@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) {
// 签发一个Json Web Token
// 令牌ID=uuid,用户=clientKey,签发者=clientKey
// token有效期=1分钟,用户角色=null,用户权限=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 签发人
* @param period 有效时间(毫秒)
* @param roles 访问主张-角色
* @param permissions 访问主张-权限
* @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);//签发者
if(Strings.isNotBlank(issuer)) jwt.setIssuer(issuer);//签发者
jwt.setIssuedAt(new Date(currentTimeMillis));//签发时间
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);//权限
jwt.compressWith(CompressionCodecs.DEFLATE);//压缩,可选GZIP
jwt.signWith(algorithm, secretKeyBytes);//加密设置
return jwt.compact();
}
}

添加过滤器:filterChainManager.addFilter( "hmac", new HmacFilter());
配置过滤规则:filterChainManager.addToChain("/auth/**", "hmac");
如果有需要可以在规则中添加其他过滤器。
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());// 随机数,进行混淆
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签发服务,要使用JWT就需要业务系统有JWT验鉴功能,同样在shiro中集成。
由于JWT是自包含的,令牌中已经声明了访问主张(比如角色、权限等),验签功能只需要验证令牌合法就行了,不需要访问数据库。

shiro Token定义:

/**
* JWT令牌
* @author wangjie (http://www.jianshu.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.jianshu.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());// 签发者
jwtPlayload.setIssuedAt(claims.getIssuedAt());// 签发时间
jwtPlayload.setAudience(claims.getAudience());// 接收方
jwtPlayload.setRoles(claims.get("roles", String.class));// 访问主张-角色
jwtPlayload.setPerms(claims.get("perms", String.class));// 访问主张-权限
} 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 令牌参数异常:" + e.getMessage());
} catch (Exception e) {
throw new AuthenticationException("JWT 令牌错误:" + e.getMessage());
}
// 如果要使token只能使用一次,此处可以过滤并缓存jwtPlayload.getId()
// 可以做签发方验证
// 可以做接收方验证
return new SimpleAuthenticationInfo(jwtPlayload, Boolean.TRUE, getName());
} /**
* 授权,JWT已包含访问主张只需要解析其中的主张定义就行了
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
JwtPlayload jwtPlayload = (JwtPlayload) principals.getPrimaryPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 解析角色并设置
Set<String> roles = Sets.newHashSet(StringUtils.split(jwtPlayload.getRoles(), ","));
info.setRoles(roles);
// 解析权限并设置
Set<String> permissions = Sets.newHashSet(StringUtils.split(jwtPlayload.getPerms(), ","));
info.setStringPermissions(permissions);
return info;
}
}

处理逻辑中抛出的异常信息很详细,其实这样并不安全只是对调试友好,线上环境不用把异常信息给那么细。

JWT鉴权过滤器定义:


/**
* 基于JWT标准的无状态认证过滤器
* @author wangjie (http://www.jianshu.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);
} }

资源访问权限过滤器定义:

/**
* 基于JWT( JSON WEB TOKEN)的无状态资源过滤器
* @author wangjie (http://www.jianshu.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());
配置过滤规则:filterChainManager.addToChain("/api/", "jwt");
filterChainManager.addToChain("/api/delete/
", "jwtPerms["api:delete"]");
如果有需要可以在规则中添加其他过滤器。
同令牌申请服务一样,需要设置shiro不创建SESSION。

jsets-shiro-spring-boot-starter中封装了JWT的鉴权,请参见:

项目文档、源码

项目中经常用到的功能比如:验证码、密码错误次数限制、账号唯一用户登陆、动态URL过滤规则、无状态鉴权等等jsets-shiro-spring-boot-starter对这些常用的功能进行了封装和自动导入,少量的配置就可以应用在项目中。

1、jsets-shiro-spring-boot-starter项目详情请参见:jsets-shiro-spring-boot-starter
2、应用示例源码请参见:jsets-shiro-demo
3、jsets-shiro-spring-boot-starter使用说明请参见:使用说明

码字不易,转载请保留原文连接http://www.jianshu.com/p/0a5d3d07a151

作者:wangjie2016
链接:https://www.jianshu.com/p/0a5d3d07a151
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

shiro jwt 构建无状态分布式鉴权体系的更多相关文章

  1. http无状态和鉴权解决四种方案

    http协议本身是无状态的,但是在实际的web开发中常有一些操作需要有状态.比如想要访问一些私人访问权限的文章,或者这种操作需要明确当前用户身份. 显然,最简单的方案就是每次都发送账户和密码,但是这样 ...

  2. 基于JWT的无状态分布式授权【本文摘自智车芯官网】

    简介 JWT是一种用于HTTP交互双方之间传递安全信息的简洁的.安全的表述性声明规范.JWT作为一个开发的标准,它定义了一种简洁的,自包含的方法用于通信双发之间以JSON形式安全传递.且因为数字证书的 ...

  3. Spring Boot Security 整合 JWT 实现 无状态的分布式API接口

    简介 JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案.JSON Web Token 入门教程 - 阮一峰,这篇文章可以帮你了解JWT的概念.本文重点讲解Spring Boo ...

  4. 5分钟Serverless实践 | 构建无服务器图片鉴黄Web应用

    Serverless是什么 Serverless中文译为“无服务器”,最早可以追溯到2012年Ken Fromm发表的<Why The Future Of Software And Apps I ...

  5. Spring Cloud Gateway + Jwt + Oauth2 实现网关的鉴权操作

    Spring Cloud Gateway + Jwt + Oauth2 实现网关的鉴权操作 一.背景 二.需求 三.前置条件 四.项目结构 五.网关层代码的编写 1.引入jar包 2.自定义授权管理器 ...

  6. 开箱即用 - jwt 无状态分布式授权

    基于JWT(Json Web Token)的授权方式 JWT 是JSON风格轻量级的授权和身份认证规范,可实现无状态.分布式的Web应用授权: 从客户端请求服务器获取token, 用该token 去访 ...

  7. jwt 无状态分布式授权

    基于JWT(Json Web Token)的授权方式 JWT 是JSON风格轻量级的授权和身份认证规范,可实现无状态.分布式的Web应用授权: 从客户端请求服务器获取token, 用该token 去访 ...

  8. SpringBoot+JWT+SpringSecurity+MybatisPlus实现Restful鉴权脚手架

    若图片查看异常,请前往掘金查看:https://juejin.im/post/5d1dee34e51d4577790c1cf4 前言 JWT(json web token)的无状态鉴权方式,越来越流行 ...

  9. 5分钟构建无服务图片鉴黄web应用(基于FunctionGraph)

    函数工作流(FunctionGraph,FGS)是一项基于事件驱动的函数托管计算服务,托管函数具备以毫秒级弹性伸缩.免运维.高可靠的方式运行.即使在一些复杂的web应用场景中,函数工作流也能发挥出令人 ...

随机推荐

  1. (总结)CentOS 6.x使用yum快速安装Apache+PHP+Tomcat(JSP)+MySQL

    (总结)CentOS 6.x使用yum快速安装Apache+PHP+Tomcat(JSP)+MySQL PS:这个是懒人yum快速安装法,用于开发和测试环境很方便,用于没有特殊要求的生产环境也可以.特 ...

  2. Java中的包扫描(工具)

    在现在好多应用场景中,我们需要得到某个包名下面所有的类, 包括我们自己在src里写的java类和一些第三方提供的jar包里的类,那么怎么来实现呢? 今天带大家来完成这件事. 先分享代码: 1.这个类是 ...

  3. Codeforces 1053 B - Vasya and Good Sequences

    B - Vasya and Good Sequences 思路: 满足异或值为0的区间,必须满足一下条件: 1.区间中二进制1的个数和为偶数个; 2.区间二进制1的个数最大值的两倍不超过区间和. 如果 ...

  4. SPOJ 刷题记录

    按点赞数降序 297 二分 #include<bits/stdc++.h> using namespace std; #define fi first #define se second ...

  5. 子数组最小值的总和 Sum of Subarray Minimums

    2018-09-27 23:33:49 问题描述: 问题求解: 方法一.DP(MLE) 动态规划的想法应该是比较容易想到的解法了,因为非常的直观,但是本题的数据规模还是比较大的,如果直接使用动态规划, ...

  6. Linux awk命令详解 + 练习

    https://www.cnblogs.com/ftl1012/p/9250541.html 练习步骤: 1.我先是在root文件下面创建一个yan.txt文件,然后在文件中随便敲了几个字符串,由空格 ...

  7. 昂达 v891 v1 终于 删除 windows 分区 并且恢复了容量。

    参考了很多文章(最后列出重要的),却始终失败. 途中因为乱改分区表,竟然fastboot 都进不去了,当时真是欲哭无泪. 总结关键点: 1) partition.tbl不能把硬盘剩余空间全给data分 ...

  8. JSON格式简介

    一.JSON:JavaScript Object Notation的简写,是一种轻量级数据交换格式. 二.数据类型:标量.序列(数组).映射(key-value) 三.JSON的四个原则 1 .并列数 ...

  9. C#如何实现类似QQ那样靠边隐藏的功能

    http://www.cnblogs.com/yechensi/archive/2009/08/02/1537145.html C#如何实现类似QQ那样靠边隐藏的功能 你想过为自己的程序添加靠边隐藏的 ...

  10. OSPF - 3,OSPF区域和LSA

    1,四种末端区域骨干区域和标准区域:1,2,3,4,5,包含5类LSA,为了减少某些普通区域的LSA(主要就是4类和5类,有时做绝到连3类也不要了),引入了末梢区域.同时为了确保数据能出去,一般ABR ...