JWT combat tutorial
在Juejin. Cn/post / 691203…In this article, I documented the cumbersome use of redis to store the tooken implementation validation. Here’s JWT.
1. What is JWT
JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.
– [from official website]
1. The translation
- Introduction: Jwt. IO /introductio…
- Jsonwebtoken (JWT) is an open standard (RFC7519) that defines a compact, self-contained way to securely transfer information between parties as JSON objects. This information can be verified and trusted because it is digitally signed. JWT can sign using secret (using the HMAC algorithm) or using RSA or ECDSA’s public/private key pair
2. Popular explanation
- JWT is a JSON Web Token, which is used as a Token in a Web application in JSON form to securely transfer information between parties as JSON objects. In the process of data transmission, data encryption, signature and other related processing can be completed.
2. What can JWT do
1. The authorization
- This is the most common scenario using JWT. Once the user is logged in, each subsequent request will include JWT, allowing the user to access the routes, services, and resources that the token allows. Single sign-on is a feature of JWT that is widely used today because of its low overhead and ease of use in different domains.
2. Information exchange
- JSON Web Tokens are a great way to securely transfer information between parties. Because JWT can be signed (for example, using a public/private key pair), you can be sure that the sender is who they say they are. In addition, because the signature is computed using headers and payloads, you can verify that the content has not been tampered with.
3. Why JWT
Based on traditional Session authentication
1. Authentication mode
- As we know, the HTTP protocol itself is a stateless protocol, which means that if the user to our application provides a user name and password for user authentication, so the next time a request, the user will once again for user authentication, because based on HTTP protocol, we don’t know is which user requests, So in order for our application to be able to identify which user is making the request, we can only store a copy of the user’s login information on the server, and this login information is passed to the browser in response, telling it to save as a cookie, so that it can be sent to our application in the next request. So our application can identify the user from whom the request is coming, which is traditional session-based authentication.
2. Certification process
3. Expose the problem
-
1. After each user is authenticated by our application, our application makes a record on the server to facilitate the identification of the user’s next request. Generally speaking, sessions are stored in memory, but with the increase of authenticated users, the overhead on the server will increase significantly
-
2. After user authentication, the server makes authentication records. If the authentication records are stored in memory, the next user request must be made on this server to obtain authorized resources. This also means limiting the application’s ability to scale.
-
3. Because user identification is based on cookies, if cookies are intercepted, users will be vulnerable to cross-site request forgery attacks.
-
4. This is even more painful in a system with front-end separation: as shown in the figure below, front-end separation increases deployment complexity after application decoupling. Typically, a request is forwarded multiple times. If a session is used to carry the sessionID to the server each time, the server also queries the user information. And if you have a lot of users. This information is stored in the server memory, adding to the burden on the server. There is also CSRF (Cross-site forgery Request Attack) attack. Session is used to identify users based on cookies. If cookies are intercepted, users will be vulnerable to cross-site request forgery attack. In addition, the sessionID is an eigenvalue, which is not rich enough to express information. Not easy to scale. And if you have a multi-node back-end application. Then you need to implement the session sharing mechanism. It is not convenient for cluster applications.
JWT based certification
1. Certification process
-
First, the front end sends its user name and password to the back-end interface via a Web form. This process is typically an HTTP POST request. The recommended method is ssl-encrypted transmission (HTTPS protocol) to avoid sensitive information being sniffed.
-
After the user name and password are verified successfully, the backend uses other information, such as the user ID, as the JWT Payload. After combining the information with the header, the backend signs the information to form a JWT(Token). The resulting JWT is a string shaped like ll.zzz.xxx. token head.payload.siginature
-
The back end returns the JWT string to the front end as the result of a successful login. The front-end can save the returned results in localStorage or sessionStorage, and the front-end can delete the saved JWT when logging out.
-
The front end puts the JWT into the Authorization bit in the HTTP Header on each request. HEADER address XSS and XSRF issues
-
Back-end checks for presence, if any, to verify the validity of JWT. For example, check whether the signature is correct. Check whether the Token expires. Check whether the recipient of the Token is itself (optional).
-
After verification, the backend uses the user information contained in the JWT to perform other logical operations and returns corresponding results.
2. JWT advantage
-
Compact: You can send via URL, POST parameter, or HTTP header, because data is small and fast
-
Self-contained: The payload contains all the information the user needs, avoiding multiple queries to the database
-
Because tokens are stored on the client side in JSON-encrypted form, JWT is cross-language and in principle any Web form is supported.
-
There is no need to save session information on the server, especially for distributed micro services.
4. What is the structure of JWT?
token string ====> header.payload.siginature token
1. Token composition
- 1. The Header (Header)
- It’s a Payload.
- 3. The Signature (Signature)
- Therefore, JWT usually looks like this: xxXXx.yyyyy.zzzzz header.payload-signature
2.Header
-
The header usually consists of two parts: the type of token (that is, JWT) and the signature algorithm used, such as HMAC SHA256 or RSA. It uses Base64 encoding to form the first part of the JWT structure.
-
Note :Base64 is an encoding, which means it can be translated back to its original form. It is not an encryption process.
{
"alg": "HS256"."typ": "JWT"
}
Copy the code
3.Payload
- The second part of the token is the payload, which contains the declaration. Declarations are declarations about entities (usually users) and other data. Again, it uses Base64 encoding to form the second part of the JWT structure
{
"sub": "1234567890"."name": "John Doe"."admin": true
}
Copy the code
4.Signature
- The first two parts are encoded in Base64, meaning that the front end can unlock the information inside. Signature needs to use the encoded header, payload, and a key provided by us, and then use the Signature algorithm specified in the header (HS256) to sign. The purpose of the signature is to ensure that JWT has not been tampered with
- For example, HMACSHA256(base64UrlEncode(header) + “.” + base64UrlEncode(payload),secret);
Signature purpose
- The final step in the signing process is actually to sign the header and payload to prevent the content from being tampered with. If someone decodes the header and payload contents, modifies them, encodes them, and finally adds the previous signatures to form a new JWT, the server will determine that the signature formed by the new header and payload is different from the signature attached to the JWT. If you want to sign new headers and payloads, the resulting signature will be different without knowing the key the server used for encryption.
Information security issues
-
Base64 is a code that is reversible, so wouldn’t my information be exposed?
-
Yes. Therefore, in JWT, no sensitive data should be added to the payload. In the example above, we are transferring the User ID of the User. This value is not actually sensitive content and is generally known to be safe. But things like passwords cannot be placed in JWT. If you put a user’s password in JWT, a malicious third party can quickly learn your password through Base64 decoding. JWT is therefore suitable for delivering non-sensitive information to Web applications. JWT is also often used to design user authentication and authorization systems, and even to implement single sign-on for Web applications.
5. Put them together
- The output is three dot-separated Base64-URL strings that can be easily passed between HTML and HTTP environments and are more compact than XML-based standards such as SAML.
- Compact can be sent through urls, POST parameters, or HTTP headers because of the small amount of data and the fast transfer speed
- A self-contained load contains all the information the user needs, avoiding multiple queries to the database
5. Use the JWT
1. Introduce dependencies
<! - the introduction of JWT - >
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
Copy the code
2. The token is generated
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND, 90);
// Generate a token
String token = JWT.create()
.withClaim("username"."Zhang")// Set a custom user name
.withExpiresAt(instance.getTime())// Set the expiration time
.sign(Algorithm.HMAC256("token! Q2W#E$RW"));// Setting the signature secret is complex
// Outputs the token
System.out.println(token);
Copy the code
- Generate results
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOlsicGhvbmUiLCIxNDMyMzIzNDEzNCJdLCJleHAiOjE1OTU3Mzk0NDIsInVzZXJuYW1lIjoi5by g5LiJIn0.aHmE3RNqvAjFr_dvyn_sD2VJ46P7EGiS5OBMO_TI5jg
3. Parse data by token and signature
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("token! Q2W#E$RW")).build();
DecodedJWT decodedJWT = jwtVerifier.verify(token);
System.out.println("Username:" + decodedJWT.getClaim("username").asString());
System.out.println("Expiration Time:"+decodedJWT.getExpiresAt());
Copy the code
4. Common exception information
- SignatureVerificationException: signature inconsistencies
- TokenExpiredException: Token expiration exception
- Does not match the abnormal AlgorithmMismatchException: algorithm
- InvalidClaimException: Invalid payload is abnormal
6. Encapsulate the tool classes
public class JWTUtils {
private static String TOKEN = "token! Q@W3e4r";
/** * Generate token *@paramMap // Payload *@returnReturns the token * /
public static String getToken(Map<String,String> map){
JWTCreator.Builder builder = JWT.create();
map.forEach((k,v)->{
builder.withClaim(k,v);
});
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND,7);
builder.withExpiresAt(instance.getTime());
return builder.sign(Algorithm.HMAC256(TOKEN)).toString();
}
/** * verify token *@param token
* @return* /
public static void verify(String token){
JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);
}
/** * Retrieve token payload *@param token
* @return* /
public static DecodedJWT getToken(String token){
returnJWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token); }}Copy the code
7. Integrated springboot
0. Set up springBoot + Mybatis + JWT environment
- Introduction of depend on
- Write the configuration
<! - the introduction of JWT - >
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
<! - introduction of mybatis -- -- >
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<! - the introduction of lombok -- -- >
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<! Druid - in - >
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.19</version>
</dependency>
<! - introduction of mysql -- -- >
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
Copy the code
server.port=8989
spring.application.name=jwt
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/jwt? characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=root
mybatis.type-aliases-package=com.baizhi.entity
mybatis.mapper-locations=classpath:com/baizhi/mapper/*.xml
Create a directory with/under resource.
logging.level.com.baizhi.dao=debug
Copy the code
1. Develop the database
- The simplest table structure is used here to validate JWT usage
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'primary key',
`name` varchar(80) DEFAULT NULL COMMENT 'Username',
`password` varchar(40) DEFAULT NULL COMMENT 'User password'.PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
Copy the code
2. The development of the entity
@Data
@Accessors(chain=true)
public class User {
private String id;
private String name;
private String password;
}
Copy the code
3. Develop DAO interface and mapper.xml
@Mapper // This is equivalent to the @mapperscan annotation on the startup class that Spring can scan
public interface UserDAO {
User login(User user);
}
Copy the code
<mapper namespace="com.baizhi.dao.UserDAO">
<! -- This is not the point -->
<select id="login" parameterType="User" resultType="User">
select * from user where name=#{name} and password = #{password}
</select>
</mapper>
Copy the code
4. Develop Service interfaces and implementation classes
public interface UserService {
User login(User user); // Login interface
}
Copy the code
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserDAO userDAO;
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public User login(User user) {
User userDB = userDAO.login(user);
if(userDB! =null) {return userDB;
}
throw new RuntimeException("Login failed ~~"); }}Copy the code
5. Develop the controller
@RestController
@Slf4j
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/user/login")
public Map<String,Object> login(User user) {
Map<String,Object> result = new HashMap<>();
log.info("Username: [{}]", user.getName());
log.info(Password: [{}], user.getPassword());
try {
User userDB = userService.login(user);
Map<String, String> map = new HashMap<>();// Payload
map.put("id",userDB.getId());
map.put("username", userDB.getName());
String token = JWTUtils.getToken(map);
result.put("state".true);
result.put("msg"."Login successful!!");
result.put("token",token); // Token information is returned successfully
} catch (Exception e) {
e.printStackTrace();
result.put("state"."false");
result.put("msg",e.getMessage());
}
returnresult; }}Copy the code
6. Add the test data startup project to the database
7. Failed to simulate the login through Postman
8. The postman login is successful
9. Write test interfaces
@PostMapping("/test/test")
public Map<String, Object> test(String token) {
Map<String, Object> map = new HashMap<>();
try {
JWTUtils.verify(token);
map.put("msg"."Verified ~~~");
map.put("state".true);
} catch (TokenExpiredException e) {
map.put("state".false);
map.put("msg"."Token has expired!!");
} catch (SignatureVerificationException e){
map.put("state".false);
map.put("msg"."Wrong signature!!");
} catch (AlgorithmMismatchException e){
map.put("state".false);
map.put("msg"."Encryption algorithms don't match!!");
} catch (Exception e) {
e.printStackTrace();
map.put("state".false);
map.put("msg"."Invalid token ~ ~");
}
return map;
}
Copy the code
10. Request the interface through Postman
11. The problem?
- Do you need to pass token data each time using the above method, and each method needs to verify that the token code is redundant and not flexible enough? How to optimize
- Optimizations using interceptors
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("token");
Map<String,Object> map = new HashMap<>();
try {
JWTUtils.verify(token);
return true;
} catch (TokenExpiredException e) {
map.put("state".false);
map.put("msg"."Token has expired!!");
} catch (SignatureVerificationException e){
map.put("state".false);
map.put("msg"."Wrong signature!!");
} catch (AlgorithmMismatchException e){
map.put("state".false);
map.put("msg"."Encryption algorithms don't match!!");
} catch (Exception e) {
e.printStackTrace();
map.put("state".false);
map.put("msg"."Invalid token ~ ~");
}
// Convert map to JSON, Jackson mode
String json = new ObjectMapper().writeValueAsString(map);
// corresponding to the front-end
response.setContentType("application/json; charset=UTF-8");
response.getWriter().println(json);
return false;
}
Copy the code
@Component
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JwtTokenInterceptor()).
excludePathPatterns("/user/**") // Allow user related interfaces, such as login interfaces and registration interfaces
.addPathPatterns("/ * *"); // To intercept all paths, token authentication is required.}}Copy the code
12, JJWT
Utility class dependencies based on JJWT encapsulation:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
Copy the code
// Based on JJWT encapsulated utility classes
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
* {" alG ": "HS512","type": * {"alg": "HS512","type": {"sub":"wang","created":1489079981393,"exp":1489684781} * HMACSHA512(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret) */
@Component
public class JwtTokenUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenUtil.class);
private static final String CLAIM_KEY_USERNAME = "sub";
private static final String CLAIM_KEY_CREATED = "created";
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
/** * based on the token responsible for generating the JWT */
private String generateToken(Map<String, Object> claims) {
return Jwts.builder()
.setClaims(claims)
.setExpiration(generateExpirationDate())
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
/** * get the payload in JWT from token */
private Claims getClaimsFromToken(String token) {
Claims claims = null;
try {
claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
LOGGER.info("JWT format validation failed :{}",token);
}
return claims;
}
/** * Expiration time of token generation */
private Date generateExpirationDate(a) {
return new Date(System.currentTimeMillis() + expiration * 1000);
}
/** * Obtain the login user name from the token */
public String getUserNameFromToken(String token) {
String username;
try {
Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
}
/** * Verify that the token is still valid **@paramToken Indicates the token * passed by the client@paramUserDetails User information queried from the database */
public boolean validateToken(String token, UserDetails userDetails) {
String username = getUserNameFromToken(token);
returnusername.equals(userDetails.getUsername()) && ! isTokenExpired(token); }/** * Check whether the token is invalid */
private boolean isTokenExpired(String token) {
Date expiredDate = getExpiredDateFromToken(token);
return expiredDate.before(new Date());
}
/** * Get expiration time from token */
private Date getExpiredDateFromToken(String token) {
Claims claims = getClaimsFromToken(token);
return claims.getExpiration();
}
/** * Generate token */ based on user information
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
claims.put(CLAIM_KEY_CREATED, new Date());
return generateToken(claims);
}
/** * Check whether the token can be refreshed */
public boolean canRefresh(String token) {
return! isTokenExpired(token); }/** * Refresh token */
public String refreshToken(String token) {
Claims claims = getClaimsFromToken(token);
claims.put(CLAIM_KEY_CREATED, new Date());
returngenerateToken(claims); }}Copy the code