One, foreword

I talked about XSS defense yesterday, worked overtime this morning, and had a meeting this afternoon. I took some time to write about Shiro today. I searched a lot of projects on GitHub, and as the Springboot family bucket is more and more abundant, Most permission systems start using SpringSecurity, but since you haven’t learned about SpringSecurity yet, let’s take a look at Shiro integration JWT, followed by a brief review of Shiro’s basics.

2. Shiro’s concept

Shiro is what?

Apache Shiro (pronounced “shee – roh”, The Japanese word for “castle”) is powerful and easy-use Java security framework that performs so short a period of authentication,

Shiro, an Apache project, is a powerful, easy-to-use Java security framework that is, in essence, an authentication and authorization framework

The main functions are as follows:

  • certification
  • authorization
  • Session management
  • encryption
  • Web support
  • The cache
  • Remember that I

For more information, please check the official website. I will arrange Shiro’s specific content in detail later.

Third, JWT

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way to securely transfer information between parties as JSON objects. Because this information is digitally signed, it can be verified and trusted. JWT can be signed confidentially (using the HMAC algorithm) or using public/private key pairs from RSA or ECDSA.

Composition of JWT:

  • The header
  • The payload
  • The signature
xxxxx.yyyyy.zzzzz
Copy the code

1, the headers

Headers typically consist of two parts: the type of token (JWT) and the signature algorithm used, such as HMAC SHA256 or RSA.

{
  "alg": "HS256"."typ": "JWT"
}
Copy the code

2. Payload

In this case, the payloads are mainly non-private (passwords, ID numbers, phone numbers). Generally, we store user ids, account numbers, and so on

{
  "sub": "1234567890"."name": "John Doe"."admin": true
}
Copy the code

3, signature

To use the HMAC SHA256 algorithm, create a signature in the following ways:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)
Copy the code

The final GENERATED JWT is:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJ SMeKKF2QT4fwpMeJf36POk6yJV_adQssw5cCopy the code

Shiro and JWT

1. Basic process

The specific process is as follows:

  • We enter the account, password and other information on the front-end login interface to request the back-end for verification
  • After the back-end authenticates the account and password, the token is generated using the JWT tool class
  • After receiving the token, the front-end saves it locally and puts the token into the header for each request
  • After receiving the request from the front end, the back end obtains the token in the user header through the interceptor for verification
  • If the verification is successful, related business operations are performed and the results are returned to the front end
  • If the verification fails, an error message is returned to the front-end

2. Why do YOU need JWT?

With the growth of business, the current single architecture can no longer meet the business needs, so we need to use the load balancing technology. We use the traditional session, which can only be valid on the current server. If the load is balanced to other servers, it is impossible to verify whether the login has happened

JWT also addresses a single sign-on, frequent acquisition of sessions, and a server that stores a large number of sessions. Using this technology, you can verify it no matter which server accesses it

One of the most important issues is that JWT supports multiple clients. Previously, using HttpSession only supported browsers, but JWT can support devices such as Android

3, Coding

3.1 Importing Dependencies
<! --shiro--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.53.</version>
</dependency>
<dependency>
   <groupId>org.apache.shiro</groupId>
   <artifactId>shiro-spring</artifactId>
   <version>1.53.</version> </dependency> <! --jwt--> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.103.</version> </dependency> <! <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <! <dependency> <groupId>org.apache.commons</groupId> <artifactId> Commons -lang3</artifactId> <version>3.11</version> </dependency> <! - provide access to the HTTP server functions - > < the dependency > < groupId > org.. Apache httpcomponents < / groupId > < artifactId > httpcore < / artifactId > <version>4.413.</version> </dependency> <! </groupId> <artifactId> </artifactId> </artifactId> </artifactId> </artifactId> </artifactId> </artifactId> </artifactId> </dependency>Copy the code
3.2 Creating a JWT utility class

Define the key and expiration time

For easy maintenance, we put the key and expiration time in the configuration file

Gofly: # JWT configure JWT: # secret: MIC123456 # expiration time5Day expire:5# Cache expiration time10Day cache - the expire:10
Copy the code

JWT tools

/** * JWT utility class */
public class JwtUtil {

    / / key
    @Value("${gofly.jwt.secret}")
    private String secret;

    @Value("${gofly.jwt.expire}")
    private int expire;

