Spring Boot集成JSON Web Token(JWT)

A: Certification

Before looking at JWT first look at the traditional session-based authentication and token authentication.

1.1 Traditional session authentication

http protocol is a stateless protocol, ie, the browser sends a request to the server, the server does not know the request is sent to which user. To let the server know which request is sent to the user and require users to provide a user name and password for authentication. When the browser first accesses the server (assuming that the interface is login), server authentication username and password, the server will generate a sessionid (only the first will be generated, the other will use the same sessionid), and the session and user information associate, then sessionid returned to the browser, the browser receives sessionid to save Cookie, when the user accesses the server is the second time value will carry Cookie, Cookie server to get the value, and then get to the sessionid, according to associate acquiring sessionid user information.

public the User Login (the HttpServletRequest Request, the HttpServletResponse Response) throws of AuthenticationException { 
String username = request.getParameter ( " username " ); 
String password = request.getParameter ( " password " ); 
the User User = userService.login (username, password);
 IF (the user == null ) {
 the throw  new new AuthenticationException ( " user name or password error " ); 
} 
// returns the current session associated with this request, if no session is to create a new
 // need to record the session on the server side 
HttpSession session =request.getSession (); 
session.setAttribute ( " the User " , the User);
 // allow the browser to save the cookie sessionid
 // Cookie cookie = new new Cookie ( "sessionid", session.getId ());
 // cookie. setPath ( "/");
 // response.addCookie (Cookie); 
return User; 
} 
public Object getUserInfo (the HttpServletRequest request) {
 // Get request from the Cookie
 // Get the Cookie from the sessionid
 // Get sessionid according to the corresponding Session Object
 // get the associated user information from the session 
the HttpSession session = Request.getSession (); 
Object user = session.getAttribute ("user");
return user;
}

session disadvantages:

  • Session: Each user authentication after our application, our application on the server must do a record, to facilitate the identification of the user's next request, general session are stored in memory, and as the authenticated user increase in the cost of the server will be significantly increased.
  • Scalability: After user authentication, the server doing the authentication record, if authenticated records are stored in memory, then it means that the user requests the next request must also be on this server, so as to get the authorization of resources, so in a distributed application, the corresponding limits the ability of the load balancer. This also means that limits the scalability of applications. That can not meet the single sign-on
  • CSRF: Because it is based on a cookie to identify the user, if the cookie is intercepted, the user could be vulnerable to cross-site request forgery attacks.

1.2 token-based authentication

token principle:

  1. Username and password request Login Interface
  2. Interface login authentication username and password
  3. Uuid as a login interface generation token, as the value of the user information, and then saved to the cache redis jedis.set (token, user);
  4. Interface login information and return the user token
  5. The browser will be saved to a local token
  6. When the request token value carried on another interface
  7. According to the interface token to check the cache, if found on the call interface, if no newspaper token error (typically done by checking interceptors)
public String auth(String username, String password) throws AuthenticationException {
User user = userService.login(username, password);
if (user == null) {
throw new AuthenticationException("用户名或密码错误");
}
String token = UUID.randomUUID().toString();
redisClient.set(token, user);
return token;
}
public Object getUserInfo(@RequestHeader("token") String token) throws AuthenticationException {
User user = redisClient.get(token);
if (user == null) {
throw new AuthenticationException("token不可用");
}
return user;
}

 

session and token difference:

In principle this way and manner similar session, carrying value is a client call interface, server to obtain information through the user's value.

Except that the session is to save the information to the machine's memory, there is a limit (to load only the same machine) for load balancing, token is saved to the cache server (redis), there is no restriction on load balancing, if you use the same redis server can also ensure that a single sign-on. session generally used in PC, token can be used on a PC can also be used in the APP.

1.3 JWT

(Recommended collection) | Spring Boot integration JSON Web Token (JWT)

1.3.1 Introduction