    /** * Create token *@paramUserId userId *@return* /
    public String createToken(int userId){
        // Current date + Expiration time = Expiration date
        Date date = DateUtil.offset(new Date(), DateField.DAY_OF_YEAR, expire).toJdkDate();
        Algorithm algorithm = Algorithm.HMAC256(secret);
        JWTCreator.Builder builder = JWT.create();
        String token = builder.withClaim("userId", userId).withExpiresAt(date).sign(algorithm);
        return token;
    }

    /** * Get user ID *@paramToken tokens *@return* /
    public int getUserId(String token){
        try{
            // Decrypt the token
            DecodedJWT decode = JWT.decode(token);
            // Get the user ID
            Integer userId = decode.getClaim("userId").asInt();
            return userId;
        }catch (Exception e){
            throw new GoflyException("The token is invalid"); }}/** * Verify that the token is valid *@paramToken tokens * /
    public void verifierToken(String token){
        // Get the encryption algorithm
        Algorithm algorithm = Algorithm.HMAC256(secret);
        JWTVerifier verifier = JWT.require(algorithm).build();
        / / checkverifier.verify(token); }}Copy the code
3.3 Encapsulating authentication Objects

Basic structure of Shiro:

When we use the account and password to log in, we need to encapsulate the account and password into the UsernamePasswordToken object, and then log in

UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("username"."password");
Copy the code

As you can see from the source code, UsernamePasswordToken implements two interfaces, but both inherit from the AuthenticationToken interface

So, we just need to create an object implementationAuthenticationTokenYou can:

/** * Custom token */
public class ShiroToken implements AuthenticationToken {

    private String token;

    public ShiroToken(String token){
        this.token = token;
    }

    @Override
    public Object getPrincipal(a) {
        return token;
    }

    @Override
    public Object getCredentials(a) {
        returntoken; }}Copy the code
3.4 Implementing custom Realms

Because this is just a simple concept, the specific implementation of authentication and authorization needs to be done.

/** * custom Realm */
@Component
public class UserRealm extends AuthorizingRealm {

    /** * After customizing the token authentication object, you must override this method, otherwise an error will be reported *@param token
     * @return* /
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof ShiroToken;
    }

    /** * Authorization *@param principalCollection
     * @return* /
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        / * * * 1. TODO: Get the user ID * from the token --> Because we have implemented the getPrincipal method in ShiroToken, Returns the token, so principalCollection. GetPrimaryPrincipal () to obtain is the token of * -- - > we only need to call * 2 JWTUtil tools for userId.TODO:Get user permission information from userId * --> Register permissions with SimpleAuthorizationInfo */
        return info;
    }

    /** * Certification *@param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo();
        / * * *TODO:Authentication operation */
        returninfo; }}Copy the code
3.5 Refreshing a token

Why do you need to refresh the token?

Because when we configured JWT, we set the expiration time of JWT to 5 days;

Gofly: # JWT configure JWT: # secret: MIC123456 # expiration time5Day expire:5# Cache expiration time10Day cache - the expire:10
Copy the code

We assume that when we access the API on day 4, the API can be accessed normally, but if we access the API on day 5, the token is expired and the user needs to log in again:

ThisAccessedTime indicates the last access time. ThisAccessedTime indicates the current access time. ThisAccessedTime indicates the last access time. As long as thisAccessedTime-thisAccessedTime<=maxInactiveInterval can access normally, as long as it does not exceed maxInactiveInterval, you do not need to log in again

Compare these two:

  • For example, if the expiration date is 15 days, access is available on the 14th day, but the access is unavailable on the 15th day
  • Second: as long as the interval does not exceed the maximum expiration time, for example, if the expiration time is 15 days, the access is available on the 14th day, and the access is also available on the 15th day

How to refresh the token

  • Double token:

    Access_token: Access token that is carried with each access API

    Refresh_token: refresh_token. When the access_token expires, refresh_token is used to exchange for a new access_token on the server. The refresh_token is automatically refreshed and the expiration time is increased

    If both expire, the user needs to log in again

  • Token cache

    The token generated by JWT is responded to the user and stored in Redis. The expiration time of the token stored in Redis is longer than that generated by JWT

Verification process:

1. JwtFilter intercepts client requests and verifies token validity

2. After the verification is passed, the access is normal

3. If the verification fails, check whether the existing token in Redis is expired. If the token is not expired, a new token is generated and stored in Redis

Two cases of expiration

The Token is invalid and there is no cache in Redis

If the token generated by JWT is invalid, we need to check whether the token stored in Redis has expired in the JwtFilter. If the token has not expired, it indicates that the user has not performed any operation for 10 days and logs in again

Token invalid, Redis cache not expired

If JWT generates a token, the token expires within 5 days. If Redis cache a token, the token expires within 10 days and the user does not need to log in again.

The token is regenerated and stored in Redis

How the client updates the token

  • After the server updates the token, it responds to the user
  • The user stores the returned token locally
  • Carry this token every time you access the API

The process of token storage

  • We define the JwtFilter class to intercept all Http requests by doing the following:
1.It encapsulates the acquisition token in the request as an authentication object2.Check whether a token is expired. If a token is expired, a new token is generated and stored in the Redis and treadLocal respectivelyCopy the code

I will continue to work tomorrow. I will write the first part tonight and try to finish the second part tomorrow. Good night, good dream 🌛