JSON Web Token (JWT) is a statement in order to pass between the network environment is performed based on open standards JSON ((RFC 7519), which defines a compact (Compact) and self-contained (Self-contained) manner for JSON objects between the parties to secure transmission of information. This information can be verified and trust through digital signatures. You can use secret (using HMAC algorithm) or using RSA public / private key pairs JWT sign .JWT the statement is generally used to provide and service providers transfer between the user identity is authenticated identity information in order to obtain resources from the server, you can also add some additional business logic other information necessary to declare that the token can be directly used for authentication, can also be encrypted. is the most popular cross-domain authentication solutions.

  • Compact (compact): due to their smaller size, JWT may be transmitted by the URL, POST or HTTP header parameter. Further, the smaller the size, the faster the transmission means.
  • Self-contained (self-contained): Payload (Playload) contains all the necessary information about the user, avoiding multiple query the database.

1.3.2 application scenarios

  • Authentication (Authentication): This is the most common JWT situation. Once the user logs on, every subsequent request will contain the JWT, the user is allowed access token allows routing, services and resources. Single sign-on is widely used today JWT's a feature, because the cost is small and can easily use across different domains.
  • Single sign-distributed sites (SSO)
  • Information Exchange (exchange of information): JSON Web Tokens are a good way to secure transmission of information between the parties. Because JWT can sign: for example, using public / private key pair, it is possible to determine the sender is who they say they are. In addition, the use of headers and payload calculated signature, so you can verify that the content has not been tampered with.

1.3.3 Syntax

jwt has three components, each in part by dividing the dot header.payload.signature

  • Header (header) is a JSON object, metadata JWT described generally like the following
  • Payload (payload) is a JSON object, used to store the actual data transfer needs
  • Visa (signature) using a key sign of the header and payload, prevent data tampering.

① header header

The head is a Jwt the JSON, then Base64URL coding, carrying two pieces of information:

  • Declared type typ, indicates that the token (token) type (type), JWT JWT token unified written as
  • Assertion of the encryption algorithm alg, usually directly HMACSHA256, is HS256, you can also use RSA, support for many algorithms (HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512, PS256, PS384)

var header = Base64URL({ "alg": "HS256", "typ": "JWT"})

Base64URL: Header and Payload of type string algorithm Base64URL. This algorithm Base64 algorithm with essentially similar, but there are some small differences. JWT as a token (token), a situation that may put some URL (such as api.example.com/?token=xxx). There are three characters Base64 +, /, and =, which have a special meaning in the URL, it is to be replaced: = is omitted, replaced + - / _ replaced. This is Base64URL algorithm.

② load payload

JSON payload is a string, where the carrier is the specific content of the message is also required Base64URL coding, the payload may contain predefined 7 available, they are not compulsory but recommended, may be added to any custom key

  1. iss (issuer): jwt issuer
  2. sub (subject): jwt are for the user
  3. aud (audience): jwt the receiving side, the audience
  4. exp (expiration time): jwt expiration time, the expiration date must be greater than the issue of time
  5. nbf (Not Before): take effect, defined before what time.
  6. iat (Issued At): jwt the issue of time
  7. jti (JWT ID): jwt unique identity, is mainly used as a one-time token, in order to avoid a replay attack.

// The token issued to 1234567890, the name is John Doe (custom fields), the issue of time is 1516239022

var payload = Base64URL( {"sub": "1234567890", "name": "John Doe", "iat": 1516239022})

Note, JWT in the payload is not encrypted, but Base64URL coding it, anyone can get to decode, so do not put sensitive information inside.

③ signature

Signature is a signature part of the first two parts, prevent data tampering.

var header = Base64URL({ "alg": "HS256", "typ": "JWT"});
var payload = Base64URL( {"sub": "1234567890", "name": "John Doe", "iat": 1516239022});
var secret = "私钥";
var signature = HMACSHA256(header + "." + payload, secret);
var jwt = header + "." + payload + "." + signature;

We can use jwt.io debugger to decode, verify and generate the JWT:

(Recommended collection) | Spring Boot integration JSON Web Token (JWT)

 

Note: secret is stored on the server side, the issue generated jwt also on the server side, secret is used to authenticate the issuance and jwt of jwt, so it is your server's private key, in any scenario should not be revealed to go. Once the client has learned the secret, it means that the client can be self-signed jwt up.

1.3.4 jwt features

  • Because of the versatility of json, so JWT can be cross-language support, like JAVA, JavaScript, NodeJS, PHP and many other languages ​​can be used.
  • Because of the payload section, so you can store some JWT other business logic necessary for non-sensitive information in itself.
  • It does not require the server to save session information, so it is easy to extend the application

JWT several features

(1) JWT default is not encrypted, but also can be encrypted. After generating the original Token, it can be re-encrypted with a key once.

(2) in the case JWT not encrypted, not sensitive data (such as passwords) JWT written, unless payload is encrypted. Protect the secret private key that is very important.

(3) JWT not only can be used for authentication, it can also be used to exchange information. Effective use of JWT, the number of server queries the database can be reduced.

(4)JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。

(5)JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。

(6)为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。

1.3.5 JWT的优点:

  • 体积小,因而传输速度更快
  • 多样化的传输方式,可以通过URL传输、POST传输、请求头Header传输(常用)
  • 简单方便,服务端拿到jwt后无需再次查询数据库校验token可用性,也无需进行redis缓存校验
  • 在分布式系统中,很好地解决了单点登录问题
  • 很方便的解决了跨域授权问题,因为跨域无法共享cookie

1.3.6 JWT的缺点:

  • 因为JWT是无状态的,因此服务端无法控制已经生成的Token失效,是不可控的,这一点对于是否使用jwt是需要重点考量的
  • 获取到jwt也就拥有了登录权限,因此jwt是不可泄露的,网站最好使用https,防止中间攻击偷取jwt
  • 在退出登录 / 修改密码时怎样实现JWT Token失效https://segmentfault.com/q/1010000010043871

1.3.7 JWT安全性:

JWT被确实存在被窃取的问题,但是如果能得到别人的token,其实也就相当于能窃取别人的密码,这其实已经不是JWT安全性的问题。网络是存在多种不安全性的,对于传统的session登录的方式,如果别人能窃取登录后的sessionID,也就能模拟登录状态,这和JWT是类似的。为了安全,https加密非常有必要,对于JWT有效时间最好设置短一点。

1.3.8 JWT常见问题

① JWT 安全吗?

Base64编码方式是可逆的,也就是透过编码后发放的Token内容是可以被解析的。一般而言,不建议在有效载荷内放敏感信息,比如使用者的密码。

② JWT Payload 內容可以被伪造吗?

JWT其中的一个组成内容为Signature,可以防止通过Base64可逆方法回推有效载荷内容并将其修改。因为Signature是经由Header跟Payload一起Base64组成的。

③ 如果我的 Cookie 被窃取了,那不就表示第三方可以做 CSRF 攻击?

是的,Cookie丢失,就表示身份就可以被伪造。故官方建议的使用方式是存放在LocalStorage中,并放在请求头中发送。

④ 空间及长度问题?

JWT Token通常长度不会太小,特别是Stateless JWT Token,把所有的数据都编在Token里,很快的就会超过Cookie的大小(4K)或者是URL长度限制。

⑤ Token失效问题?

无状态JWT令牌(Stateless JWT Token)发放出去之后,不能通过服务器端让令牌失效,必须等到过期时间过才会失去效用。

假设在这之间Token被拦截,或者有权限管理身份的差异造成授权Scope修改,都不能阻止发出去的Token失效并要求使用者重新请求新的Token。

1.3.9 JWT使用建议

  • Payload中的exp时效不要设定太长。
  • 开启Only Http预防XSS攻击。
  • 如果担心重播攻击(replay attacks )可以增加jti(JWT ID),exp(有效时间) Claim。
  • 在你的应用程序应用层中增加黑名单机制,必要的时候可以进行Block做阻挡(这是针对掉令牌被第三方使用窃取的手动防御)。

二:io.jsonwebtoken.jjwt

jwt使用流程

(Recommended collection) | Spring Boot integration JSON Web Token (JWT)

 

一般是在请求头里加入Authorization,并加上Bearer标注:

// Authorization: Bearer <token>
getToken('api/user/1', {
headers: {
'Authorization': 'Bearer ' + token
}
})

io.jsonwebtoken是最常用的工具包。

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>

application.properties

jwt.secret=JO6HN3NGIU25G2FIG8V7VD6CK9B6T2Z5
jwt.expire=60000
JwtToken类
@Configuration
public class JwtToken {
private static Logger logger = LoggerFactory.getLogger(JwtToken.class);
/** 秘钥 */
@Value("${jwt.secret}")
private String secret;
/** 过期时间(秒) */
@Value("${jwt.expire}")
private long expire;
/**
* 生成jwt token
*/
public String generateToken(Long userId) {
Date nowDate = new Date();
Date expireDate = new Date(nowDate.getTime() + expire * 1000);
return Jwts.builder()
.setHeaderParam("typ", "JWT")
.setSubject(userId + "")
.setIssuedAt(nowDate)
.setExpiration(expireDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public Claims getClaimByToken(String token) {
if (StringUtils.isEmpty(token)) {
return null;
}
String[] header = token.split("Bearer");
token = header[1];
try {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}catch (Exception e){
logger.debug("validate is token error ", e);
return null;
}
}
/**
* token是否过期
* @return true:过期
*/
public static boolean isTokenExpired(Date expiration) {
return expiration.before(new Date());
}
// Getter && Setter
}
JwtController
@RestController
public class JwtController {
@Autowired
private JwtToken jwtToken;
@PostMapping("/login")
public String login(User user) {
// 1. 验证用户名和密码
// 2. 验证成功生成token
Long userId = 666L;
String token = jwtToken.generateToken(userId);
return token;
}
@GetMapping("/getUserInfo")
public String getUserInfo(@RequestHeader("Authorization") String authHeader) throws AuthenticationException {
// 黑名单token
List<String> blacklistToken = Arrays.asList("禁止访问的token");
Claims claims = jwtToken.getClaimByToken(authHeader);
if (claims == null || JwtToken.isTokenExpired(claims.getExpiration()) || blacklistToken.contains(authHeader)) {
throw new AuthenticationException("token 不可用");
}
String userId = claims.getSubject();
// 根据用户id获取接口数据返回接口
return userId;
}
}
@Configuration
public class JwtToken {
private static Logger logger = LoggerFactory.getLogger(JwtToken.class);
/** 秘钥 */
@Value("${jwt.secret}")
private String secret;
/** 过期时间(秒) */
@Value("${jwt.expire}")
private long expire;
/**
* 生成jwt token
*/
public String generateToken(Long userId) {
Date nowDate = new Date();
Date expireDate = new Date(nowDate.getTime() + expire * 1000);
return Jwts.builder()
.setHeaderParam("typ", "JWT")
.setSubject(userId + "")
.setIssuedAt(nowDate)
.setExpiration(expireDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public Claims getClaimByToken(String token) {
if (StringUtils.isEmpty(token)) {
return null;
}
String[] header = token.split("Bearer");
token = header[1];
try {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}catch (Exception e){
logger.debug("validate is token error ", e);
return null;
}
}
/**
* token是否过期
* @return true:过期
*/
public static boolean isTokenExpired(Date expiration) {
return expiration.before(new Date());
}
// Getter && Setter
}
JwtController
@RestController
public class JwtController {
@Autowired
private JwtToken jwtToken;
@PostMapping("/login")
public String login(User user) {
// 1. 验证用户名和密码
// 2. 验证成功生成token
Long userId = 666L;
String token = jwtToken.generateToken(userId);
return token;
}
@GetMapping("/getUserInfo")
public String getUserInfo(@RequestHeader("Authorization") String authHeader) throws AuthenticationException {
// 黑名单token
List<String> blacklistToken = Arrays.asList("禁止访问的token");
Claims claims = jwtToken.getClaimByToken(authHeader);
if (claims == null || JwtToken.isTokenExpired(claims.getExpiration()) || blacklistToken.contains(authHeader)) {
throw new AuthenticationException("token 不可用");
}
String userId = claims.getSubject();
// 根据用户id获取接口数据返回接口
return userId;
}
}

 

 

Guess you like

Origin www.cnblogs.com/cndarren/p/11518443.html